@jboltai/tokui 0.1.0

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.
@@ -0,0 +1,678 @@
1
+ /**
2
+ * TokUI 构建器模块
3
+ * 提供链式调用 API 来生成 TokUI DSL 字符串。
4
+ * 用于服务端构建 TokUI 内容,支持流式输出(toChunks)和一次性输出(toString)。
5
+ *
6
+ * 使用示例:
7
+ * const b = new TokUIBuilder();
8
+ * b.card({ tt: '标题' }).h2('内容').p('描述').end();
9
+ * const result = b.toString(); // '[card tt:标题][h2 内容][p 描述][/card]'
10
+ */
11
+ 'use strict';
12
+
13
+ class TokUIBuilder {
14
+ constructor() {
15
+ /** @type {string[]} TokUI DSL 片段数组 */
16
+ this.chunks = [];
17
+ /** @type {string[]} 容器栈,用于跟踪未关闭的 open 标签 */
18
+ this.stack = [];
19
+ }
20
+
21
+ /**
22
+ * 将属性对象序列化为 TokUI DSL 属性字符串
23
+ * - 布尔 true → 仅输出属性名
24
+ * - 值含空格 → 用引号包裹
25
+ * - false/undefined/null → 跳过
26
+ *
27
+ * @param {Object} attrs - 属性键值对
28
+ * @returns {string} 序列化后的属性字符串
29
+ */
30
+ static serializeAttrs(attrs) {
31
+ if (!attrs || typeof attrs !== 'object') return '';
32
+ return Object.entries(attrs)
33
+ .map(([k, v]) => {
34
+ if (v === true) return k;
35
+ if (v === false || v === undefined || v === null) return '';
36
+ let val = String(v);
37
+ // 转义值中的双引号
38
+ val = val.replace(/"/g, '\\"');
39
+ // 值含空格、引号、换行符或 ] 时用双引号包裹
40
+ if (val.includes(' ') || val.includes('"') || val.includes('\n') || val.includes(']')) {
41
+ return `${k}:"${val}"`;
42
+ }
43
+ return `${k}:${val}`;
44
+ })
45
+ .filter(Boolean)
46
+ .join(' ');
47
+ }
48
+
49
+ /**
50
+ * 把 chart 布局属性(影响渲染形态的)排到数据属性 d/tasks 之前,返回新顺序的属性对象。
51
+ * 原因:流式预览时 d/tasks 边到边长,parser 吐的半成品 attrs 只含当前已累积的键;
52
+ * 若 orient/stack/smooth/area 写在 d 之后,预览阶段看不到 → 先按默认布局画,等 ] 闭合
53
+ * 这些键到达才翻转(横向柱的"末尾突然转向"根因)。前置后,预览从头即最终布局,无翻转。
54
+ * 顺序:t(类型,恒首)→ 布局键 → 其余按原序。
55
+ */
56
+ static chartLayoutFirst(attrs) {
57
+ if (!attrs || typeof attrs !== 'object') return attrs;
58
+ var layoutKeys = ['t', 'orient', 'orientation', 'stack', 'stacked', 'smooth', 'area'];
59
+ var seen = Object.create(null);
60
+ var out = {};
61
+ layoutKeys.forEach(function (k) {
62
+ if (attrs[k] !== undefined && attrs[k] !== null) { out[k] = attrs[k]; seen[k] = true; }
63
+ });
64
+ Object.keys(attrs).forEach(function (k) {
65
+ if (!seen[k] && attrs[k] !== undefined && attrs[k] !== null) out[k] = attrs[k];
66
+ });
67
+ return out;
68
+ }
69
+
70
+ /**
71
+ * 生成自闭合标签(如 [h1 标题]、[hr])
72
+ * @param {string} type - 标签类型
73
+ * @param {string} [content] - 文本内容
74
+ * @param {Object} [attrs] - 属性对象
75
+ * @returns {TokUIBuilder} this(支持链式调用)
76
+ */
77
+ _selfClosing(type, content, attrs) {
78
+ const attrStr = TokUIBuilder.serializeAttrs(attrs);
79
+ const parts = [type];
80
+ if (attrStr) parts.push(attrStr);
81
+ if (content) {
82
+ const c = String(content);
83
+ // content 含 [ ] : 时用双引号包裹:
84
+ // - [ ] 字面方括号:parser 引号感知,括号作字面内容不误判为嵌套子标签
85
+ // (否则 [item 生成 [0,1) 浮点数] 的 [0 被当子标签 → 内容截断)
86
+ // - : 冒号:避免被 parser 误解析为 key:value 属性
87
+ if (c.includes('[') || c.includes(']') || c.includes(':')) {
88
+ parts.push('"' + c.replace(/"/g, '\\"') + '"');
89
+ } else {
90
+ parts.push(c);
91
+ }
92
+ }
93
+ this.chunks.push(`[${parts.join(' ')}]`);
94
+ return this;
95
+ }
96
+
97
+ /**
98
+ * 生成开标签(如 [card tt:信息]),并将类型压入栈
99
+ * @param {string} type - 标签类型
100
+ * @param {Object} [attrs] - 属性对象
101
+ * @returns {TokUIBuilder} this
102
+ */
103
+ _open(type, attrs) {
104
+ const attrStr = TokUIBuilder.serializeAttrs(attrs);
105
+ const tag = attrStr ? `${type} ${attrStr}` : type;
106
+ this.chunks.push(`[${tag}]`);
107
+ this.stack.push(type);
108
+ return this;
109
+ }
110
+
111
+ /**
112
+ * 关闭栈顶的容器标签(如 [/card])
113
+ * @returns {TokUIBuilder} this
114
+ */
115
+ end() {
116
+ if (!this.stack.length) return this;
117
+ const type = this.stack.pop();
118
+ this.chunks.push(`[/${type}]`);
119
+ return this;
120
+ }
121
+
122
+ /**
123
+ * 关闭所有未关闭的容器标签
124
+ * @returns {TokUIBuilder} this
125
+ */
126
+ endAll() {
127
+ while (this.stack.length) this.end();
128
+ return this;
129
+ }
130
+
131
+ // ========== 展示组件 ==========
132
+
133
+ /** 标题 h1 ~ h6 */
134
+ h1(content, attrs) { return this._selfClosing('h1', content, attrs); }
135
+ h2(content, attrs) { return this._selfClosing('h2', content, attrs); }
136
+ h3(content, attrs) { return this._selfClosing('h3', content, attrs); }
137
+ h4(content, attrs) { return this._selfClosing('h4', content, attrs); }
138
+ h5(content, attrs) { return this._selfClosing('h5', content, attrs); }
139
+ h6(content, attrs) { return this._selfClosing('h6', content, attrs); }
140
+
141
+ /** 段落 */
142
+ // p 双行为:有 content → 自闭合 [p 文本];无 content → 容器 [p]...[/p](嵌套内联 a/tag 等)
143
+ p(content, attrs) {
144
+ return content !== undefined && content !== null
145
+ ? this._selfClosing('p', content, attrs)
146
+ : this._open('p', attrs);
147
+ }
148
+
149
+ /** Markdown 内容(容器模式,保留换行) */
150
+ md(content) {
151
+ this.chunks.push(`[md]${content}[/md]`);
152
+ return this;
153
+ }
154
+
155
+ /** 分割线 */
156
+ hr() { this.chunks.push('[hr]'); return this; }
157
+
158
+ /** Divider 分割线 */
159
+ dv(attrs) { return this._selfClosing('dv', null, attrs); }
160
+
161
+ /** Tag 标签 */
162
+ tag(content, attrs) { return this._selfClosing('tag', content, attrs); }
163
+
164
+ /** Toggle 切换按钮(自闭合) */
165
+ toggle(attrs) { return this._selfClosing('toggle', null, attrs); }
166
+ /** Toggle Group 切换按钮组(容器) */
167
+ toggleGroup(attrs) { return this._open('toggle-group', attrs); }
168
+
169
+ /** 链接 */
170
+ a(attrs) { return this._selfClosing('a', null, attrs); }
171
+
172
+ /** 图片 */
173
+ img(attrs) { return this._selfClosing('img', null, attrs); }
174
+
175
+ /**
176
+ * 代码块(容器模式)
177
+ * 生成 [code lang:xxx]content[/code]
178
+ * @param {Object} attrs - 属性(如 { lang: 'js' })
179
+ * @param {string} [content] - 代码内容
180
+ */
181
+ code(attrs, content) {
182
+ this._open('code', attrs);
183
+ if (content) {
184
+ this.chunks.push(String(content));
185
+ return this.end();
186
+ }
187
+ return this;
188
+ }
189
+
190
+ // ========== 表格组件 ==========
191
+
192
+ /** 表格容器 */
193
+ table(attrs) { return this._open('table', attrs); }
194
+ /** 表头 */
195
+ /** 表头容器(无 cols 时为容器,有 cols 时自闭合)*/
196
+ thead(attrs) { return attrs && attrs.cols ? this._selfClosing('thead', null, attrs) : this._open('thead', attrs); }
197
+ /** @deprecated 表格列定义请使用 tcol() 或 theadCols(),col() 与布局列冲突 */
198
+ col(attrs) { return this._selfClosing('col', null, attrs); }
199
+ /** 表格列定义(简写形式) */
200
+ tcol(attrs) { return this._selfClosing('tcol', null, attrs); }
201
+ /** 表头简写:带 cols 字符串的自闭合形式 */
202
+ theadCols(cols) { return this._selfClosing('thead', null, { cols }); }
203
+ /** 表格主体 */
204
+ tbody() { return this._open('tbody'); }
205
+ /** 表格数据行,多个值用逗号拼接,含逗号的值自动加双引号 */
206
+ row(...values) {
207
+ const escaped = values.map(v => {
208
+ const s = String(v);
209
+ // 只对含逗号的 cell 加引号,含冒号的交给 _selfClosing 统一处理
210
+ if (s.includes(',')) return `"${s}"`;
211
+ return s;
212
+ });
213
+ return this._selfClosing('tr', escaped.join(','));
214
+ }
215
+
216
+ // ========== 表单组件 ==========
217
+
218
+ /** 表单容器 */
219
+ form(attrs) { return this._open('form', attrs); }
220
+ /** 输入框 */
221
+ input(attrs) { return this._selfClosing('input', null, attrs); }
222
+ /** 密码输入框 */
223
+ pwd(attrs) { return this._selfClosing('pwd', null, attrs); }
224
+ /** 按钮 */
225
+ btn(attrs) { return this._selfClosing('btn', null, attrs); }
226
+ /** 按钮组 */
227
+ btngroup(attrs) { return this._open('btngroup', attrs); }
228
+ /** 自定义选择器 */
229
+ picker(attrs) { return this._open('picker', attrs); }
230
+ /** 下拉选择框 */
231
+ select(attrs) { return this._open('select', attrs); }
232
+ /** 选项 */
233
+ opt(attrs) { return this._selfClosing('opt', null, attrs); }
234
+ /** 单选按钮组 */
235
+ radio(attrs) { return this._open('radio', attrs); }
236
+ /** 复选框 */
237
+ checkbox(attrs) { return this._selfClosing('checkbox', null, attrs); }
238
+ /** 开关组件(方法名 switcher 避开 JS 关键字) */
239
+ switcher(attrs) { return this._selfClosing('switch', null, attrs); }
240
+ /** 多行文本框 */
241
+ textarea(attrs) { return this._open('textarea', attrs); }
242
+ /** 容器内原始文本(用于 textarea/md/code 等容器组件) */
243
+ text(content) { this.chunks.push(String(content)); return this; }
244
+
245
+ // ========== 布局组件 ==========
246
+
247
+ /** 卡片(容器模式:需 .end() 关闭) */
248
+ card(attrs) { return this._open('card', attrs); }
249
+ /** 卡片(自闭合模式:tx 属性作为 body 文本) */
250
+ cardTx(title, text, attrs) {
251
+ const a = Object.assign({ tt: title, tx: text }, attrs);
252
+ return this._selfClosing('card', null, a);
253
+ }
254
+ /** 卡片页脚 */
255
+ ft(attrs) { return this._open('ft', attrs); }
256
+ /** 栅格行(使用 row_layout 避免与 table.row 冲突) */
257
+ row_layout(attrs) { return this._open('row', attrs); }
258
+ /** 栅格列 */
259
+ col_layout(attrs) { return this._open('col', attrs); }
260
+ /** 列表 */
261
+ list(attrs) { return this._open('list', attrs); }
262
+ /** 列表项(有内容自闭合 [item 文本],无内容开容器供嵌套子 list)。
263
+ * content 含字面 [ ] 时由 _selfClosing 自动包双引号(避免 [0 被误判嵌套子标签截断内容)。 */
264
+ // item 双行为:字符串 → 列表项内容 [item 文本];对象 → 属性 [item tx:.. clk:..](command-group 内命令项用)。
265
+ item(contentOrAttrs) {
266
+ if (contentOrAttrs && typeof contentOrAttrs === 'object') {
267
+ return this._selfClosing('item', null, contentOrAttrs);
268
+ }
269
+ return contentOrAttrs ? this._selfClosing('item', contentOrAttrs) : this._open('item');
270
+ }
271
+ /** 有序列表(ol 标签) */
272
+ ol(attrs) { return this._open('ol', attrs); }
273
+ /** 无序列表(ul 标签) */
274
+ ul(attrs) { return this._open('ul', attrs); }
275
+ /** 列表项(i 标签,有内容自闭合,无内容容器模式) */
276
+ i(content) { return content ? this._selfClosing('i', content) : this._open('i'); }
277
+
278
+ /** 多图容器 */
279
+ imgs(attrs) { return this._open('imgs', attrs); }
280
+
281
+ /** 时间轴容器 */
282
+ timeline(attrs) { return this._open('timeline', attrs); }
283
+ /** 时间轴子项 */
284
+ ti(content, attrs) { return this._selfClosing('ti', content, attrs); }
285
+
286
+ /** Callout 提示框(自闭合) */
287
+ callout(attrs) { return this._selfClosing('callout', null, attrs); }
288
+ /** Think 思考块(容器) */
289
+ think(attrs) { return this._open('think', attrs); }
290
+ /** ThoughtChain 推理链容器 */
291
+ thinkChain(attrs) { return this._open('think-chain', attrs); }
292
+ /** ThoughtChain 推理步骤(容器) */
293
+ thinkStep(attrs) { return this._open('think-step', attrs); }
294
+ /** Copy 复制按钮(自闭合) */
295
+ copy(attrs) { return this._selfClosing('copy', null, attrs); }
296
+ /** Spin 加载指示器(自闭合) */
297
+ spin(attrs) { return this._selfClosing('spin', null, attrs); }
298
+ /** Thumb 点赞/点踩(自闭合) */
299
+ thumb(attrs) { return this._selfClosing('thumb', null, attrs); }
300
+ /** File 文件卡片(自闭合) */
301
+ file(attrs) { return this._selfClosing('file', null, attrs); }
302
+ /** Chart 图表:有 d/tasks 内联数据→自闭合;gauge 带内联 v 也自闭合(单值完整);
303
+ * 否则容器模式收 pt/task/ms 流式子节点(含 gauge 无 v + pt 流式)*/
304
+ chart(attrs) {
305
+ var a = attrs || {};
306
+ // hasInline 判定:凡带内联数据载体的 chart 均自闭合。
307
+ // d=柱/折/面积/饼/雷达/散点/气泡/直方/瀑布/箱线/K线/树图; tasks=甘特;
308
+ // rows=热力; nodes+flows=桑基; v=仪表盘/进度条(单值)。
309
+ // 漏判会让本应自闭合的 chart 走容器分支,.end() 错位闭合 → 后续栅格结构串味乱套。
310
+ var hasInline = a.d || a.tasks || a.rows || (a.nodes && a.flows) ||
311
+ (a.v !== undefined && (a.t === 'gauge' || a.t === 'progress'));
312
+ // 布局属性前置:流式预览阶段 d/tasks 边到边长,若 orient/stack/smooth/area 排在数据后,
313
+ // parser 半成品看不到 → 预览用默认布局,] 闭合才翻转(如 orient:h 末尾到达致纵向→横向闪)。
314
+ // 排到 d 前输出,预览从头即正确布局,消除中途翻转。
315
+ var ordered = TokUIBuilder.chartLayoutFirst(attrs);
316
+ return hasInline && !a._container
317
+ ? this._selfClosing('chart', null, ordered)
318
+ : this._open('chart', ordered);
319
+ }
320
+ /** 图表数据点(chart 容器内):attrs {v},scatter 用 v:"x,y" / treemap 用 v:"名:值" */
321
+ chartPoint(attrs) { return this._selfClosing('pt', null, attrs); }
322
+ /** 热力图行(heatmap 容器内):attrs {v} = "v,v,v" 一行 */
323
+ heatmapRow(attrs) { return this._selfClosing('hrow', null, attrs); }
324
+ /** 桑基图流(sankey 容器内):attrs {v} = "源->目标:值" */
325
+ sankeyFlow(attrs) { return this._selfClosing('flow', null, attrs); }
326
+ /** 甘特图任务(gantt 容器内):attrs {n,s,e,p,g} = 名称,开始,结束,进度,组 */
327
+ ganttTask(attrs) {
328
+ var a = attrs || {};
329
+ var content = [a.n, a.s, a.e, a.p, a.g].map(function (x) {
330
+ return x === undefined || x === null ? '' : String(x);
331
+ }).join(',');
332
+ // name 含空格时整段加引号(parser 引号还原后 chartAppendChild 会 strip 首尾)
333
+ if (/\s/.test(content)) {
334
+ this.chunks.push('[task "' + content.replace(/"/g, '\\"') + '"]');
335
+ } else {
336
+ this.chunks.push('[task ' + content + ']');
337
+ }
338
+ return this;
339
+ }
340
+ /** 甘特图里程碑(gantt 容器内):attrs {n,t,g} = 名称,时间,组 */
341
+ ganttMs(attrs) {
342
+ var a = attrs || {};
343
+ var content = [a.n, a.t, a.g].map(function (x) {
344
+ return x === undefined || x === null ? '' : String(x);
345
+ }).join(',');
346
+ if (/\s/.test(content)) {
347
+ this.chunks.push('[ms "' + content.replace(/"/g, '\\"') + '"]');
348
+ } else {
349
+ this.chunks.push('[ms ' + content + ']');
350
+ }
351
+ return this;
352
+ }
353
+
354
+ /** Empty 空状态(自闭合) */
355
+ empty(attrs) { return this._selfClosing('empty', null, attrs); }
356
+ /** Result 结果页(自闭合) */
357
+ result(attrs) { return this._selfClosing('result', null, attrs); }
358
+ /** Stat 统计数值(自闭合) */
359
+ stat(attrs) { return this._selfClosing('stat', null, attrs); }
360
+ /** Description List 描述列表(容器) */
361
+ desc(attrs) { return this._open('desc', attrs); }
362
+ /** Carousel 轮播图容器 */
363
+ carousel(attrs) { return this._open('carousel', attrs); }
364
+ /** Carousel 轮播图子项(自闭合) */
365
+ carouselItem(attrs) { return this._selfClosing('carousel-item', null, attrs); }
366
+ /** Description Item 描述项(自闭合) */
367
+ descItem(attrs) { return this._selfClosing('desc-item', null, attrs); }
368
+ /** Number Input 数字输入框(自闭合) */
369
+ numinput(attrs) { return this._selfClosing('numinput', null, attrs); }
370
+
371
+ /** DatePicker 日期选择器(自闭合) */
372
+ datepicker(attrs) { return this._selfClosing('datepicker', null, attrs); }
373
+ /** TimePicker 时间选择器(自闭合) */
374
+ timepicker(attrs) { return this._selfClosing('timepicker', null, attrs); }
375
+ /** DateTimePicker 日期时间选择器(自闭合) */
376
+ datetimepicker(attrs) { return this._selfClosing('datetimepicker', null, attrs); }
377
+
378
+ /** Popconfirm 确认气泡(自闭合) */
379
+ popconfirm(attrs, text) {
380
+ return this._selfClosing('popconfirm', text || null, attrs);
381
+ }
382
+
383
+ /** Notification 全局通知(自闭合) */
384
+ notification(attrs) { return this._selfClosing('notification', null, attrs); }
385
+
386
+ /** Popover 气泡卡片(容器) */
387
+ popover(attrs) { return this._open('popover', attrs); }
388
+ /** Hover Card 悬浮卡片(容器) */
389
+ hoverCard(attrs) { return this._open('hover-card', attrs); }
390
+ /** Hover Card 触发器(容器) */
391
+ hoverTrigger(attrs) { return this._open('hover-trigger', attrs); }
392
+ /** Hover Card 内容区(容器) */
393
+ hoverContent(attrs) { return this._open('hover-content', attrs); }
394
+ /** InputTag 标签输入框(容器,有 tags 初始值时自闭合) */
395
+ inputTag(attrs) { return attrs && attrs.tags ? this._selfClosing('input-tag', null, attrs) : this._open('input-tag', attrs); }
396
+ /** Countdown 倒计时(自闭合) */
397
+ countdown(attrs) { return this._selfClosing('countdown', null, attrs); }
398
+
399
+ /** Progress 进度条(自闭合) */
400
+ progress(attrs) { return this._selfClosing('progress', null, attrs); }
401
+ /** Steps 步骤条容器 */
402
+ steps(attrs) { return this._open('steps', attrs); }
403
+ /** Step 步骤项(自闭合) */
404
+ step(content, attrs) { return this._selfClosing('step', content, attrs); }
405
+
406
+ // ========== 动态更新 ==========
407
+
408
+ /** Upd 异步更新指令(自闭合),推送状态更新到已有组件 */
409
+ upd(attrs) { return this._selfClosing('upd', null, attrs); }
410
+
411
+ // ========== 交互组件 ==========
412
+
413
+ /** 标签页容器 */
414
+ tabs(attrs) { return this._open('tabs', attrs); }
415
+ /** 标签页 */
416
+ tab(attrs) { return this._open('tab', attrs); }
417
+ /** 手风琴 */
418
+ accordion(attrs) { return this._open('accordion', attrs); }
419
+ /** 折叠面板 */
420
+ collapse(attrs) { return this._open('collapse', attrs); }
421
+ /** 对话框 */
422
+ dialog(attrs) { return this._open('dialog', attrs); }
423
+ /** 抽屉 */
424
+ drawer(attrs) { return this._open('drawer', attrs); }
425
+ /** 命令面板(容器) */
426
+ command(attrs) { return this._open('command', attrs); }
427
+ /** 命令分组(容器) */
428
+ commandGroup(attrs) { return this._open('command-group', attrs); }
429
+ /** 命令项(自闭合) */
430
+ commandItem(attrs) { return this._selfClosing('command-item', null, attrs); }
431
+
432
+ // ========== AI 对话组件 ==========
433
+
434
+ /** 聊天气泡(容器) */
435
+ bubble(attrs) { return this._open('bubble', attrs); }
436
+ /** 操作栏(容器) */
437
+ toolbar(attrs) { return this._open('toolbar', attrs); }
438
+ /** 徽标数(自闭合) */
439
+ badge(attrs) {
440
+ const content = (attrs && attrs.tx) || null;
441
+ const cleanAttrs = Object.assign({}, attrs);
442
+ delete cleanAttrs.tx;
443
+ return this._selfClosing('badge', content, cleanAttrs);
444
+ }
445
+ /** 徽标数包裹容器(容器模式,子元素右上角显示徽标) */
446
+ badgeBox(attrs) { return this._open('badge-box', attrs); }
447
+ /** 骨架屏(自闭合) */
448
+ skeleton(attrs) { return this._selfClosing('skeleton', null, attrs); }
449
+ /** 轻提示(自闭合) */
450
+ toast(attrs) { return this._selfClosing('toast', null, attrs); }
451
+ /** 状态指示点(自闭合) */
452
+ dot(attrs) { return this._selfClosing('dot', null, attrs); }
453
+ /** 头像(自闭合) */
454
+ avatar(attrs) { return this._selfClosing('avatar', null, attrs); }
455
+ /** 悬浮提示(自闭合) */
456
+ tooltip(content, attrs) { return this._selfClosing('tooltip', content, attrs); }
457
+ /** 分页(自闭合) */
458
+ pagination(attrs) { return this._selfClosing('pagination', null, attrs); }
459
+ /** 面包屑 */
460
+ breadcrumb(attrs) { return this._selfClosing('breadcrumb', null, attrs); }
461
+ /** 下拉菜单(容器) */
462
+ dropdown(attrs) { return this._open('dropdown', attrs); }
463
+ /** 下拉菜单项(自闭合) */
464
+ ddItem(attrs) { return this._selfClosing('dd-item', null, attrs); }
465
+
466
+ /** 滑块(自闭合) */
467
+ slider(attrs) { return this._selfClosing('slider', null, attrs); }
468
+ /** 评分(自闭合) */
469
+ rate(attrs) { return this._selfClosing('rate', null, attrs); }
470
+ /** 穿梭框(容器) */
471
+ transfer(attrs) { return this._open('transfer', attrs); }
472
+ /** 级联选择器(容器) */
473
+ cascader(attrs) { return this._open('cascader', attrs); }
474
+ /** 文件上传(自闭合) */
475
+ upload(attrs) { return this._selfClosing('upload', null, attrs); }
476
+ /** 树形控件(容器) */
477
+ tree(attrs) { return this._open('tree', attrs); }
478
+ /** 树节点(容器);leaf 节点自闭合 */
479
+ tn(attrs) {
480
+ if (attrs && attrs.leaf) return this._selfClosing('tn', null, attrs);
481
+ return this._open('tn', attrs);
482
+ }
483
+
484
+ /** 水印容器 */
485
+ watermark(attrs) { return this._open('watermark', attrs); }
486
+
487
+ /** Scroll Area 自定义滚动区域(容器) */
488
+ scrollArea(attrs) { return this._open('scroll-area', attrs); }
489
+
490
+ /** 回到顶部按钮 */
491
+ backtop(attrs) { return this._selfClosing('backtop', null, attrs); }
492
+
493
+ /** 日历容器 */
494
+ calendar(attrs) { return this._selfClosing('calendar', null, attrs); }
495
+
496
+ /** 菜单容器 */
497
+ menu(attrs) { return this._open('menu', attrs); }
498
+
499
+ /** 对话输入框(容器模式,支持自定义子节点) */
500
+ chatInput(attrs) { return this._open('chat-input', attrs); }
501
+
502
+ /** Welcome 欢迎页容器 */
503
+ welcome(attrs) { return this._open('welcome', attrs); }
504
+ /** Welcome Feature 功能特性卡片(容器);推荐用 feature() 自闭合简写 */
505
+ welcomeFeature(attrs) { return this._open('welcome-feature', attrs); }
506
+ /** feature:welcome-feature 自闭合简写 */
507
+ feature(attrs) { return this._selfClosing('feature', null, attrs); }
508
+
509
+ /** 会话列表容器 */
510
+ conversations(attrs) { return this._open('conversations', attrs); }
511
+ /** 会话列表子项(自闭合) */
512
+ conv(attrs) { return this._selfClosing('conv', null, attrs); }
513
+
514
+ // ========== 附件组件 ==========
515
+
516
+ /** 附件区域容器 */
517
+ attachments(attrs) { return this._open('attachments', attrs); }
518
+
519
+ /** 单个附件项(自闭合) */
520
+ attach(attrs) { return this._selfClosing('attach', null, attrs); }
521
+
522
+ /** 消息操作栏(容器模式) */
523
+ msgActions(attrs) { return this._open('msg-actions', attrs); }
524
+
525
+ /** 菜单项(自闭合)*/
526
+ menuItem(attrs) { return this._selfClosing('menu-item', null, attrs); }
527
+
528
+ /** 侧边栏容器 */
529
+ sidebar(attrs) { return this._open('sidebar', attrs); }
530
+ /** 侧边栏内容区 */
531
+ sidebarContent(attrs) { return this._open('sidebar-content', attrs); }
532
+ /** 侧边栏页脚 */
533
+ sidebarFooter(attrs) { return this._open('sidebar-footer', attrs); }
534
+
535
+ // ========== AI 对话高级组件 ==========
536
+
537
+ // Phase 1: P0 核心AI组件
538
+
539
+ /** 工具调用卡片(容器) */
540
+ toolCall(attrs) { return this._open('tool-call', attrs); }
541
+
542
+ /** 打字指示器(自闭合) */
543
+ typing(attrs) { return this._selfClosing('typing', null, attrs); }
544
+
545
+ /** 快捷回复(自闭合 with items,或容器) */
546
+ quickReply(attrs) { return attrs && attrs.items ? this._selfClosing('quick-reply', null, attrs) : this._open('quick-reply', attrs); }
547
+
548
+ /** 提示建议卡片容器 */
549
+ suggestions(attrs) { return this._open('suggestions', attrs); }
550
+
551
+ /** 单个建议卡片(自闭合) */
552
+ suggestion(attrs) { return this._selfClosing('suggestion', null, attrs); }
553
+
554
+ /** 引用来源卡片(自闭合) */
555
+ source(attrs) { return this._selfClosing('source', null, attrs); }
556
+
557
+ /** 代码差异视图(容器) */
558
+ diff(attrs) { return this._open('diff', attrs); }
559
+
560
+ // Phase 2: P1 Agent/代码助手
561
+
562
+ /** 任务计划(容器) */
563
+ plan(attrs) { return this._open('plan', attrs); }
564
+
565
+ /** 计划步骤(自闭合或容器) */
566
+ planStep(attrs) { return this._selfClosing('plan-step', null, attrs); }
567
+
568
+ /** Agent 状态卡片(自闭合或容器) */
569
+ agent(attrs) { return attrs && (attrs.status || attrs.name) && !attrs._container ? this._selfClosing('agent', null, attrs) : this._open('agent', attrs); }
570
+
571
+ /** 文件树(容器) */
572
+ fileTree(attrs) { return this._open('file-tree', attrs); }
573
+
574
+ /** 文件树文件夹(容器) */
575
+ ftFolder(attrs) { return this._open('ft-folder', attrs); }
576
+
577
+ /** 文件树文件(自闭合) */
578
+ ftFile(attrs) { return this._selfClosing('ft-file', null, attrs); }
579
+
580
+ /** 终端输出(容器) */
581
+ terminal(attrs) { return this._open('terminal', attrs); }
582
+
583
+ /** 流式闪光加载(自闭合) */
584
+ shimmer(attrs) { return this._selfClosing('shimmer', null, attrs); }
585
+
586
+ /** 耗时标记(自闭合) */
587
+ latency(attrs) { return this._selfClosing('latency', null, attrs); }
588
+
589
+ // Phase 3: P2 高级组件
590
+
591
+ /** 视频播放器(自闭合) */
592
+ video(attrs) { return this._selfClosing('video', null, attrs); }
593
+
594
+ /** 音频播放器(自闭合) */
595
+ audio(attrs) { return this._selfClosing('audio', null, attrs); }
596
+
597
+ /** 消息引用(容器) */
598
+ quote(attrs) { return this._open('quote', attrs); }
599
+
600
+ /** 代码预览沙盒(容器) */
601
+ sandbox(attrs) { return this._open('sandbox', attrs); }
602
+
603
+ /** Git 提交信息(自闭合) */
604
+ commit(attrs) { return this._selfClosing('commit', null, attrs); }
605
+
606
+ /** 测试结果(容器) */
607
+ testResult(attrs) { return this._open('test-result', attrs); }
608
+
609
+ /** 测试用例(自闭合);case 为 test-case 简写别名 */
610
+ testCase(attrs) { return this._selfClosing('test-case', null, attrs); }
611
+ case(attrs) { return this._selfClosing('case', null, attrs); }
612
+
613
+ // ========== Artifact / Canvas 侧边预览 ==========
614
+
615
+ /** Artifact 侧边预览面板(容器) */
616
+ artifact(attrs) { return this._open('artifact', attrs); }
617
+
618
+ /** Artifact 代码区(容器) */
619
+ artifactCode(attrs) { return this._open('artifact-code', attrs); }
620
+
621
+ /** Artifact 预览区(容器) */
622
+ artifactPreview(attrs) { return this._open('artifact-preview', attrs); }
623
+
624
+ /** Resizable 分割面板(容器) */
625
+ resizable(attrs) { return this._open('resizable', attrs); }
626
+
627
+ /** Canvas 侧边预览面板(容器) */
628
+ canvas(attrs) { return this._open('canvas', attrs); }
629
+
630
+ /** Canvas 内容区(容器) */
631
+ canvasContent(attrs) { return this._open('canvas-content', attrs); }
632
+
633
+ // ========== 输出方法 ==========
634
+
635
+ /**
636
+ * 构建完整的 chunks 数组(包含自动关闭的容器标签)
637
+ * @returns {string[]} DSL 片段数组
638
+ * @private
639
+ */
640
+ _finalizeChunks() {
641
+ const copy = [...this.chunks];
642
+ const stackCopy = [...this.stack];
643
+ while (stackCopy.length) {
644
+ copy.push(`[/${stackCopy.pop()}]`);
645
+ }
646
+ return copy;
647
+ }
648
+
649
+ /**
650
+ * 输出完整的 TokUI DSL 字符串
651
+ * 自动关闭所有未关闭的容器。
652
+ * @returns {string} TokUI DSL 字符串
653
+ */
654
+ toString() {
655
+ return this._finalizeChunks().join('');
656
+ }
657
+
658
+ /**
659
+ * 输出 TokUI DSL 片段数组(用于 SSE 流式传输)
660
+ * 每个元素对应一个独立的 TokUI 标签。
661
+ * @returns {string[]} DSL 片段数组
662
+ */
663
+ toChunks() {
664
+ return this._finalizeChunks();
665
+ }
666
+
667
+ /**
668
+ * 重置构建器状态
669
+ * @returns {TokUIBuilder} this
670
+ */
671
+ reset() {
672
+ this.chunks = [];
673
+ this.stack = [];
674
+ return this;
675
+ }
676
+ }
677
+
678
+ module.exports = { TokUIBuilder };