v0.6.0: beautifului 整合 + 会话/工作区/队列/权限/命令/附件全链路

- beautifului.dev 组件直连:Sidebar Nav、Tool Chips、Approval Card(抽屉化)、Streaming/Thinking、Code Block、Selection Actions、Prompt Bar、Search
This commit is contained in:
Rick Ye
2026-09-06 09:57:14 +08:00
parent 3c2e9e96c5
commit a5951849c8
96 changed files with 11828 additions and 4649 deletions
+210
View File
@@ -0,0 +1,210 @@
// 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>
)