deerflow2/frontend/src/hooks/use-iframe-skill.ts

99 lines
3.5 KiB
TypeScript

import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect, useCallback, useRef } from "react";
import {
POST_MESSAGE_TYPES,
RECEIVE_MESSAGE_TYPES,
isSelectedSkillMessage,
type SelectedSkillPayloadItem,
sendToParent,
} from "@/core/iframe-messages";
// Skill 数据类型
interface SkillData {
skill_id: string;
title: string;
}
// Hook 返回类型
interface UseIframeSkillReturn {
selectedSkill: SkillData | null;
sendSelectSkill: (selectedSkills: SelectedSkillPayloadItem[]) => void;
openSkillDialog: () => void;
clearSkill: () => void;
}
export function useIframeSkill(): UseIframeSkillReturn {
const router = useRouter();
const searchParams = useSearchParams();
const threadIdFromQuery = searchParams.get("thread_id");
const isChattingFromQuery = searchParams.get("is_chatting");
const lastThreadIdRef = useRef<string | null>(null);
const [selectedSkill, setSelectedSkill] = useState<SkillData | null>(null);
// 1. 监听 query 参数变化(临时禁用)
// TODO: 当前 skill 仅通过 iframe postMessage 传递,暂不从 URL 读取 skill_id/title。
// useEffect(() => {
// const skillIdFromQuery = searchParams.get("skill_id");
// const titleFromQuery = searchParams.get("title");
// if (skillIdFromQuery && titleFromQuery) {
// setSelectedSkill({ skill_id: skillIdFromQuery, title: titleFromQuery });
// }
// }, [searchParams]);
// 0. 监听 query 中 is_chatting=true 且带 thread_id 时跳转到 thread 页面
useEffect(() => {
if (!threadIdFromQuery) return;
if (isChattingFromQuery !== "true") return;
if (lastThreadIdRef.current === threadIdFromQuery) return;
lastThreadIdRef.current = threadIdFromQuery;
router.replace(`/workspace/chats/${threadIdFromQuery}`);
}, [isChattingFromQuery, router, threadIdFromQuery]);
// 2. 监听宿主页 postMessage
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data?.type !== RECEIVE_MESSAGE_TYPES.SELECTED_SKILL) {
return;
}
if (!isSelectedSkillMessage(event.data)) {
console.warn("[useIframeSkill] 忽略非法 selectedSkill 消息", event.data);
return;
}
const { id, title } = event.data;
setSelectedSkill({ skill_id: String(id), title });
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
// 发送选择预定义 skill
const sendSelectSkill = useCallback((selectedSkills: SelectedSkillPayloadItem[]) => {
const message = { type: POST_MESSAGE_TYPES.SELECT_SKILLS, selectedSkills };
console.log("[useIframeSkill] sendSelectSkill:", message);
sendToParent(message);
}, []);
// 打开 skill 选择对话框
const openSkillDialog = useCallback(() => {
const message = {
type: POST_MESSAGE_TYPES.OPEN_SKILL_DIALOG,
openSkillDialog: true,
} as const;
console.log("[useIframeSkill] openSkillDialog:", message);
sendToParent(message);
}, []);
// 清除选中并发送空 selectedSkills 数组给主页
const clearSkill = useCallback(() => {
setSelectedSkill(null);
// 发送空数组给主页,通知取消选择
const message = { type: POST_MESSAGE_TYPES.SELECT_SKILLS, selectedSkills: [] };
console.log("[useIframeSkill] clearSkill, sending selectedSkills=[]:", message);
sendToParent(message);
}, []);
return { selectedSkill, sendSelectSkill, openSkillDialog, clearSkill };
}