feat: 将 demo 数据迁移为 JSON 协议文件,激活 Raw Protocol 面板
- 新建 6 个 demo JSON 文件 (demo-a ~ demo-f),替代 TS 硬编码常量
- 新建 prompt-envelope.schema.json (JSON Schema Draft 2020-12)
- 新建 demos-loader.ts: validateEnvelope() 运行时验证 + hydrateSkills() 从 skills 源补全 body
- 新建 manifest.json 场景索引
- ProtocolPanel: Raw Protocol tab 已激活,可切换查看原始 PromptEnvelope JSON
- demo.ts 改为从 JSON import + loadEnvelope() 加载
- Skill body 不入协议,仅保留 name + description (L1),由加载器补全
- 时间戳改为固定 epoch 毫秒值
- 删除 6 个旧 demo-{a..f}.ts 文件
构建通过,26 个测试全部通过
This commit is contained in:
@@ -7,29 +7,52 @@ interface ProtocolPanelProps {
|
||||
envelope: PromptEnvelope
|
||||
}
|
||||
|
||||
type Tab = 'openai' | 'raw'
|
||||
|
||||
export default function ProtocolPanel({ envelope }: ProtocolPanelProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('openai')
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const openaiFormat = useMemo(() => exportToOpenAIFormat(envelope), [envelope])
|
||||
const rawProtocolJson = useMemo(() => JSON.stringify(envelope, null, 2), [envelope])
|
||||
|
||||
const displayedJson = activeTab === 'openai'
|
||||
? JSON.stringify(openaiFormat, null, 2)
|
||||
: rawProtocolJson
|
||||
|
||||
const downloadFilename = activeTab === 'openai'
|
||||
? 'openai-export.json'
|
||||
: 'prompt-envelope.json'
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(JSON.stringify(openaiFormat, null, 2))
|
||||
await navigator.clipboard.writeText(displayedJson)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
const handleDownload = () => {
|
||||
const blob = new Blob([JSON.stringify(openaiFormat, null, 2)], {
|
||||
type: 'application/json',
|
||||
})
|
||||
const blob = new Blob([displayedJson], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'openai-export.json'
|
||||
a.download = downloadFilename
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
/** 统计 envelope 中的 segment 数量 */
|
||||
const segmentStats = useMemo(() => {
|
||||
const counts: Record<string, number> = {}
|
||||
for (const msg of envelope.messages) {
|
||||
for (const seg of msg.segments) {
|
||||
counts[seg.kind] = (counts[seg.kind] || 0) + 1
|
||||
}
|
||||
}
|
||||
return counts
|
||||
}, [envelope])
|
||||
|
||||
const totalSegments = Object.values(segmentStats).reduce((a, b) => a + b, 0)
|
||||
|
||||
return (
|
||||
<div className="w-96 border-l border-gray-200 bg-white flex flex-col h-full">
|
||||
{/* Header */}
|
||||
@@ -56,27 +79,53 @@ export default function ProtocolPanel({ envelope }: ProtocolPanelProps) {
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex text-xs border-b border-gray-100">
|
||||
<div className="flex-1 text-center py-2 font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50">
|
||||
<button
|
||||
onClick={() => setActiveTab('openai')}
|
||||
className={`flex-1 text-center py-2 transition-colors cursor-pointer ${
|
||||
activeTab === 'openai'
|
||||
? 'font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50'
|
||||
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
OpenAI Format
|
||||
</div>
|
||||
<div className="flex-1 text-center py-2 text-gray-400">
|
||||
Raw Protocol (soon)
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('raw')}
|
||||
className={`flex-1 text-center py-2 transition-colors cursor-pointer ${
|
||||
activeTab === 'raw'
|
||||
? 'font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50'
|
||||
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
Raw Protocol
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* JSON Content */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<pre className="p-4 text-xs font-mono text-gray-600 whitespace-pre-wrap break-all leading-relaxed">
|
||||
{JSON.stringify(openaiFormat, null, 2)}
|
||||
{displayedJson}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Footer stats */}
|
||||
<div className="px-4 py-2 border-t border-gray-100 text-[10px] text-gray-400 flex items-center gap-3">
|
||||
<span>model: {openaiFormat.model}</span>
|
||||
<span>{envelope.messages.length} 条协议消息</span>
|
||||
<span>{openaiFormat.messages.length} OpenAI messages</span>
|
||||
{openaiFormat.tools && <span>{openaiFormat.tools.length} tools</span>}
|
||||
<div className="px-4 py-2 border-t border-gray-100 text-[10px] text-gray-400 flex items-center gap-3 flex-wrap">
|
||||
{activeTab === 'openai' ? (
|
||||
<>
|
||||
<span>model: {openaiFormat.model}</span>
|
||||
<span>{envelope.messages.length} 条协议消息</span>
|
||||
<span>{openaiFormat.messages.length} OpenAI messages</span>
|
||||
{openaiFormat.tools && <span>{openaiFormat.tools.length} tools</span>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>version: {envelope.version}</span>
|
||||
{envelope.model && <span>model: {envelope.model}</span>}
|
||||
<span>{envelope.messages.length} 条消息</span>
|
||||
<span>{totalSegments} 个 segment</span>
|
||||
<span>{Object.keys(segmentStats).length} 种类型</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user