227 lines
7.1 KiB
TypeScript
227 lines
7.1 KiB
TypeScript
"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=skill,URL 已更新");
|
||
}
|
||
|
||
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>
|
||
|
||
{/* 场景 2:skill 选择通信 */}
|
||
<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>
|
||
);
|
||
}
|