// ———— Settings ————
function PwdInput({ value, onChange, placeholder }) {
  const [show, setShow] = useState(false);
  return (
    <div style={{ position: "relative" }}>
      <input className="input mono" type={show ? "text" : "password"} value={value} onChange={e=>onChange(e.target.value)} placeholder={placeholder || "●●●●●●●●"} />
      <button type="button" onClick={()=>setShow(s=>!s)} className="btn ghost sm"
        style={{ position: "absolute", right: 4, top: 4, padding: "3px 8px" }}>
        {show ? "隐藏" : "显示"}
      </button>
    </div>
  );
}

function SettingsPage() {
  // RPA
  const [rpaHost,   setRpaHost]   = useState(() => localStorage.getItem(LS.rpaHost)   || "10.0.0.2");
  const [rpaPort,   setRpaPort]   = useState(() => localStorage.getItem(LS.rpaPort)   || "8000");
  const [rpaGitUrl, setRpaGitUrl] = useState(() => localStorage.getItem(LS.rpaGitUrl) || "");
  // Hermes（可选）
  const [hermesUrl, setHermesUrl] = useState(() => localStorage.getItem(LS.hermesUrl) || "");
  const [hermesKey, setHermesKey] = useState(() => localStorage.getItem(LS.hermesKey) || "");
  // 飞书
  const [feishuAppId,  setFeishuAppId]  = useState(() => localStorage.getItem(LS.feishuId)     || "");
  const [feishuSecret, setFeishuSecret] = useState(() => localStorage.getItem(LS.feishuSecret) || "");
  const [baseToken,    setBaseToken]    = useState(() => localStorage.getItem(LS.feishuBase)   || "");
  const [metaTableId,  setMetaTableId]  = useState(() => localStorage.getItem(LS.feishuMeta)   || "");
  const [anaTableId,   setAnaTableId]   = useState(() => localStorage.getItem(LS.feishuAna)    || "");
  const [docFolder,    setDocFolder]    = useState(() => localStorage.getItem(LS.feishuFolder) || "");
  // 分析提示词（空 = 使用服务端默认文件）
  const [rpaPrompt,   setRpaPrompt]   = useState(() => localStorage.getItem(LS.rpaPrompt) || "");
  const [feishuFields, setFeishuFields] = useState(() => {
    try { return JSON.parse(localStorage.getItem(LS.feishuFields) || "[]"); } catch { return []; }
  });

  const [wikiState,       setWikiState]       = useState("idle");
  const [wikiMsg,         setWikiMsg]         = useState("");
  const [wikiCheckState,  setWikiCheckState]  = useState("idle");   // idle | loading | ok | error
  const [wikiCheckResult, setWikiCheckResult] = useState(null);     // {initialized, detail}
  const [testState,       setTestState]       = useState({ status: "idle", results: null });
  const [feishuTest,      setFeishuTest]      = useState({ status: "idle", results: null, error: "" });
  const [rpaStatus,       setRpaStatus]       = useState("idle");   // idle | loading | ok | fail
  // 提示词相关操作状态
  const [promptOptState,  setPromptOptState]  = useState("idle");   // idle | loading | ok | error
  const [promptOptMsg,    setPromptOptMsg]    = useState("");
  const [fieldsState,     setFieldsState]     = useState("idle");   // idle | loading | ok | error
  const [fieldsMsg,       setFieldsMsg]       = useState("");
  const [promptLoading,   setPromptLoading]   = useState(false);
  const [savedAt,         setSavedAt]         = useState(null);
  const [activeSection,   setActiveSection]   = useState("s1");
  const [importMsg,       setImportMsg]       = useState("");   // 导入反馈
  // 评论抓取配置
  const [commentLimit, setCommentLimit] = useState(() => localStorage.getItem(LS.commentLimit) || "100");
  const [commentAll,   setCommentAll]   = useState(() => localStorage.getItem(LS.commentAll) === "1");
  const importRef = React.useRef(null);

  const scrollTo = (id) => {
    setActiveSection(id);
    const el = document.getElementById(id);
    if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
  };

  const canInitWiki = hermesUrl && hermesKey;

  const save = () => {
    localStorage.setItem(LS.rpaHost,    rpaHost);
    localStorage.setItem(LS.rpaPort,    rpaPort);
    localStorage.setItem(LS.rpaGitUrl,  rpaGitUrl);
    localStorage.setItem(LS.hermesUrl,  hermesUrl);
    localStorage.setItem(LS.hermesKey,  hermesKey);
    localStorage.setItem(LS.feishuId,       feishuAppId);
    localStorage.setItem(LS.feishuSecret,   feishuSecret);
    localStorage.setItem(LS.feishuBase,     baseToken);
    localStorage.setItem(LS.feishuMeta,     metaTableId);
    localStorage.setItem(LS.feishuAna,      anaTableId);
    localStorage.setItem(LS.feishuFolder,   docFolder);
    localStorage.setItem(LS.rpaPrompt,      rpaPrompt);
    localStorage.setItem(LS.feishuFields,   JSON.stringify(feishuFields));
    localStorage.setItem(LS.commentLimit, commentLimit || "100");
    localStorage.setItem(LS.commentAll,   commentAll ? "1" : "");
    setSavedAt(new Date());
  };

  const reset = () => {
    [LS.rpaHost, LS.rpaPort, LS.rpaGitUrl,
     LS.hermesUrl, LS.hermesKey,
     LS.feishuId, LS.feishuSecret, LS.feishuBase,
     LS.feishuMeta, LS.feishuAna, LS.feishuFolder,
     LS.rpaPrompt, LS.feishuFields,
     LS.commentLimit, LS.commentAll,
    ].forEach(k => localStorage.removeItem(k));
    setRpaHost("10.0.0.2"); setRpaPort("8000"); setRpaGitUrl("");
    setHermesUrl(""); setHermesKey("");
    setFeishuAppId(""); setFeishuSecret(""); setBaseToken("");
    setMetaTableId(""); setAnaTableId(""); setDocFolder("");
    setRpaPrompt(""); setFeishuFields([]);
    setCommentLimit("100"); setCommentAll(false);
    setSavedAt(null);
  };

  // ── 导出配置 ─────────────────────────────────────────────────────────────────
  const exportConfig = () => {
    save(); // 先把屏幕上的最新值写入 localStorage
    const exportKeys = [
      "rpaHost", "rpaPort", "rpaGitUrl", "rpaPrompt",
      "hermesUrl", "hermesKey",
      "feishuId", "feishuSecret", "feishuBase", "feishuMeta", "feishuAna", "feishuFolder",
      "feishuFields",
      "commentLimit", "commentAll",
    ];
    const cfg = {};
    exportKeys.forEach(k => {
      const val = localStorage.getItem(LS[k]);
      if (val !== null && val !== "") {
        if (k === "feishuFields") {
          try { cfg[k] = JSON.parse(val); } catch { cfg[k] = []; }
        } else {
          cfg[k] = val;
        }
      }
    });
    const blob = new Blob([JSON.stringify(cfg, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `oa-config-${new Date().toISOString().slice(0, 10)}.json`;
    a.click();
    URL.revokeObjectURL(url);
  };

  // ── 导入配置 ─────────────────────────────────────────────────────────────────
  const importConfig = (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      try {
        const cfg = JSON.parse(ev.target.result);
        const set = (lsKey, stateKey, setter) => {
          if (cfg[stateKey] !== undefined && cfg[stateKey] !== "") {
            localStorage.setItem(lsKey, cfg[stateKey]);
            setter(cfg[stateKey]);
          }
        };
        set(LS.rpaHost,      "rpaHost",      setRpaHost);
        set(LS.rpaPort,      "rpaPort",      setRpaPort);
        set(LS.rpaGitUrl,    "rpaGitUrl",    setRpaGitUrl);
        set(LS.hermesUrl,    "hermesUrl",    setHermesUrl);
        set(LS.hermesKey,    "hermesKey",    setHermesKey);
        set(LS.feishuId,     "feishuId",     setFeishuAppId);
        set(LS.feishuSecret, "feishuSecret", setFeishuSecret);
        set(LS.feishuBase,   "feishuBase",   setBaseToken);
        set(LS.feishuMeta,   "feishuMeta",   setMetaTableId);
        set(LS.feishuAna,    "feishuAna",    setAnaTableId);
        set(LS.feishuFolder, "feishuFolder", setDocFolder);
        set(LS.rpaPrompt,    "rpaPrompt",    setRpaPrompt);
        set(LS.commentLimit, "commentLimit", setCommentLimit);
        if (cfg.commentAll !== undefined) {
          const val = cfg.commentAll ? "1" : "";
          localStorage.setItem(LS.commentAll, val);
          setCommentAll(cfg.commentAll === true || cfg.commentAll === "1");
        }
        if (Array.isArray(cfg.feishuFields)) {
          localStorage.setItem(LS.feishuFields, JSON.stringify(cfg.feishuFields));
          setFeishuFields(cfg.feishuFields);
        }
        setSavedAt(new Date());
        setImportMsg("✓ 导入成功，配置已恢复");
        setTimeout(() => setImportMsg(""), 4000);
      } catch {
        setImportMsg("✗ 导入失败：不是有效的配置文件");
        setTimeout(() => setImportMsg(""), 4000);
      }
      e.target.value = ""; // 允许重复选同一文件
    };
    reader.readAsText(file);
  };

  // ── 提示词：加载默认值 ───────────────────────────────────────────────────────
  const loadDefaultPrompt = async () => {
    setPromptLoading(true);
    try {
      const data = await window.API.getDefaultPrompt();
      setRpaPrompt(data.prompt);
      setPromptOptMsg("已加载默认提示词");
      setPromptOptState("ok");
    } catch (e) {
      setPromptOptMsg(e.message); setPromptOptState("error");
    } finally {
      setPromptLoading(false);
    }
  };

  // ── 提示词：Hermes 优化 ──────────────────────────────────────────────────────
  const optimizePrompt = async () => {
    if (!rpaPrompt.trim()) { setPromptOptMsg("提示词不能为空"); setPromptOptState("error"); return; }
    save();
    setPromptOptState("loading"); setPromptOptMsg("");
    try {
      const data = await window.API.optimizePrompt(rpaPrompt);
      setRpaPrompt(data.prompt);
      localStorage.setItem(LS.rpaPrompt, data.prompt);
      setPromptOptState("ok");
      setPromptOptMsg("优化完成，已更新到上方编辑框");
    } catch (e) {
      setPromptOptState("error"); setPromptOptMsg(e.message);
    }
  };

  // ── 提示词：提交并推导飞书字段 ────────────────────────────────────────────────
  const genFields = async () => {
    if (!rpaPrompt.trim()) { setFieldsMsg("请先填写提示词"); setFieldsState("error"); return; }
    save();
    setFieldsState("loading"); setFieldsMsg("");
    try {
      const data = await window.API.genFeishuFields(rpaPrompt);
      setFeishuFields(data.fields || []);
      localStorage.setItem(LS.feishuFields, JSON.stringify(data.fields || []));
      setFieldsState("ok");
      setFieldsMsg(`推导完成，共 ${(data.fields||[]).length} 个字段`);
    } catch (e) {
      setFieldsState("error"); setFieldsMsg(e.message);
    }
  };

  // ── 字段表格行编辑 ────────────────────────────────────────────────────────────
  const updateField = (idx, key, val) => {
    setFeishuFields(prev => {
      const next = prev.map((f, i) => i === idx ? { ...f, [key]: val } : f);
      localStorage.setItem(LS.feishuFields, JSON.stringify(next));
      return next;
    });
  };
  const removeField = (idx) => {
    setFeishuFields(prev => {
      const next = prev.filter((_, i) => i !== idx);
      localStorage.setItem(LS.feishuFields, JSON.stringify(next));
      return next;
    });
  };
  const addField = () => {
    setFeishuFields(prev => {
      const next = [...prev, { name: "", type: "文本", label: "", description: "" }];
      localStorage.setItem(LS.feishuFields, JSON.stringify(next));
      return next;
    });
  };

  const canHermes = hermesUrl && hermesKey;

  const checkWiki = async () => {
    save();
    setWikiCheckState("loading");
    setWikiCheckResult(null);
    try {
      const res = await window.API.checkHermesWiki();
      setWikiCheckState("ok");
      setWikiCheckResult(res);
    } catch (err) {
      setWikiCheckState("error");
      setWikiCheckResult({ initialized: false, detail: err.message });
    }
  };

  const initWiki = async () => {
    setWikiState("loading"); setWikiMsg("");
    try {
      save();
      const AGENTS_MD = `# 短视频爆款知识库 — Wiki 维护规范\n你是这个知识库唯一的维护者。收到 [INGEST] 消息时按 7 步流程处理，收到 [QUERY] 时综合 wiki 回答，末尾附 referenced_ids。`;
      const resp = await window.API.initHermesWiki(AGENTS_MD);
      setWikiState("ok");
      setWikiMsg(resp.message || "初始化成功");
      // 初始化完成后自动刷新检查状态
      setWikiCheckResult({ initialized: true, detail: "刚刚完成初始化" });
      setWikiCheckState("ok");
    } catch (err) {
      setWikiState("error");
      setWikiMsg(err.message);
    }
  };

  const runTest = async () => {
    setTestState({ status: "loading", results: null });
    setRpaStatus("loading");
    const results = {};
    try {
      const health = await window.API.health();
      results.redis   = health.redis_connected ? "ok" : "fail";
      results.workers = health.worker_count > 0 ? "ok" : "warn";
    } catch (e) {
      results.redis = "fail";
    }
    setTestState({ status: "done", results });
    // RPA 独立检测
    try {
      const r = await window.API.pingRpa();
      setRpaStatus(r.reachable ? "ok" : "fail");
    } catch (e) {
      setRpaStatus("fail");
    }
  };

  const runFeishuTest = async () => {
    save(); // 先把屏幕上的值写入 localStorage，确保测试的是当前填写内容
    setFeishuTest({ status: "loading", results: null, error: "" });
    try {
      const results = await window.API.testFeishu();
      setFeishuTest({ status: "done", results, error: "" });
    } catch (e) {
      setFeishuTest({ status: "error", results: null, error: e.message });
    }
  };

  return (
    <>
      {/* 隐藏的文件输入，用于导入配置 */}
      <input type="file" accept=".json" ref={importRef} style={{ display: "none" }} onChange={importConfig} />

      <div className="page-header">
        <div>
          <h1 className="page-title">设置</h1>
          <div className="page-subtitle">
            所有配置仅保存在浏览器 localStorage，每次请求通过 Header 传递给后端，服务器不存储。
            <br/>
            <span style={{ color: "var(--warning, #f59e0b)", fontSize: 12 }}>
              ⚠ 使用无痕模式或浏览器设置了「关闭时清除数据」会导致配置丢失，请用「导出配置」提前备份。
            </span>
          </div>
        </div>
        <div className="btn-row" style={{ flexWrap: "wrap", gap: 6 }}>
          {importMsg && (
            <span style={{
              fontSize: 12, fontFamily: "var(--font-mono)",
              color: importMsg.startsWith("✓") ? "var(--success)" : "var(--danger)",
            }}>
              {importMsg}
            </span>
          )}
          {savedAt && <span style={{ fontSize: 12, color: "var(--success)", fontFamily: "var(--font-mono)" }}>
            <Icon.Check size={12}/> 已保存 {savedAt.toLocaleTimeString()}
          </span>}
          <button className="btn sm" onClick={() => importRef.current && importRef.current.click()} title="从 JSON 文件恢复全部配置">
            📥 导入配置
          </button>
          <button className="btn sm" onClick={exportConfig} title="将当前配置导出为 JSON 文件备份">
            📤 导出配置
          </button>
          <button className="btn" onClick={reset}>重置</button>
          <button className="btn primary" onClick={save}>保存配置</button>
        </div>
      </div>

      <div className="settings-grid">
        <aside className="settings-side">
          <a className={`settings-side-item${activeSection==="s1"?" active":""}`} onClick={()=>scrollTo("s1")} style={{cursor:"pointer"}}>① RPA 分析服务</a>
          <a className={`settings-side-item${activeSection==="s2"?" active":""}`} onClick={()=>scrollTo("s2")} style={{cursor:"pointer"}}>② 飞书配置</a>
          <a className={`settings-side-item${activeSection==="s3"?" active":""}`} onClick={()=>scrollTo("s3")} style={{cursor:"pointer"}}>③ 连接 &amp; 权限测试</a>
          <a className={`settings-side-item${activeSection==="s4"?" active":""}`} onClick={()=>scrollTo("s4")} style={{cursor:"pointer"}}>④ 分析提示词</a>
          <div style={{ marginTop: 18, padding: 12, background: "var(--bg-muted)", border: "1px dashed var(--line)", borderRadius: 6, fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.6 }}>
            <strong style={{ color: "var(--ink-2)", fontSize: 12 }}>说明</strong><br/>
            页面加载时从 <code style={{fontFamily:"var(--font-mono)"}}>localStorage</code> 自动回填，保存后立即生效。
            <br/><br/>
            <strong style={{ color: "var(--warning, #f59e0b)", fontSize: 11.5 }}>配置备份</strong><br/>
            无痕模式 / 浏览器隐私清除会丢失配置。建议首次配置完成后点「<strong>导出配置</strong>」保存 JSON 文件，下次打开时「<strong>导入配置</strong>」一键恢复。
          </div>
        </aside>

        <div>
          {/* ── Section 1：RPA 分析服务 ── */}
          <div id="s1" className="section-card">
            <div className="section-header">
              <h3><span className="num">01</span> RPA 分析服务</h3>
              <p>视频下载后 push 到 Git 仓库，RPA 服务拉取并调用 AI 分析，分析完成后自动清理视频文件。</p>
            </div>
            <div className="section-body">
              <div className="grid-2">

                <div className="field">
                  <label className="field-label">RPA 服务地址 <span className="req">*</span></label>
                  <input className="input mono" value={rpaHost} onChange={e=>setRpaHost(e.target.value)} placeholder="10.0.0.2" />
                  <div className="field-help">RPA 服务的 IP 或域名，不含协议和端口</div>
                </div>

                <div className="field">
                  <label className="field-label">RPA 服务端口 <span className="req">*</span></label>
                  <input className="input mono" value={rpaPort} onChange={e=>setRpaPort(e.target.value)} placeholder="8000" style={{maxWidth:120}} />
                  <div className="field-help">最终请求地址：<code>http://{rpaHost || "10.0.0.2"}:{rpaPort || "8000"}</code></div>
                </div>

                <div className="field" style={{gridColumn:"1/-1"}}>
                  <label className="field-label">视频 Git 仓库地址 <span className="req">*</span></label>
                  <input className="input mono" value={rpaGitUrl} onChange={e=>setRpaGitUrl(e.target.value)} placeholder="git@your-server:repo/videos.git  或  https://git.example.com/videos.git" />
                  <div className="field-help">视频以 <code>video_id.mp4</code> 命名推送；分析完成后自动 git rm 删除</div>
                </div>

                <div className="field">
                  <label className="field-label">Hermes API URL <span className="optional">可选</span></label>
                  <input className="input mono" value={hermesUrl} onChange={e=>setHermesUrl(e.target.value)} placeholder="http://your-server:8642" />
                  <div className="field-help">知识库 AI 综合洞察（搜索页用）</div>
                </div>

                <div className="field">
                  <label className="field-label">Hermes API Key <span className="optional">可选</span></label>
                  <PwdInput value={hermesKey} onChange={setHermesKey} />
                </div>

                <div className="field">
                  <label className="field-label">评论抓取条数 <span className="optional">可选</span></label>
                  <input
                    className="input mono"
                    type="number"
                    min="1"
                    max="9999"
                    value={commentLimit}
                    onChange={e => setCommentLimit(e.target.value)}
                    disabled={commentAll}
                    style={{ maxWidth: 120 }}
                  />
                  <div className="field-help">仅 YouTube 有效；默认 100 条（按热度）</div>
                </div>

                <div className="field">
                  <label className="field-label">抓取全部评论 <span className="optional">可选</span></label>
                  <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                    <input
                      type="checkbox"
                      checked={commentAll}
                      onChange={e => setCommentAll(e.target.checked)}
                    />
                    <span style={{ fontSize: 13 }}>开启后忽略条数限制，抓取所有评论（耗时较长）</span>
                  </label>
                </div>

                <div className="field">
                  <label className="field-label">LLM Wiki 初始化 <span className="optional">可选</span></label>
                  <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                    {/* 检查状态按钮 */}
                    <button className="btn sm" disabled={!canInitWiki || wikiCheckState==="loading"} onClick={checkWiki}>
                      {wikiCheckState === "loading" ? <><Icon.Spinner /> 检查中...</> : "检查状态"}
                    </button>
                    {/* 状态结果 badge */}
                    {wikiCheckState === "ok" && wikiCheckResult && (
                      wikiCheckResult.initialized
                        ? <span className="badge success"><Icon.Check size={10}/> 已初始化</span>
                        : <span className="badge warning">未初始化</span>
                    )}
                    {wikiCheckState === "error" && (
                      <span className="badge danger">检查失败</span>
                    )}
                    {/* 分隔 */}
                    <span style={{ color: "var(--line)", userSelect: "none" }}>|</span>
                    {/* 初始化按钮 */}
                    <button className="btn" disabled={!canInitWiki || wikiState==="loading"} onClick={initWiki}>
                      {wikiState === "loading" && <Icon.Spinner />}
                      {wikiState === "loading" ? "初始化中..." : "初始化 / 重新初始化"}
                    </button>
                  </div>
                  {/* Hermes 回复详情 */}
                  {wikiCheckResult && wikiCheckState === "ok" && (
                    <div className="field-help" style={{
                      marginTop: 6,
                      padding: "6px 8px",
                      background: "var(--surface-2)",
                      borderRadius: 4,
                      fontFamily: "var(--font-mono)",
                      fontSize: 11,
                      color: "var(--ink-2)",
                      whiteSpace: "pre-wrap",
                    }}>
                      {wikiCheckResult.detail}
                    </div>
                  )}
                  {wikiMsg && (
                    <div className="field-help" style={{ color: wikiState==="error" ? "var(--danger)" : "var(--success)" }}>
                      {wikiMsg}
                    </div>
                  )}
                  {!wikiMsg && !wikiCheckResult && (
                    <div className="field-help">
                      {!canInitWiki ? "需先填写 Hermes URL + Key" : "先「检查状态」确认是否需要初始化。"}
                    </div>
                  )}
                </div>

              </div>
            </div>
          </div>

          {/* ── Section 2：飞书配置 ── */}
          <div id="s2" className="section-card">
            <div className="section-header">
              <h3><span className="num">02</span> 飞书配置</h3>
              <p>分析结果写入飞书多维表格，AI 拆解全文存为飞书文档。视频文件不上传飞书。</p>
            </div>
            <div className="section-body">
              <div className="grid-2">
                <div className="field">
                  <label className="field-label">飞书 App ID <span className="req">*</span></label>
                  <input className="input mono" value={feishuAppId} onChange={e=>setFeishuAppId(e.target.value)} placeholder="cli_xxxxxxxxxxxx" />
                </div>
                <div className="field">
                  <label className="field-label">飞书 App Secret <span className="req">*</span></label>
                  <PwdInput value={feishuSecret} onChange={setFeishuSecret} />
                </div>
                <div className="field">
                  <label className="field-label">多维表格 Base Token <span className="req">*</span></label>
                  <input className="input mono" value={baseToken} onChange={e=>setBaseToken(e.target.value)} placeholder="bascn..." />
                </div>
                <div className="field">
                  <label className="field-label">元数据表 ID <span className="req">*</span></label>
                  <input className="input mono" value={metaTableId} onChange={e=>setMetaTableId(e.target.value)} placeholder="tblXXXXXXXXXXXX" />
                </div>
                <div className="field">
                  <label className="field-label">分析结果表 ID <span className="req">*</span></label>
                  <input className="input mono" value={anaTableId} onChange={e=>setAnaTableId(e.target.value)} placeholder="tblXXXXXXXXXXXX" />
                </div>
                <div className="field">
                  <label className="field-label">文档文件夹 Token <span className="optional">可选</span></label>
                  <input className="input mono" value={docFolder} onChange={e=>setDocFolder(e.target.value)} placeholder="fldbcn..." />
                </div>
              </div>

              {/* 快捷跳转飞书表格 */}
              <div style={{
                marginTop: 16, paddingTop: 14,
                borderTop: "1px dashed var(--line)",
                display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap",
              }}>
                <span style={{ fontSize: 12, color: "var(--ink-3)", marginRight: 4 }}>快捷跳转：</span>
                {[
                  { label: "元数据表", tableId: metaTableId },
                  { label: "分析结果表", tableId: anaTableId },
                ].map(({ label, tableId }) => {
                  const href = baseToken && tableId
                    ? `https://www.feishu.cn/base/${baseToken}?table=${tableId}`
                    : null;
                  return (
                    <a
                      key={label}
                      href={href || undefined}
                      target="_blank"
                      rel="noreferrer"
                      className="btn sm"
                      onClick={!href ? e => e.preventDefault() : undefined}
                      style={{
                        textDecoration: "none",
                        opacity: href ? 1 : 0.4,
                        cursor: href ? "pointer" : "not-allowed",
                      }}
                    >
                      {label} ↗
                    </a>
                  );
                })}
                {!baseToken && (
                  <span style={{ fontSize: 11.5, color: "var(--ink-3)" }}>（需先填写 Base Token 和表 ID）</span>
                )}
              </div>

            </div>
          </div>

          {/* ── Section 3：连接测试 ── */}
          <div id="s3" className="section-card">
            <div className="section-header">
              <h3><span className="num">03</span> 连接测试</h3>
              <p>验证后端服务与飞书权限，确保任务可正常运行。建议提交采集任务前先完成检测。</p>
            </div>
            <div className="section-body">

              {/* ── 3a: 服务状态 ── */}
              <div style={{ marginBottom: 20 }}>
                <div style={{ display:"flex", alignItems:"center", gap:10, marginBottom:12 }}>
                  <span style={{ fontSize:13, fontWeight:600, color:"var(--ink-2)" }}>服务状态</span>
                  <button className="btn sm" onClick={runTest} disabled={testState.status==="loading"}>
                    {testState.status==="loading" ? <><Icon.Spinner /> 测试中…</> : "测试连接"}
                  </button>
                </div>
                {[
                  { k: "redis",   name: "Redis",  desc: "任务队列" },
                  { k: "workers", name: "Worker", desc: "后台处理进程" },
                ].map(s => {
                  const r = testState.results && testState.results[s.k];
                  return (
                    <div className="status-row" key={s.k}>
                      <div className="svc">
                        <span className="svc-name">{s.name}</span>
                        <span className="svc-url">{s.desc}</span>
                      </div>
                      <div>
                        {!r           && <span className="badge neutral">未测试</span>}
                        {r === "ok"   && <span className="badge success"><Icon.Check size={10}/> 连通</span>}
                        {r === "warn" && <span className="badge warning">无 Worker</span>}
                        {r === "fail" && <span className="badge danger"><Icon.Close size={10}/> 失败</span>}
                      </div>
                    </div>
                  );
                })}
                {/* RPA 服务状态 */}
                <div className="status-row">
                  <div className="svc">
                    <span className="svc-name">RPA 服务</span>
                    <span className="svc-url">
                      {rpaHost && rpaPort ? `http://${rpaHost}:${rpaPort}` : "未配置地址"}
                    </span>
                  </div>
                  <div>
                    {rpaStatus === "idle"    && <span className="badge neutral">未测试</span>}
                    {rpaStatus === "loading" && <span className="badge neutral"><Icon.Spinner /> 检测中…</span>}
                    {rpaStatus === "ok"      && <span className="badge success"><Icon.Check size={10}/> 连通</span>}
                    {rpaStatus === "fail"    && <span className="badge danger"><Icon.Close size={10}/> 不可达</span>}
                  </div>
                </div>
              </div>

              {/* ── 3b: 飞书权限 ── */}
              <div style={{ borderTop:"1px solid var(--line)", paddingTop:20 }}>
                <div style={{ display:"flex", alignItems:"center", gap:10, marginBottom:6 }}>
                  <span style={{ fontSize:13, fontWeight:600, color:"var(--ink-2)" }}>飞书权限</span>
                  <button className="btn sm" onClick={runFeishuTest} disabled={feishuTest.status==="loading"}>
                    {feishuTest.status==="loading" ? <><Icon.Spinner /> 检测中…</> : "检测权限"}
                  </button>
                </div>
                <div style={{ fontSize:12, color:"var(--ink-3)", marginBottom:12, lineHeight:1.6 }}>
                  会向多维表格写入并立即删除一条测试记录，以核验写入权限。<br/>
                  若写入权限检测失败，任务会在采集视频后才报错。<strong style={{color:"var(--ink-2)"}}>请先确保所有项通过再提交任务。</strong>
                </div>

                {feishuTest.status === "error" && (
                  <div style={{ color:"var(--danger)", fontSize:12, marginBottom:10, padding:"8px 12px",
                    background:"var(--danger-bg, rgba(239,68,68,0.06))", borderRadius:6, border:"1px solid var(--danger-muted, rgba(239,68,68,0.2))" }}>
                    {feishuTest.error}
                  </div>
                )}

                {[
                  { k:"auth",       name:"应用认证",       desc:"App ID + App Secret 有效" },
                  { k:"base_read",  name:"多维表格读取",   desc:"bitable:app 读取权限" },
                  { k:"base_write", name:"多维表格写入",   desc:"bitable:app 写入权限" },
                  { k:"doc_folder", name:"文档文件夹",     desc:"drive 写入权限（可选）" },
                ].map(item => {
                  const r = feishuTest.results && feishuTest.results[item.k];
                  const isOk   = r && r.ok === true;
                  const isFail = r && r.ok === false;
                  const isSkip = r && r.ok === null;
                  const label  = r && r.msg ? r.msg : item.desc;
                  return (
                    <div className="status-row" key={item.k}>
                      <div className="svc" style={{ flex: 1, minWidth: 0 }}>
                        <span className="svc-name">{item.name}</span>
                        <span className="svc-url" style={isFail ? {color:"var(--danger)"} : {}}>{label}</span>
                      </div>
                      <div style={{ display:"flex", alignItems:"center", gap:8, flexShrink:0 }}>
                        {!r      && <span className="badge neutral">未检测</span>}
                        {isOk    && <span className="badge success"><Icon.Check size={10}/> 通过</span>}
                        {isSkip  && <span className="badge neutral">未配置</span>}
                        {isFail  && <span className="badge danger"><Icon.Close size={10}/> 失败</span>}
                        {isFail && r.fix_url && (
                          <a href={r.fix_url} target="_blank" rel="noreferrer"
                            className="btn sm"
                            style={{ textDecoration:"none", whiteSpace:"nowrap" }}>
                            去授权 →
                          </a>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>

            </div>
          </div>

          {/* ── Section 4：分析提示词 ── */}
          <div id="s4" className="section-card">
            <div className="section-header">
              <h3><span className="num">04</span> 分析提示词</h3>
              <p>发送给 AI 的分析指令。留空则使用服务端默认提示词；修改后立即生效于新提交的任务。</p>
            </div>
            <div className="section-body">

              {/* 提示词编辑区 */}
              <div className="field">
                <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:6 }}>
                  <label className="field-label" style={{marginBottom:0}}>提示词内容</label>
                  <div style={{ display:"flex", gap:6 }}>
                    <button className="btn sm" disabled={promptLoading} onClick={loadDefaultPrompt}>
                      {promptLoading ? <><Icon.Spinner /> 加载中…</> : "加载默认"}
                    </button>
                    <button className="btn sm" onClick={()=>{setRpaPrompt(""); localStorage.removeItem(LS.rpaPrompt);}}>
                      清空（用默认）
                    </button>
                  </div>
                </div>
                <textarea
                  value={rpaPrompt}
                  onChange={e => setRpaPrompt(e.target.value)}
                  placeholder="留空 = 使用服务端 prompts/video_analysis_v1.md；点「加载默认」可先查看再修改"
                  style={{
                    width: "100%", height: 280, resize: "vertical",
                    fontFamily: "var(--font-mono)", fontSize: 12,
                    padding: "10px 12px", lineHeight: 1.6,
                    background: "var(--bg-muted)", border: "1px solid var(--line)",
                    borderRadius: 6, color: "var(--ink-1)", boxSizing: "border-box",
                  }}
                />
                <div className="field-help" style={{ marginTop: 4 }}>
                  ⚠️ 若修改，必须保留 <code>⚠️ 输出格式</code> 整节（JSON 模板 + 字段约束）不变，否则会导致解析失败。
                </div>
              </div>

              {/* 操作按钮区 */}
              <div style={{ display:"flex", gap:8, flexWrap:"wrap", alignItems:"center", marginTop:4 }}>
                <button
                  className="btn primary"
                  disabled={!canHermes || fieldsState==="loading"}
                  onClick={genFields}
                  title={!canHermes ? "需先填写 Hermes URL + Key" : ""}
                >
                  {fieldsState === "loading" ? <><Icon.Spinner /> 推导中…</> : "提交并推导飞书字段"}
                </button>
                <button
                  className="btn"
                  disabled={!canHermes || promptOptState==="loading"}
                  onClick={optimizePrompt}
                  title={!canHermes ? "需先填写 Hermes URL + Key" : ""}
                >
                  {promptOptState === "loading" ? <><Icon.Spinner /> 优化中…</> : "✨ 优化提示词"}
                </button>
                {!canHermes && (
                  <span style={{ fontSize:12, color:"var(--ink-3)" }}>（优化/推导功能需先填写 Hermes URL + Key）</span>
                )}
              </div>

              {/* 操作反馈 */}
              {promptOptMsg && (
                <div style={{ marginTop:6, fontSize:12, color: promptOptState==="error" ? "var(--danger)" : "var(--success)" }}>
                  {promptOptMsg}
                </div>
              )}
              {fieldsMsg && (
                <div style={{ marginTop:4, fontSize:12, color: fieldsState==="error" ? "var(--danger)" : "var(--success)" }}>
                  {fieldsMsg}
                </div>
              )}

              {/* 飞书字段推导结果 */}
              {feishuFields.length > 0 && (
                <div style={{ marginTop:20 }}>
                  <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:8 }}>
                    <span style={{ fontSize:13, fontWeight:600, color:"var(--ink-2)" }}>
                      推导出的飞书字段
                      <span style={{ fontSize:11, fontWeight:400, color:"var(--ink-3)", marginLeft:6 }}>（仅供参考，可手动编辑）</span>
                    </span>
                    <button className="btn sm" onClick={addField}>+ 新增行</button>
                  </div>
                  <div style={{ border:"1px solid var(--line)", borderRadius:6, overflow:"hidden" }}>
                    {/* 表头 */}
                    <div style={{
                      display:"grid", gridTemplateColumns:"1fr 80px 1fr 1.5fr 32px",
                      gap:0, background:"var(--bg-muted)",
                      padding:"6px 10px", fontSize:11, fontWeight:600, color:"var(--ink-3)",
                      borderBottom:"1px solid var(--line)",
                    }}>
                      <span>字段名</span><span>类型</span><span>中文名称</span><span>说明</span><span/>
                    </div>
                    {/* 数据行 */}
                    {feishuFields.map((f, idx) => (
                      <div key={idx} style={{
                        display:"grid", gridTemplateColumns:"1fr 80px 1fr 1.5fr 32px",
                        gap:0, padding:"5px 10px", alignItems:"center",
                        borderBottom: idx < feishuFields.length-1 ? "1px solid var(--line)" : "none",
                        background: idx % 2 === 0 ? "transparent" : "rgba(0,0,0,0.02)",
                      }}>
                        <input
                          className="input" style={{ fontSize:11, padding:"3px 6px", fontFamily:"var(--font-mono)" }}
                          value={f.name} onChange={e=>updateField(idx,"name",e.target.value)}
                        />
                        <select
                          className="input" style={{ fontSize:11, padding:"3px 4px" }}
                          value={f.type} onChange={e=>updateField(idx,"type",e.target.value)}
                        >
                          {["文本","数字","单选","多选","超链接","勾选"].map(t=>(
                            <option key={t} value={t}>{t}</option>
                          ))}
                        </select>
                        <input
                          className="input" style={{ fontSize:11, padding:"3px 6px" }}
                          value={f.label} onChange={e=>updateField(idx,"label",e.target.value)}
                        />
                        <input
                          className="input" style={{ fontSize:11, padding:"3px 6px", color:"var(--ink-3)" }}
                          value={f.description} onChange={e=>updateField(idx,"description",e.target.value)}
                          placeholder="说明（可选）"
                        />
                        <button
                          onClick={()=>removeField(idx)}
                          style={{ background:"none", border:"none", cursor:"pointer", color:"var(--danger)", fontSize:14, padding:"0 4px" }}
                          title="删除"
                        >×</button>
                      </div>
                    ))}
                  </div>
                  <div className="field-help" style={{marginTop:6}}>
                    编辑后点「保存配置」持久化到浏览器；重新推导会覆盖当前列表。
                  </div>
                </div>
              )}

            </div>
          </div>

        </div>
      </div>
    </>
  );
}
window.SettingsPage = SettingsPage;
