diff --git a/frontend/src/hooks/use-selected-skill-listener.ts b/frontend/src/hooks/use-selected-skill-listener.ts deleted file mode 100644 index fc20d66d..00000000 --- a/frontend/src/hooks/use-selected-skill-listener.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { useSearchParams } from "next/navigation"; -import { useEffect, useCallback, useState, useRef } from "react"; -import { toast } from "sonner"; - -import { bootstrapRemoteSkill } from "@/core/skills/api"; - -/** 宿主页发过来的 selectedSkill 消息结构 */ -interface SelectedSkillMessage { - type: "selectedSkill"; - id: number | string; - title: string; -} - -/** 技能基础数据 */ -interface SkillData { - skill_id: string; - title: string; -} - -/** 错误信息状态 */ -interface SkillError { - title: string; - message: string; -} - -interface UseSelectedSkillListenerOptions { - /** 当前会话 thread_id,用于调用 bootstrapRemoteSkill */ - threadId: string | null; -} - -interface UseSelectedSkillListenerReturn { - /** 当前选中的技能数据(用于 UI 展示,如 Badge) */ - selectedSkill: SkillData | null; - /** 当前错误信息,不为 null 时展示 DevDialog */ - skillError: SkillError | null; - /** 清除错误信息(关闭 DevDialog 时调用) */ - clearSkillError: () => void; - /** 是否正在加载(处理 skill 中) */ - isBootstrapping: boolean; -} - -/** - * 监听宿主页通过 postMessage 发送的 selectedSkill 消息或 URL 中的 skill 参数, - * 收到后自动调用 bootstrapRemoteSkill 接口: - * - 成功:使用 toast 提示 - * - 失败:返回 skillError 供 DevDialog 显示 - */ -export function useSelectedSkillListener({ - threadId, -}: UseSelectedSkillListenerOptions): UseSelectedSkillListenerReturn { - const searchParams = useSearchParams(); - const [selectedSkill, setSelectedSkill] = useState(null); - const [skillError, setSkillError] = useState(null); - const [isBootstrapping, setIsBootstrapping] = useState(false); - - const isFirstLoadRef = useRef(false); - const skillBootstrappedKeyRef = useRef(null); - - const performBootstrap = useCallback( - async (id: number | string, title: string) => { - if (!threadId) return; - - const languageTypeRaw = - searchParams.get("languageType")?.trim() ?? - searchParams.get("language_type")?.trim(); - const languageType = languageTypeRaw ? Number(languageTypeRaw) : 0; - - const initKey = `${threadId}:${id}:${languageType}`; - if (skillBootstrappedKeyRef.current === initKey) { - return; - } - - console.log( - `[useSelectedSkillListener] 开始初始化技能: ${title} (${id})`, - ); - setIsBootstrapping(true); - toast.loading(`正在加载技能「${title}」...`, { id: "skill-bootstrap" }); - - try { - const result = await bootstrapRemoteSkill({ - thread_id: threadId, - content_id: Number(id), - language_type: languageType, - target_dir: "/mnt/user-data/uploads/skill", - clear_target: true, - }); - - toast.dismiss("skill-bootstrap"); - - if (result.success) { - skillBootstrappedKeyRef.current = initKey; - toast.success(`技能「${title}」加载成功`, { - description: - result.message || `已创建 ${result.created_files} 个文件`, - duration: 4000, - }); - } else { - setSkillError({ - title: `技能「${title}」加载失败`, - message: result.message || "未知错误", - }); - } - } catch (err) { - toast.dismiss("skill-bootstrap"); - const message = err instanceof Error ? err.message : "网络请求失败"; - setSkillError({ title: `技能「${title}」加载出错`, message }); - } finally { - setIsBootstrapping(false); - } - }, - [threadId, searchParams], - ); - - // 1. URL 初始化集成 - useEffect(() => { - if (!threadId || isFirstLoadRef.current) return; - - const skillIdFromQuery = searchParams.get("skill_id"); - const titleFromQuery = searchParams.get("title"); - if (skillIdFromQuery && titleFromQuery) { - isFirstLoadRef.current = true; - setSelectedSkill({ skill_id: skillIdFromQuery, title: titleFromQuery }); - void performBootstrap(skillIdFromQuery, titleFromQuery); - } - }, [threadId, searchParams, performBootstrap]); - - const handleMessage = useCallback( - (event: MessageEvent) => { - const data = event.data as SelectedSkillMessage; - if (data?.type !== "selectedSkill") return; - - const { id, title } = data; - console.log( - "[useSelectedSkillListener] 收到 postMessage selectedSkill:", - data, - ); - - setSelectedSkill({ skill_id: String(id), title }); - void performBootstrap(id, title); - }, - [performBootstrap], - ); - - useEffect(() => { - window.addEventListener("message", handleMessage); - return () => window.removeEventListener("message", handleMessage); - }, [handleMessage]); - - const clearSkillError = useCallback(() => setSkillError(null), []); - - return { selectedSkill, skillError, clearSkillError, isBootstrapping }; -}