deerflow2/frontend/src/components/workspace/iframe-test-panel.tsx

227 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useSearchParams, useRouter } from "next/navigation";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useIframeSkill } from "@/hooks/use-iframe-skill";
import { cn } from "@/lib/utils";
/**
* IframeTestPanel —— 仅用于开发阶段测试 iframe 通信功能
*
* 测试场景:
* 1. mode=skill 侧边栏隐藏
* 2. useSpecificChatMode 注入提示词
* 3. sendSelectSkill / openSkillDialog / clearSkill
*/
export function IframeTestPanel() {
const router = useRouter();
const searchParams = useSearchParams();
const iframeSkill = useIframeSkill();
const [log, setLog] = useState<string[]>([]);
const [open, setOpen] = useState(true);
const isSkillMode = searchParams.get("mode") === "skill";
function addLog(msg: string) {
setLog((prev) => [
`[${new Date().toLocaleTimeString()}] ${msg}`,
...prev.slice(0, 9),
]);
}
function handleEnterSkillMode() {
router.push(`?mode=skill&skill_id=123&title=测试技能`);
addLog("进入 mode=skillURL 已更新");
}
function handleExitSkillMode() {
router.push(`?`);
addLog("退出 skill 模式");
}
function handleSendSelectSkill() {
iframeSkill.sendSelectSkill("skill_001");
addLog("postMessage → selectSkill (skill_id=skill_001)");
}
function handleOpenSkillDialog() {
iframeSkill.openSkillDialog();
addLog("postMessage → openSkillDialog");
}
function handleClearSkill() {
iframeSkill.clearSkill();
addLog("clearSkill 已调用postMessage → skill_id=0");
}
if (!open) {
return (
<button
className="fixed bottom-24 left-3 z-[9999] rounded-full bg-violet-500 px-3 py-1 text-xs font-bold text-white shadow-lg hover:bg-violet-600"
onClick={() => setOpen(true)}
>
🧪
</button>
);
}
return (
<div className="fixed bottom-24 left-3 z-[9999] w-72 rounded-xl border border-violet-200 bg-white/95 shadow-2xl backdrop-blur-sm">
{/* 标题栏 */}
<div className="flex items-center justify-between rounded-t-xl bg-violet-500 px-3 py-2">
<span className="text-xs font-bold text-white">🧪 iframe </span>
<button
className="text-white/70 hover:text-white"
onClick={() => setOpen(false)}
>
</button>
</div>
<div className="space-y-3 p-3">
{/* 当前状态 */}
<div className="rounded-lg bg-gray-50 px-3 py-2 text-xs">
<div className="mb-1 font-semibold text-gray-500"></div>
<div className="flex flex-col gap-1">
<span>
<span className="text-gray-400">mode</span>
<span
className={cn(
"font-mono font-bold",
isSkillMode ? "text-violet-600" : "text-gray-400",
)}
>
{isSkillMode ? "skill ✅" : "普通"}
</span>
</span>
<span>
<span className="text-gray-400">selectedSkill</span>
<span className="font-mono text-violet-600">
{iframeSkill.selectedSkill
? `${iframeSkill.selectedSkill.skill_id} / ${iframeSkill.selectedSkill.title}`
: "无"}
</span>
</span>
</div>
</div>
{/* 场景 1侧边栏隐藏 */}
<div>
<div className="mb-1 text-xs font-semibold text-gray-500">
layout
</div>
<div className="flex gap-2">
<Button
size="sm"
className="flex-1 text-xs"
variant="outline"
onClick={handleEnterSkillMode}
>
skill
</Button>
<Button
size="sm"
className="flex-1 text-xs"
variant="outline"
onClick={handleExitSkillMode}
>
退 skill
</Button>
</div>
</div>
{/* 场景 2skill 选择通信 */}
<div>
<div className="mb-1 text-xs font-semibold text-gray-500">
postMessage 宿
</div>
<div className="flex flex-col gap-2">
<Button
size="sm"
className="w-full bg-violet-50 text-xs text-violet-700 hover:bg-violet-100"
variant="ghost"
onClick={handleSendSelectSkill}
>
sendSelectSkill (skill_001)
</Button>
<Button
size="sm"
className="w-full bg-violet-50 text-xs text-violet-700 hover:bg-violet-100"
variant="ghost"
onClick={handleOpenSkillDialog}
>
openSkillDialog
</Button>
<Button
size="sm"
className="w-full bg-red-50 text-xs text-red-600 hover:bg-red-100"
variant="ghost"
onClick={handleClearSkill}
>
clearSkill ( skill_id=0)
</Button>
</div>
</div>
{/* 场景 3接收宿主页 selectedSkill */}
<div>
<div className="mb-1 text-xs font-semibold text-gray-500">
宿 selectedSkill
</div>
<div className="flex flex-col gap-2">
<Button
size="sm"
className="w-full bg-green-50 text-xs text-green-700 hover:bg-green-100"
variant="ghost"
onClick={() => {
window.postMessage(
{ type: "selectedSkill", id: 5, title: "文档处理" },
"*",
);
addLog(
"模拟宿主页 → selectedSkill { id: 5, title: '文档处理' }",
);
}}
>
selectedSkill
</Button>
<Button
size="sm"
className="w-full bg-orange-50 text-xs text-orange-700 hover:bg-orange-100"
variant="ghost"
onClick={() => {
window.postMessage(
{ type: "selectedSkill", id: 999999, title: "不存在的技能" },
"*",
);
addLog(
"模拟宿主页 → selectedSkill { id: 999999, title: '不存在的技能' }",
);
}}
>
selectedSkill/
</Button>
</div>
</div>
{/* 日志 */}
{log.length > 0 && (
<div className="rounded-lg bg-gray-900 p-2">
<div className="mb-1 text-[10px] font-semibold text-gray-400">
</div>
{log.map((l, i) => (
<div
key={i}
className="truncate font-mono text-[10px] text-green-400"
>
{l}
</div>
))}
</div>
)}
</div>
</div>
);
}