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:
@@ -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>
|
||||
)
|
||||
Reference in New Issue
Block a user