Files
inkreach-agent/apps/desktop/preview/main.tsx
T
Rick Ye a5951849c8 v0.6.0: beautifului 整合 + 会话/工作区/队列/权限/命令/附件全链路
- beautifului.dev 组件直连:Sidebar Nav、Tool Chips、Approval Card(抽屉化)、Streaming/Thinking、Code Block、Selection Actions、Prompt Bar、Search
2026-09-06 09:57:14 +08:00

211 lines
8.8 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.
// beautifului.dev 组件视觉验收页(静态构建,模拟聊天流内的真实用法)
import { useRef, useState } from 'react'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './preview.css'
import { Thinking } from '../src/renderer/src/components/Thinking'
import { StreamingText } from '../src/renderer/src/components/StreamingText'
import { ApprovalStack } from '../src/renderer/src/components/ApprovalCard'
import { ToolChips } from '../src/renderer/src/components/ToolChips'
import { SidebarNav } from '../src/renderer/src/components/SidebarNav'
import { Search } from '../src/renderer/src/components/Search'
import { CodeBlock } from '../src/renderer/src/components/CodeBlock'
import { SelectionActions } from '../src/renderer/src/components/SelectionActions'
import { Loading } from '../src/renderer/src/components/Loading'
import { Markdown } from '../src/renderer/src/components/Markdown'
import type { ApprovalRequest, SessionMeta } from '../src/shared/types'
const thinkingRows = [
{ primary: '用户想了解今天的品类热点,我先查一下最近的趋势数据。' },
{ primary: '需要调用 query_trend 工具获取近 7 天的热点词,再按类目分组。' },
{ primary: '结果里先给 TOP3 品类,附上建议的标题关键词。' }
]
const toolCalls = [
{
id: 'call-1',
name: 'query_trend',
tool: {
toolName: 'query_trend',
args: { category: 'home', days: 7 },
status: 'done' as const,
result: { content: [{ type: 'text', text: '{ "top": ["收纳", "香薰", "桌布"], "lift": [0.32, 0.21, 0.18] }' }] }
}
},
{
id: 'call-2',
name: 'draft_title',
tool: {
toolName: 'draft_title',
args: { keywords: ['收纳', '灵感'] },
status: 'error' as const,
result: { content: [{ type: 'text', text: 'Error: rate limited, retry after 30s' }] }
}
},
{
id: 'call-3',
name: 'update_price',
pendingApproval: { args: { sku: 'SKU-1024', from: 39.9, to: 35.9 } }
}
]
const approvalRequest: ApprovalRequest = {
id: 'approval-1',
toolName: 'update_price',
args: { sku: 'SKU-1024', from: 39.9, to: 35.9 }
}
const sessions: SessionMeta[] = [
{ id: 's1', path: 'a.json', name: '供应商记录整理', firstMessage: '帮我整理供应商', created: '', modified: new Date().toISOString(), messageCount: 12 },
{ id: 's2', path: 'b.json', name: '', firstMessage: '根据热点词起草一个商品标题方案', created: '', modified: new Date(Date.now() - 86400000).toISOString(), messageCount: 5 },
{ id: 's3', path: 'c.json', name: '', firstMessage: '查询最近一次上架任务的执行结果', created: '', modified: new Date(Date.now() - 3 * 86400000).toISOString(), messageCount: 8 }
]
const CODE_SNIPPET = `export async function churnBatch() {
const flavor = await getFlavor("pistachio");
const base = await dairy.fetch({ flavor });
await freezer.store(base, { temp: "-16C" });
if (!base.approved) return null;
return base.gallons;
}`
const DIFF_SNIPPET = `@@ -1,4 +1,5 @@
export const hero = "mint-chip";
-const windows = slots;
+const windows = slots.filter(
+ (s) => s.temp <= -12,
+);
schedule(windows);`
const PARA =
'百香果风味在整个周末保持热销榜首。周六一早就要开始搅拌,让批次在下午高峰前有足够时间固化。同时需要预留两个晚间冷冻窗口,避免与常规生产冲突。'
function PreviewCard({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="mb-8 rounded-2xl border border-zinc-200 bg-white p-5">
<h2 className="mb-3 text-[12px] font-medium tracking-wide text-zinc-400 uppercase">{title}</h2>
{children}
</section>
)
}
function App() {
const [searchOpen, setSearchOpen] = useState(false)
const [applied, setApplied] = useState('')
const selectionBoxRef = useRef<HTMLDivElement>(null)
// 调试分区:URL hash 指定只渲染某一节(#thinking / #streaming / #tools /
// #approval / #code / #selection / #loading / #sidebar / #none),空 hash 全渲染
const only = window.location.hash.slice(1)
const show = (id: string): boolean => only === '' || only === id
return (
<div className="mx-auto max-w-5xl px-6 py-8">
<h1 className="mb-6 text-[14px] font-semibold text-zinc-900">beautifului.dev · 8 组件直连验收(light)</h1>
<div className="grid grid-cols-[minmax(0,1fr)_240px] items-start gap-6">
<div className="min-w-0">
{show('thinking') && (
<PreviewCard title="Thinking · 流式中 / 已完成">
<Thinking rows={thinkingRows} streaming />
<div className="mt-4">
<Thinking rows={thinkingRows} streaming={false} />
</div>
</PreviewCard>
)}
{show('streaming') && (
<PreviewCard title="Streaming Text · 流式正文 + Follow-ups">
<StreamingText streaming={false} text="已完成的回答文本。" followUps={['冬季哪些口味卖得最好', '对比凝胶和软冰的毛利']} onFollowUp={(q) => setApplied(q)}>
<Markdown text={'夏季核果风味需求激增——**桃子和杏子**领先。建议先盘点蛋筒库存,再推广华夫碗套餐。'} />
</StreamingText>
</PreviewCard>
)}
{show('tools') && (
<PreviewCard title="Tool Chips · 真实工具调用(含待审批行)">
<div className="w-fit max-w-full rounded-card bg-inset p-3">
<ToolChips calls={toolCalls} thinking={['先查近 7 天热点词,再按类目分组给出 TOP3。']} />
</div>
</PreviewCard>
)}
{show('approval') && (
<PreviewCard title="Approval Card · 单题向导(允许/拒绝 + 倒计时)">
<ApprovalStack requests={[approvalRequest]} onResolve={(_id, approved) => setApplied(approved ? '已允许' : '已拒绝')} />
<div className="h-2" />
</PreviewCard>
)}
{show('code') && (
<PreviewCard title="Code Block · 行号代码 / unified diff">
<CodeBlock code={CODE_SNIPPET} lang="ts" filename="churn.ts" />
<div className="mt-4">
<CodeBlock code={DIFF_SNIPPET} lang="diff" />
</div>
</PreviewCard>
)}
{show('selection') && (
<PreviewCard title="Selection Actions · 划选文本试试">
<div ref={selectionBoxRef} className="relative pb-10">
<p className="text-[13px] leading-relaxed text-ink">{PARA}</p>
<SelectionActions containerRef={selectionBoxRef} onApply={(instruction, selection) => setApplied(`${instruction}:${selection.slice(0, 18)}…`)} />
</div>
{applied && <p className="mt-2 font-mono text-[11px] text-zinc-500">onApply → {applied}</p>}
</PreviewCard>
)}
{show('loading') && (
<PreviewCard title="Loading(附带)">
<Loading label="思考中" variant="Dots" />
</PreviewCard>
)}
</div>
{show('sidebar') && (
<div className="sticky top-8">
<PreviewCard title="Sidebar Nav · 可折叠">
<div className="h-[560px] overflow-hidden rounded-xl border border-zinc-200">
<SidebarNav
sessionState={{ sessionId: 's1', streaming: false, activeTools: [], messageCount: 12 }}
sessions={sessions}
workspaces={[{ name: 'Default', path: 'C:/workspaces/Default' }]}
activeWorkspace={{ name: 'Default', path: 'C:/workspaces/Default' }}
onCreateWorkspace={() => undefined}
onOpenWorkspace={() => undefined}
onNewSession={() => setApplied('新会话')}
onOpenSession={() => setApplied('打开会话')}
onRenameSession={() => setApplied('重命名')}
onDeleteSession={() => setApplied('删除')}
onRefreshSessions={() => setApplied('刷新')}
onOpenSearch={() => setSearchOpen(true)}
onOpenSettings={() => setApplied('设置')}
/>
</div>
</PreviewCard>
</div>
)}
</div>
{show('sidebar') && (
<Search
open={searchOpen}
onClose={() => setSearchOpen(false)}
sessions={sessions}
onNewSession={() => setApplied('新会话')}
onRefreshSessions={() => setApplied('刷新')}
onOpenSettings={() => setApplied('设置')}
onOpenSession={() => setApplied('打开会话')}
/>
)}
</div>
)
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
)