ConfigPage.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. "use client";
  2. import { useCallback, useEffect, useState } from "react";
  3. import { useRouter } from "next/navigation";
  4. import {
  5. getConfigQueryPrompts,
  6. getConfigRulePacks,
  7. getConfigWalkPolicy,
  8. getConfigWalkStrategy
  9. } from "@/lib/api/client";
  10. import type { ConfigFileResponse } from "@/lib/api/types";
  11. import { AppShell } from "@/components/layout/AppShell";
  12. import { RulePacksTab, WalkStrategyTab, WalkPolicyTab, QueryPromptsTab } from "@/features/config/configTables";
  13. const TABS = [
  14. { id: "rule-packs", label: "规则包" },
  15. { id: "walk-strategy", label: "游走策略" },
  16. { id: "walk-policy", label: "游走护栏(walk-policy)" },
  17. { id: "query-prompts", label: "Query Prompts" }
  18. ] as const;
  19. type TabId = (typeof TABS)[number]["id"];
  20. export function ConfigPage() {
  21. const router = useRouter();
  22. const [tab, setTab] = useState<TabId>("rule-packs");
  23. const [responses, setResponses] = useState<Partial<Record<TabId, ConfigFileResponse>>>({});
  24. const [error, setError] = useState<string | null>(null);
  25. const [loading, setLoading] = useState(true);
  26. const load = useCallback(async () => {
  27. setLoading(true);
  28. setError(null);
  29. try {
  30. const [rulePacks, walkStrategy, walkPolicy, queryPrompts] = await Promise.all([
  31. getConfigRulePacks(),
  32. getConfigWalkStrategy(),
  33. getConfigWalkPolicy(),
  34. getConfigQueryPrompts()
  35. ]);
  36. setResponses({
  37. "rule-packs": rulePacks,
  38. "walk-strategy": walkStrategy,
  39. "walk-policy": walkPolicy,
  40. "query-prompts": queryPrompts
  41. });
  42. } catch (err) {
  43. setError(err instanceof Error ? err.message : "配置加载失败");
  44. } finally {
  45. setLoading(false);
  46. }
  47. }, []);
  48. useEffect(() => {
  49. void load();
  50. }, [load]);
  51. const current = responses[tab];
  52. // 回退到来时的页面(通常是某个 run 的「策略配置」);无历史则兜底回 /runs。
  53. const goBack = () => {
  54. if (typeof window !== "undefined" && window.history.length > 1) {
  55. router.back();
  56. } else {
  57. router.push("/runs");
  58. }
  59. };
  60. return (
  61. <AppShell onRefresh={load} onBack={goBack}>
  62. <section className="detail-panel">
  63. <header className="detail-header">
  64. <div>
  65. <h1 className="detail-title">配置即真相(只读)</h1>
  66. <p className="muted">
  67. 运行时读取的 JSON 配置原文。只读展示;编辑走 Excel→JSON 工具链(可视化编辑留 Web V3)。
  68. </p>
  69. </div>
  70. {current ? <span className="muted">{current.source_file}</span> : null}
  71. </header>
  72. <div className="filter-bar">
  73. {TABS.map((option) => (
  74. <button
  75. key={option.id}
  76. type="button"
  77. className={`chip${tab === option.id ? " active" : ""}`}
  78. onClick={() => setTab(option.id)}
  79. >
  80. {option.label}
  81. </button>
  82. ))}
  83. </div>
  84. {loading ? <div className="loading-state">配置加载中…</div> : null}
  85. {error ? <div className="error-state">{error}</div> : null}
  86. {current && tab === "rule-packs" ? <RulePacksTab data={current.data} /> : null}
  87. {current && tab === "walk-strategy" ? <WalkStrategyTab data={current.data} /> : null}
  88. {current && tab === "walk-policy" ? <WalkPolicyTab data={current.data} /> : null}
  89. {current && tab === "query-prompts" ? <QueryPromptsTab data={current.data} /> : null}
  90. </section>
  91. </AppShell>
  92. );
  93. }