@simon_he/vue-tui 0.0.3 → 0.0.5
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/README.md +137 -74
- package/dist/index.cjs +13121 -3617
- package/dist/index.d.ts +672 -77
- package/dist/index.js +12682 -4970
- package/dist/path-suggest-CZQYJIIg.js +473 -0
- package/dist/path-suggest-DIMby3r0.js +3 -0
- package/package.json +22 -17
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Vue Terminal
|
|
2
2
|
|
|
3
3
|
用 Vue 组件语法绘制和交互的终端渲染框架。目标是让 “终端” 具备:
|
|
4
|
+
|
|
4
5
|
- 类终端的写入/删除/更新/尺寸调整能力
|
|
5
6
|
- ANSI 样式渲染(颜色、加粗、下划线等)
|
|
6
7
|
- 事件机制(点击、移动、滚动、拖拽)
|
|
@@ -13,7 +14,13 @@
|
|
|
13
14
|
```vue
|
|
14
15
|
<script setup lang="ts">
|
|
15
16
|
import { computed, ref } from 'vue'
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
TerminalProvider,
|
|
19
|
+
TBox,
|
|
20
|
+
TInput,
|
|
21
|
+
TText,
|
|
22
|
+
useLayout,
|
|
23
|
+
} from '@simon_he/vue-tui'
|
|
17
24
|
|
|
18
25
|
const layout = useLayout()
|
|
19
26
|
const cols = computed(() => layout.clipRect?.w ?? 80)
|
|
@@ -22,8 +29,21 @@ const input = ref('')
|
|
|
22
29
|
</script>
|
|
23
30
|
|
|
24
31
|
<template>
|
|
25
|
-
<TerminalProvider
|
|
26
|
-
|
|
32
|
+
<TerminalProvider
|
|
33
|
+
:cols="80"
|
|
34
|
+
:rows="24"
|
|
35
|
+
:default-style="{ fg: 'whiteBright' }"
|
|
36
|
+
>
|
|
37
|
+
<TBox
|
|
38
|
+
:x="0"
|
|
39
|
+
:y="0"
|
|
40
|
+
:w="80"
|
|
41
|
+
:h="24"
|
|
42
|
+
border
|
|
43
|
+
title="Demo"
|
|
44
|
+
:padding="1"
|
|
45
|
+
:style="{ fg: 'blueBright' }"
|
|
46
|
+
>
|
|
27
47
|
<TText :x="0" :y="0" :w="78" :value="`cols=${cols} rows=${rows}`" />
|
|
28
48
|
<TInput :x="0" :y="20" :w="78" v-model="input" placeholder="Type..." />
|
|
29
49
|
</TBox>
|
|
@@ -31,6 +51,15 @@ const input = ref('')
|
|
|
31
51
|
</template>
|
|
32
52
|
```
|
|
33
53
|
|
|
54
|
+
## 组件文档与验收标准
|
|
55
|
+
|
|
56
|
+
- VitePress 文档站点(本地):`bun run --filter '@simon_he/vue-tui' docs:dev`(入口 `docs/index.md`)
|
|
57
|
+
- 组件使用文档:`docs/components.md`
|
|
58
|
+
- 组件 Props/Events(自动生成):`docs/generated/components-api.md`(脚本:`scripts/generate-component-api-docs.ts`)
|
|
59
|
+
- 组件验收标准(可信赖组件定义 + 测试范围):`docs/components-acceptance.md`
|
|
60
|
+
- Terminal 兼容性与颜色一致性:`docs/terminal-compatibility.md`
|
|
61
|
+
- 性能关注点与回归策略:`docs/performance.md`
|
|
62
|
+
|
|
34
63
|
### 终端(真实 CLI)渲染
|
|
35
64
|
|
|
36
65
|
内置 `vue-terminal run`,直接在真实终端中跑 UI(stdout renderer + stdin driver):
|
|
@@ -67,11 +96,13 @@ terminal 构建产物会生成 `dist-terminal/terminal.js`,可以直接作为
|
|
|
67
96
|
GoatChain 示例应用目前用的是 mock LLM,但你可以很快替换成任意 LLM SDK(OpenAI/Anthropic/本地模型)。
|
|
68
97
|
|
|
69
98
|
建议做法:
|
|
99
|
+
|
|
70
100
|
1. 用 ACP 风格的流式协议驱动 UI(支持 `text_delta` / `tool_call_delta` / `tool_call_end` / `tool_result` / `done`)。
|
|
71
101
|
2. tool_call 的执行可以交给 agent 侧(推荐,和 GoatChain CLI 一致),UI 只负责渲染与“暂停→继续”的用户输入。
|
|
72
102
|
3. 在 UI 侧维护 token 统计与 context window(真实 tokenizer 或近似估算均可)。
|
|
73
103
|
|
|
74
104
|
GoatChain 的核心协议定义在:
|
|
105
|
+
|
|
75
106
|
- `apps/goatchain/src/core/acp.ts`
|
|
76
107
|
- `apps/goatchain/src/core/model.ts`
|
|
77
108
|
- `apps/goatchain/src/core/store.ts`
|
|
@@ -118,23 +149,27 @@ DOM 事件 -> EventManager -> HitTest -> Vue 组件事件回调
|
|
|
118
149
|
|
|
119
150
|
```ts
|
|
120
151
|
type Style = {
|
|
121
|
-
fg?: string
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
fg?: string
|
|
153
|
+
bg?: string
|
|
154
|
+
bold?: boolean
|
|
155
|
+
dim?: boolean
|
|
156
|
+
italic?: boolean
|
|
157
|
+
underline?: boolean
|
|
158
|
+
inverse?: boolean
|
|
159
|
+
}
|
|
125
160
|
|
|
126
161
|
type Cell = {
|
|
127
|
-
ch: string
|
|
128
|
-
style: Style
|
|
129
|
-
width: 1 | 2
|
|
130
|
-
}
|
|
162
|
+
ch: string
|
|
163
|
+
style: Style
|
|
164
|
+
width: 1 | 2 // 宽字符支持
|
|
165
|
+
}
|
|
131
166
|
|
|
132
167
|
type Buffer = {
|
|
133
|
-
rows: Cell[][]
|
|
134
|
-
cols: number
|
|
135
|
-
rowsCount: number
|
|
136
|
-
dirtyRows: boolean[]
|
|
137
|
-
}
|
|
168
|
+
rows: Cell[][]
|
|
169
|
+
cols: number
|
|
170
|
+
rowsCount: number
|
|
171
|
+
dirtyRows: boolean[]
|
|
172
|
+
}
|
|
138
173
|
```
|
|
139
174
|
|
|
140
175
|
## 终端语义范围(初版约束)
|
|
@@ -155,18 +190,25 @@ type Buffer = {
|
|
|
155
190
|
|
|
156
191
|
```ts
|
|
157
192
|
interface Terminal {
|
|
158
|
-
resize(cols: number, rows: number): void
|
|
159
|
-
clear(x?: number, y?: number, w?: number, h?: number): void
|
|
160
|
-
write(text: string, opts?: { x?: number; y?: number; style?: Style }): void
|
|
161
|
-
writeAnsi(text: string, opts?: { x?: number; y?: number }): void
|
|
162
|
-
put(x: number, y: number, ch: string, style?: Style): void
|
|
163
|
-
fill(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
193
|
+
resize(cols: number, rows: number): void
|
|
194
|
+
clear(x?: number, y?: number, w?: number, h?: number): void
|
|
195
|
+
write(text: string, opts?: { x?: number; y?: number; style?: Style }): void
|
|
196
|
+
writeAnsi(text: string, opts?: { x?: number; y?: number }): void
|
|
197
|
+
put(x: number, y: number, ch: string, style?: Style): void
|
|
198
|
+
fill(
|
|
199
|
+
x: number,
|
|
200
|
+
y: number,
|
|
201
|
+
w: number,
|
|
202
|
+
h: number,
|
|
203
|
+
ch?: string,
|
|
204
|
+
style?: Style,
|
|
205
|
+
): void
|
|
206
|
+
scroll(lines: number): void
|
|
207
|
+
setCursor(x: number, y: number, visible?: boolean): void
|
|
208
|
+
batch(fn: () => void): void
|
|
209
|
+
commit(): void
|
|
210
|
+
on(event: TerminalEvent, cb: (e: TerminalEventPayload) => void): () => void
|
|
211
|
+
dispose(): void
|
|
170
212
|
}
|
|
171
213
|
```
|
|
172
214
|
|
|
@@ -178,52 +220,67 @@ interface Terminal {
|
|
|
178
220
|
|
|
179
221
|
```ts
|
|
180
222
|
type TerminalEvent =
|
|
181
|
-
| 'click'
|
|
182
|
-
| '
|
|
183
|
-
| '
|
|
223
|
+
| 'click'
|
|
224
|
+
| 'dblclick'
|
|
225
|
+
| 'contextmenu'
|
|
226
|
+
| 'pointerdown'
|
|
227
|
+
| 'pointerup'
|
|
228
|
+
| 'pointermove'
|
|
229
|
+
| 'pointerenter'
|
|
230
|
+
| 'pointerleave'
|
|
231
|
+
| 'mousedown'
|
|
232
|
+
| 'mouseup'
|
|
233
|
+
| 'mousemove'
|
|
234
|
+
| 'mouseenter'
|
|
235
|
+
| 'mouseleave'
|
|
184
236
|
| 'wheel'
|
|
185
|
-
| 'keydown'
|
|
186
|
-
| '
|
|
187
|
-
| '
|
|
188
|
-
| '
|
|
189
|
-
| '
|
|
237
|
+
| 'keydown'
|
|
238
|
+
| 'keyup'
|
|
239
|
+
| 'input'
|
|
240
|
+
| 'change'
|
|
241
|
+
| 'compositionstart'
|
|
242
|
+
| 'compositionupdate'
|
|
243
|
+
| 'compositionend'
|
|
244
|
+
| 'focus'
|
|
245
|
+
| 'blur'
|
|
246
|
+
| 'drag'
|
|
190
247
|
|
|
191
248
|
type TerminalBaseEvent = {
|
|
192
|
-
type: TerminalEvent
|
|
193
|
-
target: TerminalNode | null
|
|
194
|
-
currentTarget: TerminalNode | null
|
|
195
|
-
bubbles: boolean
|
|
196
|
-
cancelable: boolean
|
|
197
|
-
defaultPrevented: boolean
|
|
198
|
-
timeStamp: number
|
|
199
|
-
stopPropagation(): void
|
|
200
|
-
preventDefault(): void
|
|
201
|
-
nativeEvent?: Event
|
|
202
|
-
}
|
|
249
|
+
type: TerminalEvent
|
|
250
|
+
target: TerminalNode | null
|
|
251
|
+
currentTarget: TerminalNode | null
|
|
252
|
+
bubbles: boolean
|
|
253
|
+
cancelable: boolean
|
|
254
|
+
defaultPrevented: boolean
|
|
255
|
+
timeStamp: number
|
|
256
|
+
stopPropagation(): void
|
|
257
|
+
preventDefault(): void
|
|
258
|
+
nativeEvent?: Event
|
|
259
|
+
}
|
|
203
260
|
|
|
204
261
|
type TerminalPointerEvent = TerminalBaseEvent & {
|
|
205
|
-
clientX: number
|
|
206
|
-
clientY: number
|
|
207
|
-
cellX: number
|
|
208
|
-
cellY: number
|
|
209
|
-
button?: number
|
|
210
|
-
buttons?: number
|
|
211
|
-
ctrlKey?: boolean
|
|
212
|
-
shiftKey?: boolean
|
|
213
|
-
altKey?: boolean
|
|
214
|
-
metaKey?: boolean
|
|
215
|
-
deltaY?: number
|
|
216
|
-
}
|
|
262
|
+
clientX: number
|
|
263
|
+
clientY: number
|
|
264
|
+
cellX: number
|
|
265
|
+
cellY: number
|
|
266
|
+
button?: number
|
|
267
|
+
buttons?: number
|
|
268
|
+
ctrlKey?: boolean
|
|
269
|
+
shiftKey?: boolean
|
|
270
|
+
altKey?: boolean
|
|
271
|
+
metaKey?: boolean
|
|
272
|
+
deltaY?: number
|
|
273
|
+
}
|
|
217
274
|
|
|
218
275
|
type TerminalKeyboardEvent = TerminalBaseEvent & {
|
|
219
|
-
key: string
|
|
220
|
-
code: string
|
|
221
|
-
ctrlKey?: boolean
|
|
222
|
-
shiftKey?: boolean
|
|
223
|
-
altKey?: boolean
|
|
224
|
-
metaKey?: boolean
|
|
225
|
-
repeat?: boolean
|
|
226
|
-
}
|
|
276
|
+
key: string
|
|
277
|
+
code: string
|
|
278
|
+
ctrlKey?: boolean
|
|
279
|
+
shiftKey?: boolean
|
|
280
|
+
altKey?: boolean
|
|
281
|
+
metaKey?: boolean
|
|
282
|
+
repeat?: boolean
|
|
283
|
+
}
|
|
227
284
|
```
|
|
228
285
|
|
|
229
286
|
## 事件派发与冒泡(建议)
|
|
@@ -308,15 +365,19 @@ const { mount } = useTerminalRuntime()
|
|
|
308
365
|
function onInputCommit(value: string) {
|
|
309
366
|
if (value.startsWith('/')) {
|
|
310
367
|
mount(TSelect, {
|
|
311
|
-
x: 2,
|
|
368
|
+
x: 2,
|
|
369
|
+
y: 10,
|
|
370
|
+
w: 20,
|
|
371
|
+
h: 6,
|
|
312
372
|
options: ['a', 'b', 'c'],
|
|
313
|
-
onSelect: v => console.log(v),
|
|
373
|
+
onSelect: (v) => console.log(v),
|
|
314
374
|
})
|
|
315
375
|
}
|
|
316
376
|
}
|
|
317
377
|
```
|
|
318
378
|
|
|
319
379
|
约束:
|
|
380
|
+
|
|
320
381
|
- 动态插入的组件必须显式提供布局 (x/y/w/h) 或依赖父容器布局
|
|
321
382
|
- 插入/卸载会触发局部重绘,不影响其它区域
|
|
322
383
|
|
|
@@ -328,10 +389,10 @@ function onInputCommit(value: string) {
|
|
|
328
389
|
|
|
329
390
|
```ts
|
|
330
391
|
type TerminalHandle = {
|
|
331
|
-
update: (props: Record<string, unknown>) => void
|
|
332
|
-
move: (x: number, y: number) => void
|
|
333
|
-
unmount: () => void
|
|
334
|
-
}
|
|
392
|
+
update: (props: Record<string, unknown>) => void
|
|
393
|
+
move: (x: number, y: number) => void
|
|
394
|
+
unmount: () => void
|
|
395
|
+
}
|
|
335
396
|
```
|
|
336
397
|
|
|
337
398
|
## 组件生命周期与资源管理(建议)
|
|
@@ -341,6 +402,7 @@ type TerminalHandle = {
|
|
|
341
402
|
- 卸载:释放事件节点、清理 buffer 区域、移除焦点/输入绑定
|
|
342
403
|
|
|
343
404
|
对 `v-if`/`v-show` 的行为约定:
|
|
405
|
+
|
|
344
406
|
- `v-if`: 触发卸载与资源释放(清 buffer、解绑事件)
|
|
345
407
|
- `v-show`: 仅隐藏渲染,保留节点与状态(不解绑事件)
|
|
346
408
|
|
|
@@ -399,6 +461,7 @@ type TerminalHandle = {
|
|
|
399
461
|
## 带边框 Box 的内容保护
|
|
400
462
|
|
|
401
463
|
当 `TBox` 带边框时,需要保护内容区域不被破坏:
|
|
464
|
+
|
|
402
465
|
- 外框占 1 cell 边距,内容区域为 `(x+1, y+1, w-2, h-2)`
|
|
403
466
|
- 内容写入必须裁剪到内容区域,避免覆盖边框
|
|
404
467
|
- 当内容超出时,支持 `overflow: clip | scroll`
|
|
@@ -425,7 +488,7 @@ type TerminalHandle = {
|
|
|
425
488
|
## ANSI 支持
|
|
426
489
|
|
|
427
490
|
- `writeAnsi()` 接受 ANSI 字符串,解析后写入 Buffer
|
|
428
|
-
-
|
|
491
|
+
- 可先实现轻量解析器,后续切换到更完整的 ANSI 样式库(如 `ansis`)
|
|
429
492
|
|
|
430
493
|
## 文本分段与宽字符策略(建议)
|
|
431
494
|
|