@changfenhuang/dsh-genui 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +367 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh-CN.md +261 -0
- package/SKILL.md +115 -0
- package/cordis.patch.yml +9 -0
- package/demo-prompts.md +118 -0
- package/lib/assets/echarts.js +64 -0
- package/lib/assets/mermaid.js +3591 -0
- package/lib/assets/three.js +3844 -0
- package/lib/client.js +5 -0
- package/lib/index.js +1895 -0
- package/lib/invariant.js +19 -0
- package/lib/types/client/EChartNode.d.ts +4 -0
- package/lib/types/client/ErrorBoundary.d.ts +30 -0
- package/lib/types/client/GenuiBlock.d.ts +7 -0
- package/lib/types/client/PlotBlock.d.ts +44 -0
- package/lib/types/client/action-context.d.ts +7 -0
- package/lib/types/client/asset-echarts.d.ts +15 -0
- package/lib/types/client/asset-loader.d.ts +31 -0
- package/lib/types/client/asset-mermaid.d.ts +1 -0
- package/lib/types/client/asset-three.d.ts +1 -0
- package/lib/types/client/blocks/advanced.d.ts +104 -0
- package/lib/types/client/blocks/basic.d.ts +27 -0
- package/lib/types/client/blocks/charts.d.ts +38 -0
- package/lib/types/client/blocks/diagram/geometry.d.ts +73 -0
- package/lib/types/client/blocks/diagram/index.d.ts +5 -0
- package/lib/types/client/blocks/diagram/layout.d.ts +41 -0
- package/lib/types/client/blocks/diagram/theme.d.ts +35 -0
- package/lib/types/client/blocks/forms.d.ts +83 -0
- package/lib/types/client/blocks/render-node.d.ts +10 -0
- package/lib/types/client/blocks/state.d.ts +58 -0
- package/lib/types/client/dom-fence.d.ts +14 -0
- package/lib/types/client/echarts-lazy.d.ts +17 -0
- package/lib/types/client/fence-render.d.ts +58 -0
- package/lib/types/client/gallery.d.ts +9 -0
- package/lib/types/client/guard.d.ts +126 -0
- package/lib/types/client/index.d.ts +36 -0
- package/lib/types/client/interaction-store.d.ts +43 -0
- package/lib/types/client/mermaid-core.d.ts +8 -0
- package/lib/types/client/mermaid-lazy.d.ts +9 -0
- package/lib/types/client/mermaid-safe.d.ts +52 -0
- package/lib/types/client/panel-command.d.ts +29 -0
- package/lib/types/client/panel-store.d.ts +94 -0
- package/lib/types/client/panel.d.ts +15 -0
- package/lib/types/client/parse-partial.d.ts +37 -0
- package/lib/types/client/safe-math.d.ts +43 -0
- package/lib/types/client/scene3d-core.d.ts +21 -0
- package/lib/types/client/scene3d-lazy.d.ts +8 -0
- package/lib/types/client/spec.d.ts +610 -0
- package/lib/types/client/toolview.d.ts +7 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/plugin/index.d.ts +25 -0
- package/lib/types/plugin/invariant.d.ts +15 -0
- package/lib/types/plugin/tool.d.ts +27 -0
- package/lib/types/shared/fence-repair.d.ts +67 -0
- package/package.json +121 -0
- package/src/client/EChartNode.tsx +242 -0
- package/src/client/ErrorBoundary.tsx +66 -0
- package/src/client/GenuiBlock.module.css +1263 -0
- package/src/client/GenuiBlock.tsx +166 -0
- package/src/client/PlotBlock.module.css +155 -0
- package/src/client/PlotBlock.tsx +431 -0
- package/src/client/action-context.ts +32 -0
- package/src/client/asset-echarts.ts +25 -0
- package/src/client/asset-loader.ts +79 -0
- package/src/client/asset-mermaid.ts +13 -0
- package/src/client/asset-three.ts +12 -0
- package/src/client/blocks/advanced.tsx +435 -0
- package/src/client/blocks/basic.tsx +116 -0
- package/src/client/blocks/charts.tsx +319 -0
- package/src/client/blocks/diagram/geometry.ts +151 -0
- package/src/client/blocks/diagram/index.tsx +284 -0
- package/src/client/blocks/diagram/layout.ts +199 -0
- package/src/client/blocks/diagram/theme.ts +108 -0
- package/src/client/blocks/forms.tsx +523 -0
- package/src/client/blocks/render-node.tsx +260 -0
- package/src/client/blocks/state.ts +61 -0
- package/src/client/dom-fence.tsx +641 -0
- package/src/client/echarts-lazy.ts +35 -0
- package/src/client/fence-render.tsx +230 -0
- package/src/client/gallery.ts +128 -0
- package/src/client/guard.ts +1450 -0
- package/src/client/index.tsx +195 -0
- package/src/client/interaction-store.ts +122 -0
- package/src/client/mermaid-core.ts +121 -0
- package/src/client/mermaid-lazy.ts +31 -0
- package/src/client/mermaid-safe.ts +122 -0
- package/src/client/panel-command.ts +119 -0
- package/src/client/panel-store.ts +471 -0
- package/src/client/panel.tsx +185 -0
- package/src/client/parse-partial.ts +162 -0
- package/src/client/safe-math.ts +270 -0
- package/src/client/scene3d-core.ts +192 -0
- package/src/client/scene3d-lazy.ts +26 -0
- package/src/client/spec.ts +767 -0
- package/src/client/toolview.tsx +66 -0
- package/src/css-modules.d.ts +5 -0
- package/src/index.ts +12 -0
- package/src/plugin/index.ts +173 -0
- package/src/plugin/invariant.ts +30 -0
- package/src/plugin/node-shims.d.ts +31 -0
- package/src/plugin/tool.ts +330 -0
- package/src/shared/fence-repair.ts +244 -0
- package/tsconfig.json +35 -0
- package/tsdown.config.ts +193 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dsh-ui fence render pipeline, shared by both render channels:
|
|
3
|
+
*
|
|
4
|
+
* - **Registry channel** (contract hosts): the host's MarkdownText resolves
|
|
5
|
+
* ```dsh-ui fences through the fence-registry extension point and calls
|
|
6
|
+
* {@link renderGenuiFence} with a session-scoped context (sessionId + the
|
|
7
|
+
* settled source identity). An unrepairable body renders {@link FenceFallback}.
|
|
8
|
+
* - **DOM channel** (pristine hosts, no extension point): the DOM observer
|
|
9
|
+
* (`dom-fence.ts`) finds stock code blocks labelled `dsh-ui` and mounts
|
|
10
|
+
* {@link renderResolvedFenceNode} into its own React root, wrapped in the
|
|
11
|
+
* plugin-owned action context. An unrepairable body returns `null` so the
|
|
12
|
+
* stock code block stays visible.
|
|
13
|
+
*
|
|
14
|
+
* Structural types are declared locally on purpose: the context contract is
|
|
15
|
+
* a data shape, and pristine hosts do not export the host-side type names.
|
|
16
|
+
*/
|
|
17
|
+
import { Fragment, useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type Key, type ReactNode } from 'react'
|
|
18
|
+
import { CodeBlock } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
19
|
+
import { ErrorBoundary } from './ErrorBoundary.tsx'
|
|
20
|
+
import { GenuiBlock } from './GenuiBlock.tsx'
|
|
21
|
+
import { repairGenuiSpec } from './guard.ts'
|
|
22
|
+
import { fenceStateKey } from './interaction-store.ts'
|
|
23
|
+
import { parsePartialGenuiSpec } from './parse-partial.ts'
|
|
24
|
+
import { applyPanelOperation, diagnosePanelBudget, type PanelOperationStatus } from './panel-store.ts'
|
|
25
|
+
import type { GenuiSpec } from './spec.ts'
|
|
26
|
+
import { completeFenceJson, describeJsonFailure, isCompleteJson, repairFenceJson } from '../shared/fence-repair.ts'
|
|
27
|
+
|
|
28
|
+
/** Settled fence source identity (data shape, host-independent). */
|
|
29
|
+
export interface GenuiFenceSource {
|
|
30
|
+
/** Stable structural id, e.g. `['assistant', seq, block, fence]` or `dom:<anchor>:<i>`. */
|
|
31
|
+
readonly id: string
|
|
32
|
+
/** Three-part order: [messageSeq, textBlockIndex, fenceIndex]. */
|
|
33
|
+
readonly order: readonly [number, number, number]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Context a fence renderer receives beside the raw source and React key. */
|
|
37
|
+
export interface GenuiFenceContext {
|
|
38
|
+
/** Owning session route; absent outside a session-scoped render. */
|
|
39
|
+
readonly sessionId?: string
|
|
40
|
+
/** Present only for settled/interrupted renders with a stable identity. */
|
|
41
|
+
readonly source?: GenuiFenceSource
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const FENCE_ERROR_STYLE: CSSProperties = {
|
|
45
|
+
margin: '0 0 6px',
|
|
46
|
+
padding: '6px 10px',
|
|
47
|
+
borderRadius: 6,
|
|
48
|
+
background: 'rgba(239, 68, 68, 0.14)',
|
|
49
|
+
border: '1px solid rgba(239, 68, 68, 0.4)',
|
|
50
|
+
color: '#f87171',
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
lineHeight: 1.55,
|
|
53
|
+
whiteSpace: 'pre-wrap',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fallback for a ```dsh-ui fence whose body has no finished component yet.
|
|
58
|
+
* Two very different situations land here and they must not be conflated:
|
|
59
|
+
*
|
|
60
|
+
* 1. **Streaming partial** — the reply is still being written and the JSON
|
|
61
|
+
* simply is not complete. The host marks the streaming message with
|
|
62
|
+
* `[data-streaming]` on the AssistantMarkdown root, which is an ancestor
|
|
63
|
+
* of every fence. While that marker is present, a plain code block is the
|
|
64
|
+
* correct rendering (partial JSON must never look like an error).
|
|
65
|
+
*
|
|
66
|
+
* 2. **Settled defect** — the message is finished but the body still does
|
|
67
|
+
* not parse as JSON (a malformed fence like a missing `}`). This used to
|
|
68
|
+
* fail silently: the fence degraded to a code block with no hint, and the
|
|
69
|
+
* author had no way to know the UI never rendered. Once the streaming
|
|
70
|
+
* marker is gone, surface a compact diagnostic with the parse position so
|
|
71
|
+
* the defect is visible instead of silent.
|
|
72
|
+
*/
|
|
73
|
+
function FenceFallback({ raw, fenceKey }: { raw: string; fenceKey: Key }) {
|
|
74
|
+
const ref = useRef<HTMLDivElement | null>(null)
|
|
75
|
+
const [settled, setSettled] = useState(false)
|
|
76
|
+
useLayoutEffect(() => {
|
|
77
|
+
const node = ref.current
|
|
78
|
+
if (node !== null && node.closest('[data-streaming]') === null) setSettled(true)
|
|
79
|
+
})
|
|
80
|
+
const diagnostic = settled && raw.trim() !== '' ? describeJsonFailure(raw) : null
|
|
81
|
+
return (
|
|
82
|
+
<div ref={ref}>
|
|
83
|
+
{diagnostic !== null && (
|
|
84
|
+
<div style={FENCE_ERROR_STYLE} role="alert">
|
|
85
|
+
⚠️ dsh-ui fence JSON 解析失败{diagnostic} —— 围栏保持为代码块;请让模型检查并修复 JSON 后重发。
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
<CodeBlock key={fenceKey} code={`${raw}\n`} lang="dsh-ui" />
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Keyed publisher for a settled `panel:true` fence: submits ONE panel
|
|
95
|
+
* operation from the host-provided stable source (id + order), in an
|
|
96
|
+
* effect — never inside the render function. StrictMode's duplicate effects
|
|
97
|
+
* are absorbed by the operation map's per-source dedup, so the panel folds
|
|
98
|
+
* and notifies exactly once per source. Renders nothing.
|
|
99
|
+
*/
|
|
100
|
+
function FencePanelPublisher({ sessionId, sourceId, order, spec }: {
|
|
101
|
+
sessionId: string
|
|
102
|
+
sourceId: string
|
|
103
|
+
order: readonly [number, number, number]
|
|
104
|
+
spec: GenuiSpec
|
|
105
|
+
}) {
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const status: PanelOperationStatus = applyPanelOperation(sessionId, {
|
|
108
|
+
sourceId,
|
|
109
|
+
order,
|
|
110
|
+
mode: spec.append === true ? 'append' : 'replace',
|
|
111
|
+
spec,
|
|
112
|
+
})
|
|
113
|
+
if (status === 'overflow') diagnosePanelBudget(sessionId, sourceId)
|
|
114
|
+
}, [sessionId, sourceId, order, spec])
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a raw fence body to a guarded spec.
|
|
120
|
+
*
|
|
121
|
+
* - Tier-1 repair (quote escape + trailing commas): safe at any time —
|
|
122
|
+
* adopted only when the whole body parses, so a still-growing streaming
|
|
123
|
+
* half keeps falling back to the code block, never flashing a banner.
|
|
124
|
+
* - Tier-2 completion (missing quotes/brackets): settled renders only —
|
|
125
|
+
* `context.source` exists exclusively once the message finished, so
|
|
126
|
+
* streaming halves are never completed early.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveGenuiSpec(raw: string, context?: GenuiFenceContext): GenuiSpec | null {
|
|
129
|
+
const parsed = parsePartialGenuiSpec(raw)
|
|
130
|
+
let spec = parsed === null ? null : repairGenuiSpec(parsed)
|
|
131
|
+
if (spec === null) {
|
|
132
|
+
const repaired = repairFenceJson(raw)
|
|
133
|
+
if (repaired !== null) {
|
|
134
|
+
const reparsed = parsePartialGenuiSpec(repaired.text)
|
|
135
|
+
spec = reparsed === null ? null : repairGenuiSpec(reparsed)
|
|
136
|
+
}
|
|
137
|
+
if (spec === null && context?.source !== undefined) {
|
|
138
|
+
const completed = completeFenceJson(raw)
|
|
139
|
+
if (completed !== null) {
|
|
140
|
+
const reparsed = parsePartialGenuiSpec(completed.text)
|
|
141
|
+
spec = reparsed === null ? null : repairGenuiSpec(reparsed)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return spec
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The inline GenuiBlock tree for a resolved non-panel spec. */
|
|
149
|
+
function renderInlineFence(key: Key, context: GenuiFenceContext | undefined, spec: GenuiSpec): ReactNode {
|
|
150
|
+
const sessionId = context?.sessionId
|
|
151
|
+
return (
|
|
152
|
+
// React key carries the stable source identity when present (atomic
|
|
153
|
+
// remount at streaming→settled), falling back to the document key.
|
|
154
|
+
// Repaired specs render SILENTLY: once the UI renders, no amber note
|
|
155
|
+
// tells the user something was wrong — only an unrecoverable body keeps
|
|
156
|
+
// the red diagnostic.
|
|
157
|
+
<ErrorBoundary key={context?.source?.id ?? key} label="该界面">
|
|
158
|
+
<GenuiBlock
|
|
159
|
+
spec={spec}
|
|
160
|
+
// v2.7 durable state: session + stable source + content fingerprint —
|
|
161
|
+
// replaying the same content restores answers/lock/field values; new
|
|
162
|
+
// content (换题, edited spec) gets a fresh key. Without a stable
|
|
163
|
+
// source (streaming / non-conversation surfaces) state is not
|
|
164
|
+
// persisted.
|
|
165
|
+
stateKey={sessionId === undefined
|
|
166
|
+
? undefined
|
|
167
|
+
: fenceStateKey(sessionId, context?.source?.id ?? String(key), JSON.stringify(spec))}
|
|
168
|
+
/>
|
|
169
|
+
</ErrorBoundary>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The resolved fence render for the DOM channel: `null` when the body is
|
|
175
|
+
* unrepairable (the stock code block stays visible), otherwise the panel
|
|
176
|
+
* publisher (`panel:true`; renders nothing in the flow — mounted as an empty
|
|
177
|
+
* root so the taken-over block is hidden) or the inline GenuiBlock tree.
|
|
178
|
+
* Shared verbatim by both channels.
|
|
179
|
+
*/
|
|
180
|
+
export function renderResolvedFenceNode(raw: string, key: Key, context?: GenuiFenceContext): ReactNode | null {
|
|
181
|
+
const spec = resolveGenuiSpec(raw, context)
|
|
182
|
+
if (spec === null) return null
|
|
183
|
+
if (spec.panel === true) {
|
|
184
|
+
// Publish only with a settled stable source — streaming/identity-less
|
|
185
|
+
// renders keep the panel untouched. Appends additionally gate on a
|
|
186
|
+
// complete body (a settled-but-malformed append never merges partial
|
|
187
|
+
// content).
|
|
188
|
+
if (context !== undefined && context.sessionId !== undefined && context.source !== undefined) {
|
|
189
|
+
if (spec.append === true && !isCompleteJson(raw)) return <Fragment key={key} />
|
|
190
|
+
return (
|
|
191
|
+
<FencePanelPublisher
|
|
192
|
+
key={key}
|
|
193
|
+
sessionId={context.sessionId}
|
|
194
|
+
sourceId={context.source.id}
|
|
195
|
+
order={context.source.order}
|
|
196
|
+
spec={spec}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
return <Fragment key={key} />
|
|
201
|
+
}
|
|
202
|
+
return renderInlineFence(key, context, spec)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Registry-channel fence renderer (contract hosts): like the resolved node,
|
|
207
|
+
* but an unrepairable body renders the fallback code block + settled
|
|
208
|
+
* diagnostic — the host replaced its own block with our output — and an
|
|
209
|
+
* unpublishable `panel:true` fence renders `null` (nothing in the flow).
|
|
210
|
+
*/
|
|
211
|
+
export function renderGenuiFence(raw: string, key: Key, context?: GenuiFenceContext): ReactNode {
|
|
212
|
+
const spec = resolveGenuiSpec(raw, context)
|
|
213
|
+
if (spec === null) return <FenceFallback key={key} fenceKey={key} raw={raw} />
|
|
214
|
+
if (spec.panel === true) {
|
|
215
|
+
if (context !== undefined && context.sessionId !== undefined && context.source !== undefined) {
|
|
216
|
+
if (spec.append === true && !isCompleteJson(raw)) return null
|
|
217
|
+
return (
|
|
218
|
+
<FencePanelPublisher
|
|
219
|
+
key={key}
|
|
220
|
+
sessionId={context.sessionId}
|
|
221
|
+
sourceId={context.source.id}
|
|
222
|
+
order={context.source.order}
|
|
223
|
+
spec={spec}
|
|
224
|
+
/>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
return renderInlineFence(key, context, spec)
|
|
230
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenUI gallery: one spec exercising every white-listed node type, used as
|
|
3
|
+
* the canonical full-vocabulary sample (tests render it and assert every
|
|
4
|
+
* component family appears; docs and demos reuse it as the "show everything"
|
|
5
|
+
* fence).
|
|
6
|
+
*/
|
|
7
|
+
import type { GenuiSpec } from './spec.ts'
|
|
8
|
+
|
|
9
|
+
/** A single spec covering all 40 node types in the vocabulary. */
|
|
10
|
+
export const gallerySpec: GenuiSpec = {
|
|
11
|
+
title: 'GenUI · 组件画廊',
|
|
12
|
+
gap: 14,
|
|
13
|
+
items: [
|
|
14
|
+
{ type: 'text', size: 'h1', content: '排版层级' },
|
|
15
|
+
{ type: 'text', size: 'h2', content: '二级标题' },
|
|
16
|
+
{ type: 'text', size: 'h3', content: '三级标题' },
|
|
17
|
+
{ type: 'text', size: 'body', content: '正文:组件白名单渲染,不经过任意 HTML 路径。' },
|
|
18
|
+
{ type: 'text', size: 'muted', content: '弱化文本' },
|
|
19
|
+
{ type: 'text', size: 'caption', content: '说明文字', center: true },
|
|
20
|
+
{ type: 'row', items: [
|
|
21
|
+
{ type: 'badge', label: '成功', tone: 'success' },
|
|
22
|
+
{ type: 'badge', label: '警告', tone: 'warn' },
|
|
23
|
+
{ type: 'badge', label: '危险', tone: 'danger' },
|
|
24
|
+
{ type: 'badge', label: '强调', tone: 'accent', icon: '★' },
|
|
25
|
+
{ type: 'avatar', name: 'Alice' },
|
|
26
|
+
{ type: 'avatar', name: 'Bob', color: '#3d9e8f' },
|
|
27
|
+
{ type: 'link', label: '详情链接' },
|
|
28
|
+
], wrap: true },
|
|
29
|
+
{ type: 'divider' },
|
|
30
|
+
{ type: 'audio', src: '/demo-audio.mp3', alt: '音频播放器演示' },
|
|
31
|
+
{ type: 'video', src: '/demo-video.mp4', alt: '视频播放器演示', poster: '/demo-video.jpg', aspectRatio: '16:9' },
|
|
32
|
+
{ type: 'grid', cols: 3, items: [
|
|
33
|
+
{ type: 'stat', label: 'CPU', value: '42%', delta: '+3.1%' },
|
|
34
|
+
{ type: 'stat', label: '内存', value: '6.8 GB', delta: '-1.2%' },
|
|
35
|
+
{ type: 'stat', label: '请求数', value: '128.4k' },
|
|
36
|
+
] },
|
|
37
|
+
{ type: 'progress', label: '训练进度', value: 72, valueLabel: '72%' },
|
|
38
|
+
{ type: 'card', title: '性能指标', items: [
|
|
39
|
+
// 表头可点击排序:数值感知(千分位 / k / 万 / % 都能正确比较),
|
|
40
|
+
// 数值列自动右对齐。
|
|
41
|
+
{ type: 'table', columns: ['指标', 'Q1', 'Q2', 'Q3'], rows: [
|
|
42
|
+
['延迟', 92, 87, 81], ['吞吐', '1.2k', '1.4k', '1.6k'], ['错误率', '0.3%', '0.2%', '0.1%'],
|
|
43
|
+
['营收', '3.5万', '4.1万', '5.2万'], ['注册', '1,234', '2,345', '3,456'],
|
|
44
|
+
] },
|
|
45
|
+
{ type: 'keyvalue', pairs: [
|
|
46
|
+
{ key: '版本', value: 'v0.1.0' }, { key: '环境', value: 'production' }, { key: '区域', value: 'cn-east' },
|
|
47
|
+
] },
|
|
48
|
+
] },
|
|
49
|
+
{ type: 'list', items: [
|
|
50
|
+
{ title: '标题项', desc: '带描述的列表项' },
|
|
51
|
+
'纯文本列表项',
|
|
52
|
+
{ title: '另一个标题' },
|
|
53
|
+
] },
|
|
54
|
+
{ type: 'chart', kind: 'bars', data: [
|
|
55
|
+
{ label: '一', value: 10 }, { label: '二', value: 20 }, { label: '三', value: 15 },
|
|
56
|
+
] },
|
|
57
|
+
{ type: 'chart', kind: 'line', data: [
|
|
58
|
+
{ label: '周一', value: 8 }, { label: '周二', value: 12 }, { label: '周三', value: 9 },
|
|
59
|
+
] },
|
|
60
|
+
{ type: 'chart', kind: 'donut', data: [
|
|
61
|
+
{ label: 'A', value: 30 }, { label: 'B', value: 70 },
|
|
62
|
+
] },
|
|
63
|
+
{ type: 'chart', data: [], series: [
|
|
64
|
+
{ label: '本月', data: [{ label: 'Q1', value: 3 }, { label: 'Q2', value: 5 }] },
|
|
65
|
+
{ label: '上月', data: [{ label: 'Q1', value: 2 }, { label: 'Q2', value: 4 }] },
|
|
66
|
+
] },
|
|
67
|
+
{ type: 'tabs', tabs: [
|
|
68
|
+
{ label: '概览', items: [{ type: 'text', content: '标签页一的内容' }] },
|
|
69
|
+
{ label: '明细', items: [{ type: 'list', items: ['明细 A', '明细 B'] }] },
|
|
70
|
+
{ label: '图表', items: [{ type: 'chart', kind: 'donut', data: [{ label: 'X', value: 40 }, { label: 'Y', value: 60 }] }] },
|
|
71
|
+
] },
|
|
72
|
+
{ type: 'col', gap: 8, items: [
|
|
73
|
+
{ type: 'button', label: '主按钮', tone: 'primary' },
|
|
74
|
+
{ type: 'button', label: '危险', tone: 'danger', small: true },
|
|
75
|
+
{ type: 'button', label: '成功', tone: 'success' },
|
|
76
|
+
{ type: 'button', label: '幽灵', tone: 'ghost', icon: '↗' },
|
|
77
|
+
] },
|
|
78
|
+
{ type: 'row', items: [
|
|
79
|
+
{ type: 'input', label: '名称', placeholder: '输入…' },
|
|
80
|
+
{ type: 'select', label: '环境', options: ['dev', 'staging', 'production'] },
|
|
81
|
+
], wrap: true },
|
|
82
|
+
{ type: 'row', items: [
|
|
83
|
+
{ type: 'checkbox', label: '自动保存', checked: true },
|
|
84
|
+
{ type: 'switch', label: '通知', checked: true },
|
|
85
|
+
{ type: 'radio', label: '主题', options: ['浅色', '深色', '跟随系统'] },
|
|
86
|
+
], wrap: true },
|
|
87
|
+
{ type: 'textarea', label: '备注', placeholder: '多行输入…', rows: 3 },
|
|
88
|
+
{ type: 'accordion', items: [
|
|
89
|
+
{ title: '第一项', items: [{ type: 'json', value: { ok: true, count: 3 } }] },
|
|
90
|
+
{ title: '第二项', items: [{ type: 'code', lang: 'ts', code: 'export const x = 1' }] },
|
|
91
|
+
] },
|
|
92
|
+
{ type: 'copy', label: '复制令牌', text: 'sk-1234567890' },
|
|
93
|
+
{ type: 'plot', title: '波动叠加', xMin: -6.28, xMax: 6.28, series: [
|
|
94
|
+
{ expr: 'sin(x)', label: 'sin(x)', color: '#4f8ef7' },
|
|
95
|
+
{ expr: '0.8*cos(x)', label: 'cos', color: '#3ecf8e' },
|
|
96
|
+
] },
|
|
97
|
+
{ type: 'callout', tone: 'info', title: '提示', content: '画廊覆盖全部组件词汇。' },
|
|
98
|
+
{ type: 'steps', current: 2, steps: [
|
|
99
|
+
{ title: '起草', desc: '写规格' }, { title: '渲染', desc: '画组件' }, { title: '验证', desc: '跑测试' },
|
|
100
|
+
] },
|
|
101
|
+
{ type: 'diff', diffs: [
|
|
102
|
+
{ path: 'a.ts', oldText: 'const x = 1', newText: 'const x = 2' },
|
|
103
|
+
] },
|
|
104
|
+
{ type: 'code', lang: 'json', code: '{"hello": "world"}' },
|
|
105
|
+
{ type: 'mermaid', code: 'graph TD\nA[模型] --> B[渲染器]\nB --> C[组件]' },
|
|
106
|
+
{ type: 'scene3d', title: '几何演示', ambient: 1, meshes: [
|
|
107
|
+
{ shape: 'box', color: '#4f8ef7', position: [-1.4, 0, 0], rotation: [0.5, 0.8, 0] },
|
|
108
|
+
{ shape: 'sphere', color: '#3ecf8e', position: [0, 0, 0] },
|
|
109
|
+
{ shape: 'cone', color: '#e0a458', position: [1.4, 0, 0] },
|
|
110
|
+
] },
|
|
111
|
+
{ type: 'timeline', items: [
|
|
112
|
+
{ title: '发布 v0.1', desc: '首个可用版本', time: '08-01' },
|
|
113
|
+
{ title: '事件循环', desc: 'action 回流', time: '08-08' },
|
|
114
|
+
] },
|
|
115
|
+
{ type: 'file-tree', items: [
|
|
116
|
+
{ name: 'src', type: 'dir', children: [
|
|
117
|
+
{ name: 'client', type: 'dir', children: [{ name: 'GenuiBlock.tsx', type: 'file' }] },
|
|
118
|
+
{ name: 'spec.ts', type: 'file' },
|
|
119
|
+
] },
|
|
120
|
+
{ name: 'README.md', type: 'file' },
|
|
121
|
+
] },
|
|
122
|
+
{ type: 'breadcrumb', items: ['首页', '组件', '画廊'] },
|
|
123
|
+
{ type: 'quiz', question: '1 + 1 = ?', id: 'gallery-q1', options: [
|
|
124
|
+
{ label: '1', feedback: '再想想' }, { label: '2', correct: true }, { label: '3' },
|
|
125
|
+
], explanation: '二进制里 1+1=10,十进制里是 2。' },
|
|
126
|
+
{ type: 'spacer' },
|
|
127
|
+
],
|
|
128
|
+
}
|