"use client";

import React, { useEffect, useMemo, useRef, useState } from "react";
import { ChevronLeft, History, Network, Plus, Settings, Settings2, Send, Sparkles, User, Wrench, X, Paperclip } from "lucide-react";
import { Button } from "@/components/ui/button";
import { AiBridgePanel } from "@/components/ai-agent/AiBridgePanel";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Textarea } from "@/components/ui/textarea";
import { useAppPreferencesStore } from "@/store/app-preferences";
import { parseSseChunks, readAiPanelPrefs, type AiProvider, writeAiPanelPrefs } from "@/components/ai-agent/panelShared";

type AgentAssetItem = {
  kind: "media" | "local-mindmap" | "test-pdf";
  id: string;
  title: string;
  fileUrl: string;
  mimeType?: string | null;
  assetType?: string | null;
  fileName?: string | null;
};

type AgentMessage = { role: "user" | "assistant"; content: string };
type CodexMode = "chat" | "test" | "dev";

const OLLAMA_QWEN3_30B = "qwen3:30b-a3b-instruct-2507-q4_K_M";

const extractCodexMode = (text: string): CodexMode => {
  const s = String(text ?? "");
  const m = s.match(/^\s*#(chat|test|dev)\b/i);
  if (!m) return "chat";
  const mode = String(m[1] ?? "").toLowerCase();
  if (mode === "dev" || mode === "test" || mode === "chat") return mode;
  return "chat";
};

type MindmapInstanceLike = {
  setData?: (data: unknown) => void;
  command?: { clearHistory?: () => void };
};

const DEFAULT_SESSION_MESSAGES: AgentMessage[] = [
  {
    role: "assistant",
    content:
      "你好，我是思维导图 AI Agent。\n- 直接说需求（例如：补完选中节点、总结 @PDF 并写入导图、从 @PDF 生成章/节结构导图）\n- 使用 @ 选择文件或上传文件\n- 你可以让 AI 自动选择工具，或在“工具”里切换到手动并勾选允许使用的工具",
  },
];

type ToolLog =
  | { type: "tool_call"; id: string; tool: string; args: Record<string, unknown> }
  | { type: "tool_result"; id: string; tool: string; ok: boolean; ms: number; result: unknown }
  | { type: "info"; message: string }
  | { type: "error"; message: string };

type ChatSession = {
  id: string;
  title: string;
  createdAt: number;
  updatedAt: number;
  messages: AgentMessage[];
  toolLogs: ToolLog[];
  attachments: AgentAssetItem[];
  codexSessionId?: string | null;
  codexMode?: CodexMode | null;
};

type PanelPage = "chat" | "tools" | "history" | "account" | "settings";

const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));

const generateId = () => {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return `sess_${Math.random().toString(16).slice(2, 10)}`;
};

const normalizeSessions = (sessions: ChatSession[]) => {
  const maxSessions = 20;
  const maxMessages = 40;
  const maxToolLogs = 80;
  const maxAttachments = 20;
  return sessions
    .slice(0, maxSessions)
    .map((s) => ({
      ...s,
      messages: Array.isArray(s.messages) ? s.messages.slice(-maxMessages) : [],
      toolLogs: Array.isArray(s.toolLogs) ? s.toolLogs.slice(-maxToolLogs) : [],
      attachments: Array.isArray(s.attachments) ? s.attachments.slice(-maxAttachments) : [],
    }))
    .sort((a, b) => b.updatedAt - a.updatedAt);
};

const isRecord = (v: unknown): v is Record<string, unknown> =>
  typeof v === "object" && v !== null && !Array.isArray(v);

const getValue = (obj: unknown, key: string): unknown => (isRecord(obj) ? obj[key] : undefined);

type ToolName =
  | "mindmap_get"
  | "mindmap_get_subtree"
  | "search_web"
  | "rag_lightrag_query"
  | "image_read"
  | "mindmap_apply_ops"
  | "mindmap_add_child"
  | "mindmap_add_sibling_after"
  | "mindmap_update_node_text"
  | "mindmap_set_hyperlink"
  | "mindmap_append_note"
  | "mindmap_set_refs"
  | "mindmap_delete_node"
  | "mindmap_add_attachment_ref"
  | "mindmap_add_attachment_child"
  | "mindmap_add_image_child"
  | "mindmap_append_image_note"
  | "mindmap_expand_node";

const TOOL_LABEL: Record<ToolName, string> = {
  mindmap_get: "读导图（摘要）",
  mindmap_get_subtree: "读子树（按 uid）",
  search_web: "联网检索（SearxNG）",
  rag_lightrag_query: "LightRAG 检索",
  image_read: "图片读取（OCR）",
  mindmap_apply_ops: "写入导图（ops）",
  mindmap_add_child: "新增子节点",
  mindmap_add_sibling_after: "新增同级（在后面）",
  mindmap_update_node_text: "更新节点文本",
  mindmap_set_hyperlink: "设置/清除超链接",
  mindmap_append_note: "追加备注",
  mindmap_set_refs: "设置 refs（引用）",
  mindmap_delete_node: "删除节点",
  mindmap_add_attachment_ref: "增加附件引用（refs）",
  mindmap_add_attachment_child: "附件→子节点",
  mindmap_add_image_child: "图片→子节点",
  mindmap_append_image_note: "备注插入图片",
  mindmap_expand_node: "补完节点（检索→写入）",
};

const DEFAULT_TOOLS: ToolName[] = [
  "mindmap_get",
  "mindmap_get_subtree",
  "search_web",
  "rag_lightrag_query",
  "image_read",
  "mindmap_expand_node",
  "mindmap_add_child",
  "mindmap_update_node_text",
  "mindmap_set_hyperlink",
  "mindmap_append_note",
  "mindmap_set_refs",
  "mindmap_delete_node",
  "mindmap_add_attachment_ref",
  "mindmap_add_attachment_child",
  "mindmap_add_image_child",
  "mindmap_append_image_note",
  "mindmap_apply_ops",
];

const ONLINE_MODELS = [
  "",
  "gemini-2.5-flash",
  "gemini-2.5-pro",
  "gemini-3-pro-preview",
  "gemini-3-flash-preview",
] as const;

export function MindmapAiAgentPanelRuntime({
  documentId,
  mindmapId,
  mindmap,
  activeNodes,
  onClose,
}: {
  documentId: string;
  mindmapId: string;
  mindmap: MindmapInstanceLike | null | undefined;
  activeNodes: unknown[];
  onClose?: () => void;
}) {
  const flightMode = useAppPreferencesStore((s) => s.flightMode);
  const [messages, setMessages] = useState<AgentMessage[]>(() => DEFAULT_SESSION_MESSAGES);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);

  const [networkOn, setNetworkOn] = useState(() => !flightMode);
  const [toolAuto, setToolAuto] = useState(true);
  // 兼容旧 UI（已隐藏），避免影响已有交互与回滚风险
  const [selectedTools, setSelectedTools] = useState<ToolName[]>(DEFAULT_TOOLS);
  const [aiProvider, setAiProvider] = useState<AiProvider>("online");
  const [aiModel, setAiModel] = useState<string>("");
  const [maxSteps, setMaxSteps] = useState<number>(10);
  const [page, setPage] = useState<PanelPage>("chat");

  const [assets, setAssets] = useState<AgentAssetItem[]>([]);
  const [workspaceId, setWorkspaceId] = useState<string>("");
  const [attachments, setAttachments] = useState<AgentAssetItem[]>([]);
  const [debug, setDebug] = useState<string>("");
  const [toolLogs, setToolLogs] = useState<ToolLog[]>([]);
  const [sessions, setSessions] = useState<ChatSession[]>([]);
  const [activeSessionId, setActiveSessionId] = useState<string>("");

  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const abortRef = useRef<AbortController | null>(null);
  const syncTimerRef = useRef<number | null>(null);

  useEffect(() => {
    if (flightMode) {
      setNetworkOn(false);
    }
  }, [flightMode]);

  useEffect(() => {
    const prefs = readAiPanelPrefs("mindmap_ai", { provider: "online", model: "", maxSteps: 10 });
    setAiProvider(prefs.provider);
    setAiModel(prefs.model);
    setMaxSteps(Math.max(1, Math.min(24, Math.floor(prefs.maxSteps))));
  }, []);

  useEffect(() => {
    writeAiPanelPrefs("mindmap_ai", {
      provider: aiProvider,
      model: aiModel,
      maxSteps: Math.max(1, Math.min(24, Math.floor(maxSteps))),
    });
  }, [aiProvider, aiModel, maxSteps]);

  useEffect(() => {
    // 切换导图时，默认回到对话页
    setPage("chat");
  }, [mindmapId]);

  // 会话/历史：按 mindmapId 隔离持久化
  useEffect(() => {
    try {
      const key = `mindmap_ai_sessions:${mindmapId}`;
      const raw = window.localStorage.getItem(key);
      if (!raw) {
        const id = generateId();
        const now = Date.now();
        const session: ChatSession = {
          id,
          title: "新会话",
          createdAt: now,
          updatedAt: now,
          messages: DEFAULT_SESSION_MESSAGES,
          toolLogs: [],
          attachments: [],
          codexSessionId: null,
          codexMode: null,
        };
        setSessions([session]);
        setActiveSessionId(id);
        setMessages(session.messages);
        setToolLogs([]);
        setAttachments([]);
        return;
      }

      const parsed = JSON.parse(raw) as unknown;
      const list = typeof parsed === "object" && parsed && "sessions" in parsed ? (parsed as any).sessions : null;
      const active =
        typeof parsed === "object" && parsed && "activeSessionId" in parsed
          ? String((parsed as any).activeSessionId ?? "")
          : "";
      if (!Array.isArray(list) || list.length === 0) return;

      const loaded = normalizeSessions(
        list
          .map((x) => {
            const id = String((x as any)?.id ?? "").trim() || generateId();
            const createdAt = Number((x as any)?.createdAt ?? Date.now());
            const updatedAt = Number((x as any)?.updatedAt ?? createdAt);
            const title = String((x as any)?.title ?? "").trim() || "历史会话";
            const messages = Array.isArray((x as any)?.messages) ? (x as any).messages : DEFAULT_SESSION_MESSAGES;
            const toolLogs = Array.isArray((x as any)?.toolLogs) ? (x as any).toolLogs : [];
            const attachments = Array.isArray((x as any)?.attachments) ? (x as any).attachments : [];
            const codexSessionId = String((x as any)?.codexSessionId ?? "").trim() || null;
            const codexModeRaw = String((x as any)?.codexMode ?? "").trim();
            const codexMode = codexModeRaw === "chat" || codexModeRaw === "test" || codexModeRaw === "dev" ? (codexModeRaw as CodexMode) : null;
            return { id, title, createdAt, updatedAt, messages, toolLogs, attachments, codexSessionId, codexMode } as ChatSession;
          })
          .filter((s) => s.id),
      );
      setSessions(loaded);
      const picked = active && loaded.some((s) => s.id === active) ? active : loaded[0]!.id;
      setActiveSessionId(picked);
      const cur = loaded.find((s) => s.id === picked) ?? loaded[0]!;
      setMessages(cur.messages?.length ? cur.messages : DEFAULT_SESSION_MESSAGES);
      setToolLogs(cur.toolLogs ?? []);
      setAttachments(cur.attachments ?? []);
    } catch {
      // ignore
    }
     
  }, [mindmapId]);

  useEffect(() => {
    if (!activeSessionId) return;
    if (syncTimerRef.current) window.clearTimeout(syncTimerRef.current);
    syncTimerRef.current = window.setTimeout(() => {
      setSessions((prev) => {
        const now = Date.now();
        const next = prev.some((s) => s.id === activeSessionId)
          ? prev.map((s) =>
              s.id === activeSessionId ? { ...s, messages, toolLogs, attachments, updatedAt: now } : s,
            )
          : [
              {
                id: activeSessionId,
                title: "新会话",
                createdAt: now,
                updatedAt: now,
                messages,
                toolLogs,
                attachments,
                codexSessionId: null,
                codexMode: null,
              },
              ...prev,
            ];
        return normalizeSessions(next);
      });
    }, 200);
    return () => {
      if (syncTimerRef.current) window.clearTimeout(syncTimerRef.current);
    };
  }, [activeSessionId, attachments, messages, toolLogs]);

  useEffect(() => {
    if (!mindmapId) return;
    try {
      const key = `mindmap_ai_sessions:${mindmapId}`;
      const payload = JSON.stringify({ activeSessionId, sessions: normalizeSessions(sessions) });
      if (payload.length <= 900_000) window.localStorage.setItem(key, payload);
    } catch {
      // ignore
    }
  }, [activeSessionId, mindmapId, sessions]);

  // @ 选择
  const [mentionOpen, setMentionOpen] = useState(false);
  const [mentionQuery, setMentionQuery] = useState("");
  const [mentionRange, setMentionRange] = useState<{ start: number; end: number } | null>(null);

  const persistMindmapData = (data: unknown): boolean => {
    try {
      const w = window as unknown as {
        __mindmapPersistById?: Record<string, (data: unknown) => void>;
      };
      const fn = w.__mindmapPersistById?.[mindmapId];
      if (typeof fn === "function") {
        fn(data);
        return true;
      }
    } catch {
      // ignore
    }
    return false;
  };

  const selectedNodes = useMemo(() => {
    const list = Array.isArray(activeNodes) ? activeNodes : [];

    const readUid = (n: unknown) => {
      const nodeData = getValue(n, "nodeData");
      const nodeDataData = getValue(nodeData, "data");
      const uidFromNodeData = String(getValue(nodeDataData, "uid") ?? getValue(nodeData, "uid") ?? "").trim();
      if (uidFromNodeData) return uidFromNodeData;

      // 兼容 xmind-editor 节点对象：getData("uid")
      if (isRecord(n)) {
        const getData = n["getData"];
        if (typeof getData === "function") {
          try {
            const uid = (getData as (key: string) => unknown)("uid");
            const s = String(uid ?? "").trim();
            if (s) return s;
          } catch {
            // ignore
          }
        }
      }

      return String(getValue(n, "uid") ?? "").trim();
    };

    const toPlainText = (input: unknown) => {
      const s = String(input ?? "").trim();
      if (!s) return "";
      // 选中节点的 text 可能是富文本 HTML（例如：<p>分支主题</p>），这里转换为纯文本展示给用户
      if (!/[<>]/.test(s)) return s;
      try {
        const div = document.createElement("div");
        div.innerHTML = s;
        return String(div.textContent || div.innerText || "").trim();
      } catch {
        return s.replace(/<[^>]*>/g, "").trim();
      }
    };

    const readText = (n: unknown) => {
      try {
        // 优先走 getData("text")，其次兜底 nodeData/data 结构
        if (isRecord(n)) {
          const getData = n["getData"];
          if (typeof getData === "function") {
            try {
              const raw = (getData as (key: string) => unknown)("text");
              const text = toPlainText(raw);
              if (text) return text;
            } catch {
              // ignore
            }
          }
        }

        const nodeData = getValue(n, "nodeData");
        const nodeDataData = getValue(nodeData, "data");
        const raw =
          getValue(nodeDataData, "text") ??
          getValue(nodeData, "text") ??
          getValue(getValue(n, "data"), "text") ??
          "";
        return toPlainText(raw);
      } catch {
        return "";
      }
    };

    return list
      .slice(0, 3)
      .map((n) => ({ uid: readUid(n), text: readText(n) }))
      .filter((x) => Boolean(x.uid));
  }, [activeNodes]);

  const selectedUids = useMemo(() => selectedNodes.map((n) => n.uid), [selectedNodes]);

  useEffect(() => {
    let cancelled = false;
    if (!documentId) return;
    void (async () => {
      try {
        const res = await fetch(`/api/mindmap-ai/assets?documentId=${encodeURIComponent(documentId)}`);
        const json: unknown = await res.json().catch(() => null);
        if (!res.ok) return;
        if (cancelled) return;
        const obj = isRecord(json) ? json : {};
        setWorkspaceId(String(obj.workspaceId ?? ""));
        setAssets(Array.isArray(obj.items) ? (obj.items as AgentAssetItem[]) : []);
      } catch {
        // ignore
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [documentId]);

  const filteredAssets = useMemo(() => {
    const q = mentionQuery.trim().toLowerCase();
    const base = assets.slice(0, 200);
    if (!q) return base.slice(0, 12);
    return base
      .filter((a) => {
        const hay = `${a.title} ${a.fileName ?? ""}`.toLowerCase();
        return hay.includes(q);
      })
      .slice(0, 12);
  }, [assets, mentionQuery]);

  const updateMentionState = (value: string, cursor: number) => {
    const before = value.slice(0, Math.max(0, cursor));
    const at = before.lastIndexOf("@");
    if (at === -1) {
      setMentionOpen(false);
      setMentionQuery("");
      setMentionRange(null);
      return;
    }
    // 若 @ 前是非空白字符，视为 email/路径等，避免误触发
    if (at > 0 && /\S/.test(before[at - 1] || "")) {
      setMentionOpen(false);
      setMentionQuery("");
      setMentionRange(null);
      return;
    }
    const token = before.slice(at + 1);
    // 遇到换行/空格则不触发
    if (/\s/.test(token)) {
      setMentionOpen(false);
      setMentionQuery("");
      setMentionRange(null);
      return;
    }
    setMentionOpen(true);
    setMentionQuery(token);
    setMentionRange({ start: at, end: cursor });
  };

  const insertMention = (item: AgentAssetItem) => {
    const el = textareaRef.current;
    if (!el || !mentionRange) return;
    const next = `${input.slice(0, mentionRange.start)}@${item.title}${input.slice(mentionRange.end)}`;
    setInput(next);
    setMentionOpen(false);
    setMentionQuery("");
    setMentionRange(null);
    // 去重加入附件
    setAttachments((prev) => (prev.some((x) => x.id === item.id) ? prev : [...prev, item]));
    // 光标移动到插入后
    window.setTimeout(() => {
      try {
        const pos = mentionRange.start + 1 + item.title.length;
        el.focus();
        el.setSelectionRange(pos, pos);
      } catch {
        // ignore
      }
    }, 0);
  };

  const uploadFiles = async (files: FileList | null) => {
    if (!files || files.length === 0) return;
    if (!workspaceId || !documentId) {
      window.alert("缺少 workspaceId/documentId，无法上传。请刷新后重试。");
      return;
    }
    const file = files[0];
    const form = new FormData();
    form.append("file", file);
    form.append("workspaceId", workspaceId);
    form.append("documentId", documentId);

    setLoading(true);
    try {
      const res = await fetch("/api/media/upload", { method: "POST", body: form });
      const json: unknown = await res.json().catch(() => null);
      const obj = isRecord(json) ? json : {};
      if (!res.ok) throw new Error(String(obj.error ?? `上传失败：${res.status}`));
      const asset = isRecord(obj.asset) ? obj.asset : {};
      const item: AgentAssetItem = {
        kind: "media",
        id: String(asset.id ?? `media:${Date.now()}`),
        title: String(asset.file_name ?? file.name),
        fileUrl: String(asset.file_url ?? ""),
        mimeType: String(asset.mime_type ?? file.type ?? ""),
        assetType: String(asset.asset_type ?? "file"),
        fileName: String(asset.file_name ?? file.name),
      };
      setAttachments((prev) => (prev.some((x) => x.id === item.id) ? prev : [...prev, item]));
      // 刷新资产列表
      try {
        const listRes = await fetch(`/api/mindmap-ai/assets?documentId=${encodeURIComponent(documentId)}`);
        const listJson: unknown = await listRes.json().catch(() => null);
        const listObj = isRecord(listJson) ? listJson : {};
        if (listRes.ok && Array.isArray(listObj.items)) {
          setAssets(listObj.items as AgentAssetItem[]);
          setWorkspaceId(String(listObj.workspaceId ?? workspaceId));
        }
      } catch {
        // ignore
      }
    } catch (e) {
      window.alert(e instanceof Error ? e.message : String(e));
    } finally {
      setLoading(false);
      if (fileInputRef.current) fileInputRef.current.value = "";
    }
  };

  const currentSession = useMemo(() => sessions.find((s) => s.id === activeSessionId) ?? null, [activeSessionId, sessions]);
  const currentSessionTitle = currentSession?.title || "新会话";

  const pageTitle = useMemo(() => {
    switch (page) {
      case "tools":
        return "工具（代替 MCP）";
      case "history":
        return "历史会话";
      case "account":
        return "账户 / 模型";
      case "settings":
        return "设置";
      default:
        return "思维导图 AI Agent";
    }
  }, [page]);

  const startNewSession = () => {
    if (loading) return;
    const id = generateId();
    const now = Date.now();
    const next: ChatSession = {
      id,
      title: "新会话",
      createdAt: now,
      updatedAt: now,
      messages: DEFAULT_SESSION_MESSAGES,
      toolLogs: [],
      attachments: [],
      codexSessionId: null,
      codexMode: null,
    };
    setSessions((prev) => normalizeSessions([next, ...prev]));
    setActiveSessionId(id);
    setMessages(next.messages);
    setToolLogs([]);
    setAttachments([]);
    setInput("");
    setDebug("");
  };

  const switchSession = (id: string) => {
    if (loading) return;
    const target = sessions.find((s) => s.id === id);
    if (!target) return;
    setActiveSessionId(target.id);
    setMessages(target.messages?.length ? target.messages : DEFAULT_SESSION_MESSAGES);
    setToolLogs(target.toolLogs ?? []);
    setAttachments(target.attachments ?? []);
    setInput("");
    setDebug("");
  };

  const resetCurrentSession = () => {
    if (loading) return;
    setMessages(DEFAULT_SESSION_MESSAGES);
    setToolLogs([]);
    setAttachments([]);
    setInput("");
    setDebug("");
    if (activeSessionId) {
      setSessions((prev) =>
        normalizeSessions(
          prev.map((s) =>
            s.id === activeSessionId
              ? {
                  ...s,
                  messages: DEFAULT_SESSION_MESSAGES,
                  toolLogs: [],
                  attachments: [],
                  updatedAt: Date.now(),
                  title: s.title || "当前会话",
                  codexSessionId: null,
                  codexMode: null,
                }
              : s,
          ),
        ),
      );
    }
  };

  const clearHistory = () => {
    if (loading) return;
    const base: ChatSession =
      currentSession ??
      ({
        id: activeSessionId || generateId(),
        title: "当前会话",
        createdAt: Date.now(),
        updatedAt: Date.now(),
        messages,
        toolLogs,
        attachments,
      } as ChatSession);
    setSessions([
      {
        ...base,
        title: base.title || "当前会话",
        updatedAt: Date.now(),
        messages,
        toolLogs,
        attachments,
      },
    ]);
    setActiveSessionId(base.id);
  };

  const deleteSession = (id: string) => {
    if (loading) return;
    setSessions((prev) => normalizeSessions(prev.filter((s) => s.id !== id)));
    if (id === activeSessionId) {
      const fallback = sessions.filter((s) => s.id !== id)[0];
      if (fallback) {
        setActiveSessionId(fallback.id);
        setMessages(fallback.messages?.length ? fallback.messages : DEFAULT_SESSION_MESSAGES);
        setToolLogs(fallback.toolLogs ?? []);
        setAttachments(fallback.attachments ?? []);
      } else {
        startNewSession();
      }
    }
  };

  const canSend = useMemo(() => input.trim().length > 0 && !loading, [input, loading]);

  const stop = () => {
    try {
      abortRef.current?.abort();
    } catch {
      // ignore
    } finally {
      abortRef.current = null;
      setLoading(false);
      if (aiProvider === "codex") {
        setToolLogs((prev) => [...prev, { type: "info", message: "已暂停（可继续对话）" }]);
      }
    }
  };

  const send = async () => {
    const content = input.trim();
    if (!content) return;
    const hasExplicitCodexMode = /^\s*#(chat|test|dev)\b/i.test(content);
    const intendedCodexMode: CodexMode =
      aiProvider === "codex" ? (hasExplicitCodexMode ? extractCodexMode(content) : "chat") : "chat";
    setDebug("");
    setToolLogs([]);
    if (activeSessionId && currentSessionTitle === "新会话") {
      const title = content.length > 18 ? `${content.slice(0, 18)}…` : content;
      setSessions((prev) => normalizeSessions(prev.map((s) => (s.id === activeSessionId ? { ...s, title, updatedAt: Date.now() } : s))));
    }

    const nextMessages: AgentMessage[] = [...messages, { role: "user", content }];
    setMessages(nextMessages);
    setInput("");
    setLoading(true);

    let controller: AbortController | null = null;
    try {
      abortRef.current?.abort();
      controller = new AbortController();
      abortRef.current = controller;

      // 不把面板的“欢迎语”当作对话历史发送给服务端，避免影响任务执行
      const payloadMessages = nextMessages.filter(
        (m, idx) => !(idx === 0 && m.role === "assistant" && /思维导图 AI Agent/.test(m.content)),
      );

      const payloadMessagesForRequest = payloadMessages;

      const codexSessionIdForRequest =
        aiProvider === "codex" ? String(currentSession?.codexSessionId ?? "").trim() || null : null;

      if (aiProvider === "codex" && activeSessionId) {
        setSessions((prev) =>
          normalizeSessions(
            prev.map((s) =>
              s.id === activeSessionId ? { ...s, codexMode: intendedCodexMode, updatedAt: Date.now() } : s,
            ),
          ),
        );
      }

      const res = await fetch("/api/ai-agent/run", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        signal: controller.signal,
        body: JSON.stringify({
          stream: true,
          maxSteps,
          scope: "mindmap",
          messages: payloadMessagesForRequest.slice(-24),
          toolChoice: toolAuto
            ? {
                mode: "auto",
                toolSets: [
                  "toolset.readonly",
                  "toolset.rag_read",
                  "toolset.media_read",
                  "toolset.mindmap_read",
                  "toolset.mindmap_write",
                ],
              }
            : { mode: "manual", tools: selectedTools },
          context: { documentId, mindmapId, selectedUids },
          attachments: attachments.map((a) => ({
            id: a.id,
            title: a.title,
            fileUrl: a.fileUrl,
            mimeType: a.mimeType ?? null,
          })),
          options: {
            searxng: networkOn,
            ai: {
              provider: aiProvider,
              ...(aiProvider === "codex" && codexSessionIdForRequest ? { sessionId: codexSessionIdForRequest } : {}),
              ...(aiProvider !== "codex" && aiModel.trim() ? { model: aiModel.trim() } : {}),
            },
          },
        }),
      });
      if (!res.ok) {
        const j = (await res.json().catch(() => null)) as unknown;
        const err = typeof j === "object" && j && "error" in j ? String((j as Record<string, unknown>).error ?? "") : "";
        throw new Error(err || `请求失败：${res.status}`);
      }

      const rawEvents: Array<{ event: string; dataText: string }> = [];
      await parseSseChunks(res, (event, dataText) => {
        rawEvents.push({ event, dataText });

        if (event === "codex_session") {
          try {
            const data = JSON.parse(dataText || "null") as unknown;
            const obj = (typeof data === "object" && data ? (data as Record<string, unknown>) : {}) as Record<string, unknown>;
            const sessionId = String(obj.sessionId ?? "").trim();
            if (sessionId && activeSessionId) {
              setSessions((prev) =>
                normalizeSessions(
                  prev.map((s) =>
                    s.id === activeSessionId ? { ...s, codexSessionId: sessionId, codexMode: intendedCodexMode, updatedAt: Date.now() } : s,
                  ),
                ),
              );
            }
          } catch {
            // ignore
          }
          return;
        }

        if (event === "tool_call") {
          try {
            const data = JSON.parse(dataText || "null") as unknown;
            const obj = (typeof data === "object" && data ? (data as Record<string, unknown>) : {}) as Record<string, unknown>;
            setToolLogs((prev) => [
              ...prev,
              {
                type: "tool_call",
                id: String(obj.id ?? ""),
                tool: String(obj.tool ?? ""),
                args: (typeof obj.args === "object" && obj.args ? (obj.args as Record<string, unknown>) : {}) as Record<string, unknown>,
              },
            ]);
          } catch {
            // ignore
          }
          return;
        }

        if (event === "tool_result") {
          try {
            const data = JSON.parse(dataText || "null") as unknown;
            const obj = (typeof data === "object" && data ? (data as Record<string, unknown>) : {}) as Record<string, unknown>;
            const tool = String(obj.tool ?? "");
            const result = "result" in obj ? obj.result : null;
            setToolLogs((prev) => [
              ...prev,
              { type: "tool_result", id: String(obj.id ?? ""), tool, ok: Boolean(obj.ok), ms: Number(obj.ms ?? 0), result },
            ]);

            // 若工具返回了新的 mindmap data，则立即应用到前端实例并落盘（与 MindmapSidebar 的策略一致）
            if (
              (tool === "mindmap_apply_ops" ||
                tool === "mindmap_expand_node" ||
                tool === "mindmap_add_child" ||
                tool === "mindmap_add_sibling_after" ||
                tool === "mindmap_update_node_text" ||
                tool === "mindmap_set_hyperlink" ||
                tool === "mindmap_append_note" ||
                tool === "mindmap_set_refs" ||
                tool === "mindmap_delete_node" ||
                tool === "mindmap_add_attachment_ref" ||
                tool === "mindmap_add_attachment_child" ||
                tool === "mindmap_add_image_child" ||
                tool === "mindmap_append_image_note") &&
              obj.ok
            ) {
              const r = result as unknown;
              const dataNode =
                typeof r === "object" && r && "data" in (r as Record<string, unknown>) ? (r as Record<string, unknown>).data : null;
              if (dataNode) {
                try {
                  mindmap?.setData?.(dataNode);
                  mindmap?.command?.clearHistory?.();
                  persistMindmapData(dataNode);
                } catch {
                  // ignore
                }
              }
            }
          } catch {
            // ignore
          }
          return;
        }

        if (event === "assistant_message") {
          try {
            const data = JSON.parse(dataText || "null") as unknown;
            const assistantText =
              typeof data === "object" && data && "text" in data ? String((data as Record<string, unknown>).text ?? "") : "";
            setMessages((prev) => [
              ...prev,
              { role: "assistant", content: assistantText.trim() ? assistantText.trim() : "（无输出）" },
            ]);
          } catch {
            setMessages((prev) => [...prev, { role: "assistant", content: "（无输出）" }]);
          }
          return;
        }

        if (event === "error") {
          if (controller?.signal.aborted && aiProvider === "codex") return;
          try {
            const data = JSON.parse(dataText || "null") as unknown;
            const msg =
              typeof data === "object" && data && "message" in data ? String((data as Record<string, unknown>).message ?? "") : "";
            setToolLogs((prev) => [...prev, { type: "error", message: msg || "未知错误" }]);
          } catch {
            setToolLogs((prev) => [...prev, { type: "error", message: "未知错误" }]);
          }
          return;
        }
      });

      setDebug(JSON.stringify(rawEvents.slice(-120), null, 2));
    } catch (e) {
      if (controller?.signal.aborted && aiProvider === "codex") return;
      const msg = e instanceof Error ? e.message : String(e);
      setMessages((prev) => [...prev, { role: "assistant", content: `执行失败：${msg}` }]);
      setToolLogs((prev) => [...prev, { type: "error", message: msg }]);
    } finally {
      abortRef.current = null;
      setLoading(false);
    }
  };

  return (
    <AiBridgePanel
      title={pageTitle}
      subtitle="思维导图 AI"
      status={loading ? "运行中" : "待命"}
      onClose={onClose}
      scrollBody={false}
      className="h-full min-h-0 rounded-[24px] border-white/10 shadow-none"
      secondaryActions={
        <>
          {page === "chat" ? (
            <Sparkles className="h-4 w-4 text-white/75" />
          ) : (
            <Button
              variant="ghost"
              size="icon-sm"
              onClick={() => setPage("chat")}
              disabled={loading}
              title="返回对话"
              className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
            >
              <ChevronLeft className="h-4 w-4" />
            </Button>
          )}
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={startNewSession}
            disabled={loading}
            title="新建会话"
            className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
          >
            <Plus className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={() => setPage("tools")}
            disabled={loading}
            title="工具（代替 MCP）"
            className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
          >
            <Wrench className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={() => setPage("history")}
            disabled={loading}
            title="历史"
            className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
          >
            <History className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={() => setPage("account")}
            disabled={loading}
            title="账户/模型"
            className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
          >
            <User className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={() => setPage("settings")}
            disabled={loading}
            title="设置"
            className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
          >
            <Settings className="h-4 w-4" />
          </Button>
        </>
      }
    >
      <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
        {page === "chat" ? (
          <>
            <div
              className="border-b p-3 text-xs text-muted-foreground"
              title={
                selectedNodes.length
                  ? selectedNodes
                      .map((n) => (n.text ? `${n.text}（${n.uid}）` : n.uid))
                      .slice(0, 3)
                      .join("，")
                  : ""
              }
            >
              {selectedNodes.length
                ? `选中节点：${selectedNodes[0]?.text || selectedNodes[0]?.uid}${selectedNodes.length > 1 ? `（+${selectedNodes.length - 1}）` : ""}`
                : "未选中节点（将以整图为上下文）"}
            </div>

            <div className="flex min-h-0 flex-1 flex-col">
              <div className="min-h-0 border-b">
                <div className="px-3 py-2 text-sm font-medium">对话</div>
                <ScrollArea className="h-[36vh] border-t">
                  <div className="space-y-3 p-3 text-sm">
                    {messages.map((m, idx) => (
                      <div key={idx} className="space-y-1">
                        <div className="text-xs text-muted-foreground">{m.role === "user" ? "用户" : "AI"}</div>
                        <div className="whitespace-pre-wrap">{m.content}</div>
                      </div>
                    ))}
                  </div>
                </ScrollArea>
              </div>

              <div className="min-h-0">
                <div className="px-3 py-2 text-sm font-medium">工具日志</div>
                <ScrollArea className="h-[24vh] border-t">
                  <div className="space-y-3 p-3 text-sm">
                    {toolLogs.length === 0 ? <div className="text-muted-foreground">暂无工具日志</div> : null}
                    {toolLogs.map((l, idx) => {
                      if (l.type === "error") {
                        return (
                          <div key={idx} className="rounded border border-red-200 bg-red-50 p-2 text-red-700">
                            错误：{l.message}
                          </div>
                        );
                      }
                      if (l.type === "info") {
                        return (
                          <div key={idx} className="rounded border bg-muted p-2 text-muted-foreground">
                            {l.message}
                          </div>
                        );
                      }
                      if (l.type === "tool_call") {
                        return (
                          <details key={idx} className="rounded border p-2">
                            <summary className="cursor-pointer select-none text-xs text-muted-foreground">
                              tool_call · {l.tool} · {l.id}
                            </summary>
                            <pre className="mt-2 whitespace-pre-wrap text-xs">{JSON.stringify(l.args, null, 2)}</pre>
                          </details>
                        );
                      }
                      return (
                        <details key={idx} className="rounded border p-2">
                          <summary className="cursor-pointer select-none text-xs text-muted-foreground">
                            tool_result · {l.tool} · {l.id} · {l.ok ? "ok" : "fail"} · {l.ms}ms
                          </summary>
                          <pre className="mt-2 whitespace-pre-wrap text-xs">{JSON.stringify(l.result, null, 2)}</pre>
                        </details>
                      );
                    })}
                  </div>
                </ScrollArea>
              </div>
            </div>

            <div className="border-t p-3">
              {attachments.length > 0 ? (
                <div className="mb-2 flex flex-wrap gap-2">
                  {attachments.map((a) => (
                    <span
                      key={a.id}
                      className="inline-flex items-center gap-1 rounded border bg-muted px-2 py-1 text-xs text-foreground"
                      title={a.fileUrl}
                    >
                      <span className="max-w-[160px] truncate">{a.title}</span>
                      <button
                        type="button"
                        className="text-muted-foreground hover:text-foreground"
                        onClick={() => setAttachments((prev) => prev.filter((x) => x.id !== a.id))}
                        disabled={loading}
                        title="移除附件"
                      >
                        <X className="h-3 w-3" />
                      </button>
                    </span>
                  ))}
                </div>
              ) : null}

              <div className="relative">
                {mentionOpen && filteredAssets.length > 0 ? (
                  <div className="absolute bottom-[calc(100%+8px)] left-0 right-0 z-30 max-h-56 overflow-auto rounded-md border bg-background shadow">
                    {filteredAssets.map((a) => (
                      <button
                        key={a.id}
                        type="button"
                        className="flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm hover:bg-muted"
                        onClick={() => insertMention(a)}
                        disabled={loading}
                      >
                        <span className="truncate">{a.title}</span>
                        <span className="shrink-0 text-xs text-muted-foreground">{a.kind}</span>
                      </button>
                    ))}
                  </div>
                ) : null}

                <Textarea
                  ref={textareaRef}
                  data-testid="mindmap-ai-input"
                  className="min-h-[96px]"
                  value={input}
                  placeholder="输入你的需求。使用 @ 选择文件（PDF/附件/本地导图），例如：总结 @xx.pdf 并写入导图（章->节->要点）。"
                  disabled={loading}
                  onChange={(e) => {
                    const v = e.target.value;
                    setInput(v);
                    updateMentionState(v, e.target.selectionStart ?? v.length);
                  }}
                  onKeyDown={(e) => {
                    // 焦点在 AI 输入框时，不应触发导图 Enter/Tab 快捷键
                    e.stopPropagation();
                    if (e.key === "Escape") {
                      setMentionOpen(false);
                      return;
                    }
                    if (e.key === "Enter") {
                      // Enter 发送；Shift+Enter 换行
                      const isComposing = Boolean((e.nativeEvent as unknown as { isComposing?: boolean })?.isComposing);
                      if (!e.shiftKey && !isComposing) {
                        e.preventDefault();
                        if (canSend) void send();
                      }
                      return;
                    }
                  }}
                  onClick={(e) => {
                    const el = e.currentTarget;
                    updateMentionState(el.value, el.selectionStart ?? el.value.length);
                  }}
                />
              </div>

              <div className="mt-2 flex items-center justify-between gap-2">
                <div className="flex items-center gap-2">
                  <Button variant="outline" size="sm" onClick={() => fileInputRef.current?.click()} disabled={loading} title="上传文件到当前页面附件">
                    <Paperclip className="mr-2 h-4 w-4" />
                    上传
                  </Button>
                  <input
                    ref={fileInputRef}
                    type="file"
                    className="hidden"
                    onChange={(e) => {
                      void uploadFiles(e.target.files);
                    }}
                    accept="*/*"
                  />
                  <div className="text-xs text-muted-foreground">Enter 发送 · Shift+Enter 换行</div>
                </div>

                <div className="flex items-center gap-2">
                  <Button
                    variant="secondary"
                    disabled={!loading}
                    onClick={stop}
                    title={aiProvider === "codex" ? "暂停本次执行（类似 ESC）" : "停止本次执行"}
                  >
                    <X className="mr-2 h-4 w-4" />
                    {aiProvider === "codex" ? "暂停" : "停止"}
                  </Button>
                  <Button disabled={!canSend} onClick={() => void send()}>
                    <Send className="mr-2 h-4 w-4" />
                    发送
                  </Button>
                </div>
              </div>
            </div>
          </>
        ) : null}

        {page === "tools" ? (
          <ScrollArea className="h-full">
            <div className="space-y-3 p-3 text-sm">
              <div className="flex flex-wrap items-center gap-2">
                <button
                  type="button"
                  className={`inline-flex items-center gap-1 rounded border px-2 py-1 ${toolAuto ? "bg-white" : "bg-muted"}`}
                  onClick={() => setToolAuto((v) => !v)}
                  disabled={loading}
                  title="工具自动/手动"
                >
                  <Settings2 className="h-3.5 w-3.5" />
                  {toolAuto ? "自动工具" : "手动工具"}
                </button>
              </div>

              {!toolAuto ? (
                <div>
                  <div className="mb-2 text-xs text-muted-foreground">允许使用的工具（手动模式）</div>
                  <div className="flex flex-wrap gap-2">
                    {(Object.keys(TOOL_LABEL) as ToolName[]).map((t) => {
                      const on = selectedTools.includes(t);
                      return (
                        <button
                          key={t}
                          type="button"
                          className={`rounded border px-2 py-1 text-xs ${on ? "bg-[#111827] text-white" : "bg-white"}`}
                          onClick={() =>
                            setSelectedTools((prev) => (prev.includes(t) ? prev.filter((x) => x !== t) : [...prev, t]))
                          }
                          disabled={loading}
                        >
                          {TOOL_LABEL[t]}
                        </button>
                      );
                    })}
                  </div>
                </div>
              ) : (
                <div className="text-xs text-muted-foreground">自动模式下：AI 会在允许的 ToolSet 范围内自行选择工具。</div>
              )}
            </div>
          </ScrollArea>
        ) : null}

        {page === "history" ? (
          <ScrollArea className="h-full">
            <div className="p-3 text-sm">
              <div className="flex flex-wrap items-center justify-between gap-2">
                <div className="text-xs text-muted-foreground">最多保留 20 个会话</div>
                <div className="flex flex-wrap items-center gap-2">
                  <Button variant="outline" onClick={resetCurrentSession} disabled={loading}>
                    清空当前
                  </Button>
                  <Button variant="outline" onClick={startNewSession} disabled={loading}>
                    <Plus className="mr-2 h-4 w-4" />
                    新建会话
                  </Button>
                  <Button variant="destructive" onClick={clearHistory} disabled={loading}>
                    清空历史
                  </Button>
                </div>
              </div>
              <div className="mt-3 space-y-2">
                {sessions.map((s) => {
                  const active = s.id === activeSessionId;
                  const time = new Date(s.updatedAt || s.createdAt).toLocaleString();
                  return (
                    <div key={s.id} className={`flex items-center gap-2 rounded border px-3 py-2 ${active ? "border-[#111827]" : "border-border"}`}>
                      <button
                        type="button"
                        className="min-w-0 flex-1 text-left"
                        onClick={() => {
                          switchSession(s.id);
                          setPage("chat");
                        }}
                        disabled={loading}
                        title={active ? "当前会话" : "切换到该会话"}
                      >
                        <div className="flex items-center justify-between gap-2">
                          <div className="truncate font-medium">{s.title || "未命名"}</div>
                          <div className="shrink-0 text-xs text-muted-foreground">{time}</div>
                        </div>
                        <div className="mt-1 text-xs text-muted-foreground">消息 {s.messages.length} · 日志 {s.toolLogs.length}</div>
                      </button>
                      <Button
                        variant="ghost"
                        size="sm"
                        onClick={() => deleteSession(s.id)}
                        disabled={loading || active}
                        title={active ? "不能删除当前会话" : "删除会话"}
                      >
                        删除
                      </Button>
                    </div>
                  );
                })}
              </div>
            </div>
          </ScrollArea>
        ) : null}

        {page === "account" ? (
          <ScrollArea className="h-full">
            <div className="space-y-3 p-3 text-sm">
              <div className="flex flex-wrap items-center gap-2">
                <label className="text-xs text-muted-foreground">推理来源</label>
                <select
                  className="h-9 rounded border bg-white px-2 text-sm"
                  value={aiProvider}
                  onChange={(e) => {
                    const v = String(e.target.value || "").trim();
                    if (v === "online" || v === "local" || v === "ollama" || v === "codex") setAiProvider(v);
                    else setAiProvider("online");
                  }}
                  disabled={loading}
                >
                  <option value="online">在线</option>
                  <option value="local">本地</option>
                  <option value="ollama">Ollama</option>
                  <option value="codex">Codex</option>
                </select>
                <label className="ml-2 text-xs text-muted-foreground">模型</label>
                {aiProvider === "codex" ? (
                  <div className="text-xs text-muted-foreground">
                    使用本地 Codex 配置。消息开头加 <code className="rounded bg-muted px-1 py-0.5">#chat</code> /{" "}
                    <code className="rounded bg-muted px-1 py-0.5">#test</code> /{" "}
                    <code className="rounded bg-muted px-1 py-0.5">#dev</code>（默认 <code className="rounded bg-muted px-1 py-0.5">#chat</code>）。
                  </div>
                ) : aiProvider === "online" ? (
                  <select
                    data-testid="mindmap-ai-model-select"
                    className="h-9 rounded border bg-white px-2 text-sm"
                    value={ONLINE_MODELS.includes(aiModel as any) ? aiModel : ""}
                    onChange={(e) => setAiModel(e.target.value)}
                    disabled={loading}
                  >
                    {ONLINE_MODELS.map((m) => (
                      <option key={m || "__default__"} value={m}>
                        {m ? m : "默认（ai.md）"}
                      </option>
                    ))}
                  </select>
                ) : aiProvider === "ollama" ? (
                  <select
                    className="h-9 rounded border bg-white px-2 text-sm"
                    value={aiModel === "" || aiModel === OLLAMA_QWEN3_30B ? aiModel : ""}
                    onChange={(e) => setAiModel(e.target.value)}
                    disabled={loading}
                  >
                    <option value="">默认：{OLLAMA_QWEN3_30B}</option>
                    <option value={OLLAMA_QWEN3_30B}>{OLLAMA_QWEN3_30B}</option>
                  </select>
                ) : (
                  <input
                    data-testid="mindmap-ai-model-input"
                    className="h-9 w-[200px] rounded border bg-white px-2 text-sm"
                    value={aiModel}
                    onChange={(e) => setAiModel(e.target.value)}
                    placeholder="默认（ai.local.md/环境变量）"
                    disabled={loading}
                    list="mindmap-local-model-suggestions"
                  />
                )}
              </div>
              {aiProvider === "local" ? (
                <div className="text-xs text-muted-foreground">
                  本地 AI 需配置 `LOCAL_AI_BASE_URL`/`LOCAL_AI_MODEL` 或 `ai.local.md` / `ai-local.md`。
                </div>
              ) : aiProvider === "ollama" ? (
                <div className="text-xs text-muted-foreground">
                  Ollama 默认使用 <code className="rounded bg-muted px-1 py-0.5">http://127.0.0.1:11434/v1</code>（可用 <code className="rounded bg-muted px-1 py-0.5">OLLAMA_BASE_URL</code> 覆盖）。
                </div>
              ) : null}
              <datalist id="mindmap-local-model-suggestions">
                <option value={OLLAMA_QWEN3_30B} />
              </datalist>
            </div>
          </ScrollArea>
        ) : null}

        {page === "settings" ? (
          <ScrollArea className="h-full">
            <div className="space-y-3 p-3 text-sm">
              <div className="flex flex-wrap items-center gap-3">
                <button
                  type="button"
                  className={`inline-flex items-center gap-1 rounded border px-2 py-1 ${networkOn ? "bg-white" : "bg-muted"}`}
                  onClick={() => setNetworkOn((v) => !v)}
                  disabled={loading}
                  title="联网检索（SearxNG）"
                >
                  <Network className="h-3.5 w-3.5" />
                  {networkOn ? "联网" : "离线"}
                </button>

                <label className="flex items-center gap-2">
                  步数
                  <input
                    className="w-[92px] rounded border px-2 py-1 text-sm"
                    type="number"
                    min={1}
                    max={24}
                    step={1}
                    value={maxSteps}
                    onChange={(e) => {
                      const v = Number(e.target.value);
                      if (!Number.isFinite(v)) return;
                      setMaxSteps(clamp(Math.floor(v), 1, 24));
                    }}
                    disabled={loading}
                  />
                </label>
              </div>
              <div className="text-xs text-muted-foreground">说明：步数越大越“能做事”，但会更慢且更消耗推理额度。</div>
              {debug ? (
                <details className="rounded border bg-background p-2 text-xs text-muted-foreground">
                  <summary className="cursor-pointer select-none">调试信息（SSE 事件）</summary>
                  <pre className="mt-2 max-h-56 overflow-auto whitespace-pre-wrap">{debug}</pre>
                </details>
              ) : null}
            </div>
          </ScrollArea>
        ) : null}
      </div>

    </AiBridgePanel>
  );
}

export { MindmapAiAgentPanelRuntime as MindmapAiAgentPanel };
