@bolloon/bolloon-agent 0.3.6 → 0.3.8
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 +31 -0
- package/dist/cli/loading-tui.js +386 -33
- package/dist/cli-entry.js +5 -10
- package/dist/electron/main.js +16 -0
- package/dist/electron/main.js.map +1 -1
- package/dist/index.js +101 -22
- package/dist/social/agent-heartbeat.js +457 -0
- package/dist/utils/auto-update.js +292 -112
- package/dist/utils/auto-update.js.map +1 -0
- package/dist/web/client.js +6 -1
- package/dist/web/server.js +166 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,6 +129,37 @@ bolloon --help # 所有命令
|
|
|
129
129
|
|
|
130
130
|
详细打包 / 桌面 / iOS 流程见 [docs/BUILD.md](docs/BUILD.md).
|
|
131
131
|
|
|
132
|
+
### 从 npm / GitHub 安装(终端用户)
|
|
133
|
+
|
|
134
|
+
Bolloon 已发布到 npm,也可通过 GitHub 一键脚本安装(无需克隆仓库)。
|
|
135
|
+
|
|
136
|
+
**方式一:npm(各系统通用,需先装 Node.js LTS)**
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install -g @bolloon/bolloon-agent
|
|
140
|
+
bolloon --version
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**方式二:GitHub 一键脚本(macOS / Linux)**
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
curl -fsSL https://raw.githubusercontent.com/logos-42/bolloon/master/scripts/install.sh | sh
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
脚本优先从 GitHub Releases 下载预编译包,未提供对应平台包时自动回退 `npm install -g`。
|
|
150
|
+
|
|
151
|
+
**各系统安装 Node.js(若尚未安装)**
|
|
152
|
+
|
|
153
|
+
| 系统 | 命令 |
|
|
154
|
+
|------|------|
|
|
155
|
+
| macOS(Homebrew) | `brew install node` |
|
|
156
|
+
| Linux(apt) | `sudo apt update && sudo apt install -y nodejs npm` |
|
|
157
|
+
| Linux(dnf / yum) | `sudo dnf install -y nodejs npm` |
|
|
158
|
+
| Windows(winget) | `winget install -e --id OpenJS.NodeJS.LTS` |
|
|
159
|
+
| Windows(Chocolatey) | `choco install nodejs` |
|
|
160
|
+
|
|
161
|
+
> 说明:bolloon 暂未上架 apt / yum / brew / winget / choco 原生仓库,统一经 npm 安装;上表命令仅用于安装 Node.js 运行时。
|
|
162
|
+
|
|
132
163
|
---
|
|
133
164
|
|
|
134
165
|
## 一、项目概述
|
package/dist/cli/loading-tui.js
CHANGED
|
@@ -1,35 +1,403 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* loading-tui.ts — Bolloon Agent 终端视觉层 + 启动仪表盘 (纯 ANSI, 无外部依赖)
|
|
3
|
+
*
|
|
4
|
+
* 品牌要素 (与 TUI 一起维护):
|
|
5
|
+
* - BOLLOON_ICON : 顶部带圆标注的 "0" (气球造型)
|
|
6
|
+
* - BOLLOON_BANNER : "BOLLOON" / "AGENT" block 字体艺术字
|
|
7
|
+
*
|
|
8
|
+
* 导出构件:
|
|
9
|
+
* printBanner(v?) 一次性打印完整品牌 banner (图标 + 艺术字 + 版本/标语)
|
|
10
|
+
* renderDashboard(opts) 带品牌边框的仪表盘 (艺术字在框内)
|
|
11
|
+
* renderDialog(opts) 带品牌边框的对话框 (艺术字在框内)
|
|
12
|
+
* LoadingTUI 原地刷新的启动仪表盘类
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
const RESET = '\x1b[0m';
|
|
18
|
+
const BOLD = '\x1b[1m';
|
|
4
19
|
const CYAN = '\x1b[36m';
|
|
5
20
|
const YELLOW = '\x1b[33m';
|
|
6
21
|
const GREEN = '\x1b[32m';
|
|
7
22
|
const RED = '\x1b[31m';
|
|
8
23
|
const GRAY = '\x1b[90m';
|
|
9
|
-
const
|
|
10
|
-
const
|
|
24
|
+
const WHITE = '\x1b[37m';
|
|
25
|
+
const HIDE = '\x1b[?25l';
|
|
26
|
+
const SHOW = '\x1b[?25h';
|
|
27
|
+
// 版本信息 — 从 package.json 读取, 不再硬编码
|
|
28
|
+
function getPackageVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const pkgPath = path.resolve(here, '..', '..', 'package.json');
|
|
32
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version || '0.0.0';
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return '0.0.0';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const BOLLOON_VERSION = getPackageVersion();
|
|
39
|
+
// ── 品牌图标: 顶部带圆标注的 0 (气球) ──────────────
|
|
40
|
+
export const BOLLOON_ICON = [
|
|
41
|
+
`${CYAN} ✦${RESET}`,
|
|
42
|
+
`${CYAN} ╱ ╲${RESET}`,
|
|
43
|
+
`${CYAN} ════◆════${RESET}`,
|
|
44
|
+
`${CYAN} ╲ ╱${RESET}`,
|
|
45
|
+
`${CYAN} ╲${RESET}`,
|
|
46
|
+
`${CYAN} ✦ ╲${RESET}`,
|
|
47
|
+
].join('\n');
|
|
48
|
+
// ── 艺术字: BOLLOON (box 字体) + Bolloon Agent 副标题 ──
|
|
49
|
+
export const BOLLOON_BANNER = [
|
|
50
|
+
`${WHITE}${BOLD}██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ███╗ ██╗${RESET}`,
|
|
51
|
+
`${WHITE}${BOLD}██╔══██╗██╔═══██╗██║ ██║ ██╔═══██╗██╔═══██╗████╗ ██║${RESET}`,
|
|
52
|
+
`${WHITE}${BOLD}██████╔╝██║ ██║██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║${RESET}`,
|
|
53
|
+
`${WHITE}${BOLD}██╔══██╗██║ ██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║${RESET}`,
|
|
54
|
+
`${WHITE}${BOLD}██████╔╝╚██████╔╝███████╗███████╗╚██████╔╝╚██████╔╝██║ ╚████║${RESET}`,
|
|
55
|
+
`${WHITE}${BOLD}╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝${RESET}`,
|
|
56
|
+
`${GRAY}Bolloon Agent v${BOLLOON_VERSION}${RESET}`,
|
|
57
|
+
].join('\n');
|
|
58
|
+
/** 艺术字全部行 (图标在左, BOLLOON 艺术字在右), 供框内渲染 */
|
|
59
|
+
function brandArtLines() {
|
|
60
|
+
const icon = BOLLOON_ICON.split('\n');
|
|
61
|
+
const banner = BOLLOON_BANNER.split('\n');
|
|
62
|
+
const gap = 2;
|
|
63
|
+
const iconW = Math.max(1, ...icon.map(l => dispWidth(l)));
|
|
64
|
+
const rows = [];
|
|
65
|
+
const n = Math.max(icon.length, banner.length);
|
|
66
|
+
for (let i = 0; i < n; i++) {
|
|
67
|
+
const il = icon[i] ?? '';
|
|
68
|
+
const bl = banner[i] ?? '';
|
|
69
|
+
const pad = Math.max(0, iconW - dispWidth(il));
|
|
70
|
+
rows.push(il + ' '.repeat(pad + gap) + bl);
|
|
71
|
+
}
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
74
|
+
export function printBanner(version) {
|
|
75
|
+
console.log(BOLLOON_ICON);
|
|
76
|
+
console.log(BOLLOON_BANNER);
|
|
77
|
+
if (version)
|
|
78
|
+
console.log(`${GRAY} Bolloon Agent v${version}${RESET}`);
|
|
79
|
+
console.log(`${GRAY} P2P AI Agent · 文档智能体${RESET}`);
|
|
80
|
+
console.log('');
|
|
81
|
+
}
|
|
82
|
+
// ── 状态图标 ───────────────────────────────────────
|
|
83
|
+
export const STATUS_SYMBOL = {
|
|
11
84
|
pending: `${GRAY}○${RESET}`,
|
|
12
85
|
active: `${YELLOW}⠹${RESET}`,
|
|
13
86
|
ok: `${GREEN}✓${RESET}`,
|
|
14
87
|
warn: `${YELLOW}⚠${RESET}`,
|
|
15
88
|
error: `${RED}✗${RESET}`,
|
|
89
|
+
info: `${CYAN}●${RESET}`,
|
|
16
90
|
};
|
|
91
|
+
// 方角 (启动仪表盘 / 对话框 / 引用框)
|
|
92
|
+
const SQ = { tl: '┌', tr: '┐', bl: '└', br: '┘', v: '│', h: '─' };
|
|
93
|
+
// 圆角 (工具调用显示 / 智能体回复内容)
|
|
94
|
+
const RD = { tl: '╭', tr: '╮', bl: '╰', br: '╯', v: '│', h: '─' };
|
|
95
|
+
export function termWidth() {
|
|
96
|
+
const c = process.stdout.columns;
|
|
97
|
+
return typeof c === 'number' && c > 24 ? c : 80;
|
|
98
|
+
}
|
|
99
|
+
function stripAnsi(s) {
|
|
100
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
101
|
+
}
|
|
102
|
+
/** 显示宽度: CJK / 全角符号算 2, 其余算 1 (ANSI 转义不计入) */
|
|
103
|
+
function dispWidth(s) {
|
|
104
|
+
const clean = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
105
|
+
let n = 0;
|
|
106
|
+
for (const ch of clean) {
|
|
107
|
+
const c = ch.codePointAt(0);
|
|
108
|
+
const wide = c > 0x1100 &&
|
|
109
|
+
(c <= 0x115f ||
|
|
110
|
+
c === 0x2329 ||
|
|
111
|
+
c === 0x232a ||
|
|
112
|
+
(c >= 0x2e80 && c <= 0xa4cf) ||
|
|
113
|
+
(c >= 0xac00 && c <= 0xd7a3) ||
|
|
114
|
+
(c >= 0xf900 && c <= 0xfaff) ||
|
|
115
|
+
(c >= 0xfe30 && c <= 0xfe4f) ||
|
|
116
|
+
(c >= 0xff00 && c <= 0xff60) ||
|
|
117
|
+
(c >= 0xffe0 && c <= 0xffe6));
|
|
118
|
+
n += wide ? 2 : 1;
|
|
119
|
+
}
|
|
120
|
+
return n;
|
|
121
|
+
}
|
|
122
|
+
function centerAnsi(text, inner) {
|
|
123
|
+
const vis = dispWidth(text);
|
|
124
|
+
const pad = Math.max(0, inner - vis);
|
|
125
|
+
const left = Math.floor(pad / 2);
|
|
126
|
+
const right = pad - left;
|
|
127
|
+
return ' '.repeat(left) + text + ' '.repeat(right);
|
|
128
|
+
}
|
|
129
|
+
/** 左侧对齐并按显示宽度填充/截断到 inner */
|
|
130
|
+
function fitLeft(text, inner) {
|
|
131
|
+
const vis = dispWidth(text);
|
|
132
|
+
if (vis <= inner)
|
|
133
|
+
return text + ' '.repeat(inner - vis);
|
|
134
|
+
let out = '';
|
|
135
|
+
let w = 0;
|
|
136
|
+
for (const ch of text) {
|
|
137
|
+
const cw = dispWidth(ch);
|
|
138
|
+
if (w + cw > inner - 1)
|
|
139
|
+
break;
|
|
140
|
+
out += ch;
|
|
141
|
+
w += cw;
|
|
142
|
+
}
|
|
143
|
+
return out + '…';
|
|
144
|
+
}
|
|
145
|
+
/** 按显示宽度截断并加省略号 */
|
|
146
|
+
function truncate(text, max) {
|
|
147
|
+
if (dispWidth(text) <= max)
|
|
148
|
+
return text;
|
|
149
|
+
let out = '';
|
|
150
|
+
let w = 0;
|
|
151
|
+
for (const ch of text) {
|
|
152
|
+
const cw = dispWidth(ch);
|
|
153
|
+
if (w + cw > max - 1)
|
|
154
|
+
break;
|
|
155
|
+
out += ch;
|
|
156
|
+
w += cw;
|
|
157
|
+
}
|
|
158
|
+
return out + '…';
|
|
159
|
+
}
|
|
160
|
+
export function boxTop(title, width, corners = SQ) {
|
|
161
|
+
const inner = Math.max(0, width - 2);
|
|
162
|
+
let t = title ? ` ${title} ` : '';
|
|
163
|
+
if (dispWidth(t) > inner)
|
|
164
|
+
t = truncate(t, inner);
|
|
165
|
+
const pad = Math.max(0, inner - dispWidth(t));
|
|
166
|
+
const left = Math.floor(pad / 2);
|
|
167
|
+
const right = pad - left;
|
|
168
|
+
return corners.tl + corners.h.repeat(left) + t + corners.h.repeat(right) + corners.tr;
|
|
169
|
+
}
|
|
170
|
+
export function boxRow(content, width, align = 'left', corners = SQ) {
|
|
171
|
+
const inner = Math.max(0, width - 4);
|
|
172
|
+
const body = align === 'center' ? centerAnsi(content, inner) : fitLeft(content, inner);
|
|
173
|
+
return corners.v + ' ' + body + ' ' + corners.v;
|
|
174
|
+
}
|
|
175
|
+
export function boxBottom(width, corners = SQ) {
|
|
176
|
+
return corners.bl + corners.h.repeat(Math.max(0, width - 2)) + corners.br;
|
|
177
|
+
}
|
|
178
|
+
export function renderDashboard(opts) {
|
|
179
|
+
const showBrand = opts.brand !== false;
|
|
180
|
+
const art = showBrand ? brandArtLines() : [];
|
|
181
|
+
const maxArt = art.reduce((m, l) => Math.max(m, dispWidth(l)), 0);
|
|
182
|
+
const maxRow = opts.rows.reduce((m, r) => Math.max(m, dispWidth(r.label) + (r.detail ? dispWidth(r.detail) + 2 : 0) + 4), 0);
|
|
183
|
+
const maxTitle = dispWidth(opts.title ?? 'Bolloon Agent · 仪表盘') + 4;
|
|
184
|
+
const inner = Math.max(40, maxArt, maxRow, maxTitle);
|
|
185
|
+
const width = Math.min(termWidth() - 2, opts.width ?? inner + 4);
|
|
186
|
+
const lines = [];
|
|
187
|
+
lines.push(boxTop(opts.title ?? 'Bolloon Agent · 仪表盘', width));
|
|
188
|
+
if (showBrand) {
|
|
189
|
+
for (const l of art)
|
|
190
|
+
lines.push(boxRow(l, width, 'center'));
|
|
191
|
+
}
|
|
192
|
+
for (const r of opts.rows) {
|
|
193
|
+
const sym = STATUS_SYMBOL[r.status ?? 'info'];
|
|
194
|
+
const detail = r.detail ? ` ${GRAY}${r.detail}${RESET}` : '';
|
|
195
|
+
lines.push(boxRow(`${sym} ${r.label}${detail}`, width));
|
|
196
|
+
}
|
|
197
|
+
lines.push(boxBottom(width));
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
export function renderDialog(opts) {
|
|
201
|
+
const art = brandArtLines();
|
|
202
|
+
const maxArt = art.reduce((m, l) => Math.max(m, dispWidth(l)), 0);
|
|
203
|
+
const inner = Math.max(40, maxArt, dispWidth(opts.prompt) + 4, dispWidth(opts.title ?? 'Bolloon Agent') + 4);
|
|
204
|
+
const width = Math.min(termWidth() - 2, opts.width ?? inner + 4);
|
|
205
|
+
const lines = [];
|
|
206
|
+
lines.push(boxTop(opts.title ?? 'Bolloon Agent', width));
|
|
207
|
+
for (const l of art)
|
|
208
|
+
lines.push(boxRow(l, width, 'center'));
|
|
209
|
+
lines.push(boxRow(opts.prompt, width));
|
|
210
|
+
lines.push(boxBottom(width));
|
|
211
|
+
return lines.join('\n');
|
|
212
|
+
}
|
|
213
|
+
// ── 消息对话框 (聊天流: 已发送 / 智能体回复) ───────
|
|
214
|
+
/** 按显示宽度折行 (保留 ANSI, 优先在空格处断行; 超长无空格片段按字符硬断) */
|
|
215
|
+
function wrapText(text, width) {
|
|
216
|
+
if (width <= 0)
|
|
217
|
+
return text.split('\n');
|
|
218
|
+
const out = [];
|
|
219
|
+
for (const rawLine of text.split('\n')) {
|
|
220
|
+
if (dispWidth(rawLine) <= width) {
|
|
221
|
+
out.push(rawLine);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const words = rawLine.split(/(\s+)/);
|
|
225
|
+
let cur = '';
|
|
226
|
+
const flush = () => {
|
|
227
|
+
if (cur) {
|
|
228
|
+
out.push(cur);
|
|
229
|
+
cur = '';
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
for (const w of words) {
|
|
233
|
+
if (w.length === 0)
|
|
234
|
+
continue;
|
|
235
|
+
const wv = dispWidth(w);
|
|
236
|
+
if (wv > width) {
|
|
237
|
+
// 超长无空格片段 (如中文长句) → 按字符硬断
|
|
238
|
+
flush();
|
|
239
|
+
let chunk = '';
|
|
240
|
+
for (const ch of w) {
|
|
241
|
+
const cv = dispWidth(ch);
|
|
242
|
+
if (dispWidth(chunk) + cv > width && dispWidth(chunk) > 0) {
|
|
243
|
+
out.push(chunk);
|
|
244
|
+
chunk = ch;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
chunk += ch;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
cur = chunk;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (dispWidth(cur) + wv > width && dispWidth(cur) > 0) {
|
|
254
|
+
flush();
|
|
255
|
+
cur = w.replace(/^\s+/, '');
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
cur += w;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
flush();
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
/** 对话框默认显示上限 (按高度截断) */
|
|
266
|
+
const DEFAULT_MAX_LINES = 14;
|
|
267
|
+
export function renderMessageBox(opts) {
|
|
268
|
+
const color = opts.color ?? CYAN;
|
|
269
|
+
const title = opts.title ?? 'Bolloon Agent';
|
|
270
|
+
const maxLines = opts.maxLines && opts.maxLines > 0 ? opts.maxLines : 0;
|
|
271
|
+
const bodyLines = wrapText(opts.body, 1000);
|
|
272
|
+
// 压缩: 用「引用」框代替被压缩的内容 (仅影响显示, 原文仍发给 LLM)
|
|
273
|
+
if (maxLines > 0 && bodyLines.length > maxLines) {
|
|
274
|
+
return renderReference({ title, body: opts.body, color, hidden: bodyLines.length, width: opts.width });
|
|
275
|
+
}
|
|
276
|
+
const maxLine = bodyLines.reduce((m, l) => Math.max(m, dispWidth(l)), 0);
|
|
277
|
+
const inner = Math.max(20, dispWidth(title) + 4, maxLine);
|
|
278
|
+
const width = Math.min(termWidth() - 2, opts.width ?? inner + 4);
|
|
279
|
+
const lines = [];
|
|
280
|
+
lines.push(boxTop(`${color}${title}${RESET}`, width, RD));
|
|
281
|
+
for (const l of wrapText(opts.body, width - 4))
|
|
282
|
+
lines.push(boxRow(l, width, 'left', RD));
|
|
283
|
+
lines.push(boxBottom(width, RD));
|
|
284
|
+
return lines.join('\n');
|
|
285
|
+
}
|
|
286
|
+
/** 取首条非空行作为预览 (按可见宽度截断, 加省略号) */
|
|
287
|
+
function firstLinePreview(text, width) {
|
|
288
|
+
for (const raw of text.split('\n')) {
|
|
289
|
+
const t = raw.trim();
|
|
290
|
+
if (t)
|
|
291
|
+
return truncate(t, width);
|
|
292
|
+
}
|
|
293
|
+
return '';
|
|
294
|
+
}
|
|
295
|
+
/** 压缩后的「引用」框: 代替被压缩的正文 */
|
|
296
|
+
function renderReference(opts) {
|
|
297
|
+
const preview = firstLinePreview(opts.body, 60);
|
|
298
|
+
const inner = Math.max(20, dispWidth(opts.title) + 8, dispWidth(`已压缩 ${opts.hidden} 行 · 完整内容已发送给智能体`), dispWidth(preview) + 2);
|
|
299
|
+
const width = Math.min(termWidth() - 2, opts.width ?? inner + 4);
|
|
300
|
+
const lines = [];
|
|
301
|
+
lines.push(boxTop(`${GRAY}引用${RESET} ${opts.color}${opts.title}${RESET}`, width, RD));
|
|
302
|
+
lines.push(boxRow(`${GRAY}已压缩 ${opts.hidden} 行 · 完整内容已发送给智能体${RESET}`, width, 'left', RD));
|
|
303
|
+
if (preview)
|
|
304
|
+
lines.push(boxRow(`${GRAY}▏ ${preview}${RESET}`, width, 'left', RD));
|
|
305
|
+
lines.push(boxBottom(width, RD));
|
|
306
|
+
return lines.join('\n');
|
|
307
|
+
}
|
|
308
|
+
/** 已发送消息框 (用户输入) */
|
|
309
|
+
export function renderUserMessage(body) {
|
|
310
|
+
return renderMessageBox({ title: '✓ 已发送', body, color: GREEN, maxLines: DEFAULT_MAX_LINES });
|
|
311
|
+
}
|
|
312
|
+
/** 智能体回复框 */
|
|
313
|
+
export function renderAgentMessage(body) {
|
|
314
|
+
return renderMessageBox({ title: '◉ Bolloon Agent', body, color: CYAN, maxLines: DEFAULT_MAX_LINES });
|
|
315
|
+
}
|
|
316
|
+
/** 循环工作流连接线: 用 ╼ ╾ 串联相邻工具框 */
|
|
317
|
+
export function flowConnector(width) {
|
|
318
|
+
const unit = '╼ ╾ ';
|
|
319
|
+
let s = '';
|
|
320
|
+
while (s.length < width)
|
|
321
|
+
s += unit;
|
|
322
|
+
return s.slice(0, width);
|
|
323
|
+
}
|
|
324
|
+
/** 渲染单个工具调用为圆角框 (参数 / 状态 / 输出预览) */
|
|
325
|
+
export function renderToolCall(v) {
|
|
326
|
+
const color = v.status === 'ok' ? GREEN : RED;
|
|
327
|
+
const sym = v.status === 'ok' ? '✅' : '❌';
|
|
328
|
+
const w = Math.min(termWidth() - 2, v.width ?? 72);
|
|
329
|
+
const rows = [];
|
|
330
|
+
const argStr = typeof v.args === 'string' ? v.args : v.args ? JSON.stringify(v.args) : '';
|
|
331
|
+
if (argStr)
|
|
332
|
+
rows.push(`参数: ${truncate(argStr, w - 10)}`);
|
|
333
|
+
const dur = v.durationMs != null ? ` (${v.durationMs}ms)` : '';
|
|
334
|
+
rows.push(`状态: ${sym} ${v.status === 'ok' ? '成功' : '失败'}${dur}`);
|
|
335
|
+
const body = v.status === 'ok' ? v.output : v.error;
|
|
336
|
+
if (body) {
|
|
337
|
+
const wrapped = wrapText(body, w - 6);
|
|
338
|
+
const shown = wrapped.slice(0, 3);
|
|
339
|
+
for (const l of shown)
|
|
340
|
+
rows.push(`▏ ${l}`);
|
|
341
|
+
if (wrapped.length > 3)
|
|
342
|
+
rows.push(`▏ … 已压缩 ${wrapped.length - 3} 行`);
|
|
343
|
+
}
|
|
344
|
+
const lines = [];
|
|
345
|
+
lines.push(boxTop(`${color}◉ ${v.tool}${RESET}`, w, RD));
|
|
346
|
+
for (const l of rows)
|
|
347
|
+
lines.push(boxRow(l, w, 'left', RD));
|
|
348
|
+
lines.push(boxBottom(w, RD));
|
|
349
|
+
return lines.join('\n');
|
|
350
|
+
}
|
|
351
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
17
352
|
export class LoadingTUI {
|
|
18
353
|
write;
|
|
19
354
|
timer = null;
|
|
20
|
-
frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
21
355
|
frameIdx = 0;
|
|
22
356
|
steps = [];
|
|
23
357
|
currentLabel = 'Bolloon loading...';
|
|
24
|
-
lastRenderedStepCount = 0;
|
|
25
358
|
finished = false;
|
|
26
359
|
ok = true;
|
|
360
|
+
width = 0;
|
|
27
361
|
constructor() {
|
|
28
362
|
this.write = process.stdout.write.bind(process.stdout);
|
|
29
363
|
}
|
|
364
|
+
computeWidth() {
|
|
365
|
+
const cols = process.stdout.columns;
|
|
366
|
+
const maxCols = cols && cols > 30 ? cols - 2 : 60;
|
|
367
|
+
const artW = brandArtLines().reduce((m, l) => Math.max(m, dispWidth(l)), 0);
|
|
368
|
+
const needed = Math.max(40, artW, ...this.steps.map(s => s.label.length + 8), this.currentLabel.length + 6);
|
|
369
|
+
return Math.min(maxCols, needed + 4);
|
|
370
|
+
}
|
|
371
|
+
/** 仪表盘区域总行数: 上边框 + 艺术字 + steps + spinner + 下边框 */
|
|
372
|
+
regionLines() {
|
|
373
|
+
return 1 + brandArtLines().length + this.steps.length + 1 + 1;
|
|
374
|
+
}
|
|
375
|
+
draw(showSpinner) {
|
|
376
|
+
const w = this.width || (this.width = this.computeWidth());
|
|
377
|
+
const out = [];
|
|
378
|
+
out.push(boxTop('Bolloon Agent · 启动仪表盘', w));
|
|
379
|
+
for (const l of brandArtLines())
|
|
380
|
+
out.push(boxRow(l, w, 'center'));
|
|
381
|
+
for (const step of this.steps) {
|
|
382
|
+
out.push(boxRow(`${STATUS_SYMBOL[step.status]} ${step.label}`, w));
|
|
383
|
+
}
|
|
384
|
+
if (showSpinner) {
|
|
385
|
+
const sp = YELLOW + FRAMES[this.frameIdx % FRAMES.length] + RESET;
|
|
386
|
+
out.push(boxRow(`${sp} ${this.currentLabel}`, w));
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
out.push(boxRow(`${STATUS_SYMBOL.ok} Bolloon ready`, w));
|
|
390
|
+
}
|
|
391
|
+
out.push(boxBottom(w));
|
|
392
|
+
this.write(out.join('\n'));
|
|
393
|
+
// 回退到仪表盘上边框, 下次 draw 原地覆盖
|
|
394
|
+
this.write(`\x1b[${this.regionLines()}A`);
|
|
395
|
+
}
|
|
30
396
|
setSteps(steps) {
|
|
31
397
|
this.steps = steps.map(label => ({ label, status: 'pending' }));
|
|
32
|
-
this.
|
|
398
|
+
this.width = 0;
|
|
399
|
+
if (this.timer)
|
|
400
|
+
this.draw(true);
|
|
33
401
|
}
|
|
34
402
|
startStep(index, label) {
|
|
35
403
|
if (index < 0 || index >= this.steps.length)
|
|
@@ -37,7 +405,8 @@ export class LoadingTUI {
|
|
|
37
405
|
this.steps[index].status = 'active';
|
|
38
406
|
if (label !== undefined)
|
|
39
407
|
this.steps[index].label = label;
|
|
40
|
-
this.
|
|
408
|
+
if (this.timer)
|
|
409
|
+
this.draw(true);
|
|
41
410
|
}
|
|
42
411
|
completeStep(index, status = 'ok', label) {
|
|
43
412
|
if (index < 0 || index >= this.steps.length)
|
|
@@ -45,10 +414,13 @@ export class LoadingTUI {
|
|
|
45
414
|
this.steps[index].status = status;
|
|
46
415
|
if (label !== undefined)
|
|
47
416
|
this.steps[index].label = label;
|
|
48
|
-
this.
|
|
417
|
+
if (this.timer)
|
|
418
|
+
this.draw(true);
|
|
49
419
|
}
|
|
50
420
|
setMessage(msg) {
|
|
51
421
|
this.currentLabel = msg;
|
|
422
|
+
if (this.timer)
|
|
423
|
+
this.draw(true);
|
|
52
424
|
}
|
|
53
425
|
start(msg = 'Bolloon loading...') {
|
|
54
426
|
if (this.timer)
|
|
@@ -58,21 +430,10 @@ export class LoadingTUI {
|
|
|
58
430
|
this.timer = setInterval(() => {
|
|
59
431
|
if (this.finished)
|
|
60
432
|
return;
|
|
61
|
-
this.
|
|
433
|
+
this.frameIdx++;
|
|
434
|
+
this.draw(true);
|
|
62
435
|
}, 100);
|
|
63
|
-
|
|
64
|
-
drawAll() {
|
|
65
|
-
if (!this.timer || this.finished)
|
|
66
|
-
return;
|
|
67
|
-
const out = [];
|
|
68
|
-
for (const step of this.steps) {
|
|
69
|
-
const prefix = step.status === 'active' ? YELLOW : '';
|
|
70
|
-
out.push(` ${STEP_SYMBOL[step.status]} ${prefix}${step.label}${RESET}\n`);
|
|
71
|
-
}
|
|
72
|
-
this.lastRenderedStepCount = this.steps.length;
|
|
73
|
-
this.write(out.join(''));
|
|
74
|
-
this.write(`\x1b[${this.steps.length}A`);
|
|
75
|
-
this.write(`\r${CLEAR}\r ${YELLOW}${this.frames[this.frameIdx % this.frames.length]}${RESET} ${this.currentLabel}`);
|
|
436
|
+
this.draw(true);
|
|
76
437
|
}
|
|
77
438
|
stop(ok = true) {
|
|
78
439
|
this.finished = true;
|
|
@@ -81,15 +442,7 @@ export class LoadingTUI {
|
|
|
81
442
|
clearInterval(this.timer);
|
|
82
443
|
this.timer = null;
|
|
83
444
|
}
|
|
84
|
-
|
|
85
|
-
this.write(`\x1b[${this.lastRenderedStepCount}B`);
|
|
86
|
-
}
|
|
87
|
-
this.write(`\r${CLEAR}\r`);
|
|
88
|
-
if (this.steps.length > 0) {
|
|
89
|
-
for (const step of this.steps) {
|
|
90
|
-
this.write(` ${STEP_SYMBOL[step.status]} ${step.label}\n`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
445
|
+
this.draw(false);
|
|
93
446
|
this.write(SHOW);
|
|
94
447
|
}
|
|
95
448
|
isFinished() {
|
package/dist/cli-entry.js
CHANGED
|
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
|
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
|
+
import { printBanner } from './cli/loading-tui.js';
|
|
17
18
|
const isWindows = process.platform === 'win32';
|
|
18
19
|
// ANSI 颜色
|
|
19
20
|
const RESET = '\x1b[0m';
|
|
@@ -37,14 +38,8 @@ const VERSION = (() => {
|
|
|
37
38
|
function log(msg, color = RESET) {
|
|
38
39
|
console.log(`${color}${msg}${RESET}`);
|
|
39
40
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
${CYAN}${BOLD}
|
|
43
|
-
╔═══════════════════════════════════════════╗
|
|
44
|
-
║ 🤖 Bolloon Agent ${VERSION} ║
|
|
45
|
-
║ P2P AI Document Processor ║
|
|
46
|
-
╚═══════════════════════════════════════════╝${RESET}
|
|
47
|
-
`);
|
|
41
|
+
function printBannerCli() {
|
|
42
|
+
printBanner(VERSION);
|
|
48
43
|
}
|
|
49
44
|
function printHelp() {
|
|
50
45
|
console.log(`
|
|
@@ -232,11 +227,11 @@ async function main() {
|
|
|
232
227
|
printHelp();
|
|
233
228
|
break;
|
|
234
229
|
case 'gui':
|
|
235
|
-
|
|
230
|
+
printBannerCli();
|
|
236
231
|
await startElectron(args);
|
|
237
232
|
break;
|
|
238
233
|
case 'web':
|
|
239
|
-
|
|
234
|
+
printBannerCli();
|
|
240
235
|
await startWebServer(args);
|
|
241
236
|
break;
|
|
242
237
|
case 'cli':
|
package/dist/electron/main.js
CHANGED
|
@@ -14,6 +14,7 @@ const tray_1 = require("./tray");
|
|
|
14
14
|
const ipc_1 = require("./ipc");
|
|
15
15
|
const dialogs_1 = require("./dialogs");
|
|
16
16
|
const first_run_1 = require("./first-run");
|
|
17
|
+
const auto_update_js_1 = require("../utils/auto-update.js");
|
|
17
18
|
(0, logger_1.log)('Bolloon Electron 启动');
|
|
18
19
|
// 单实例锁
|
|
19
20
|
const gotTheLock = electron_1.app.requestSingleInstanceLock();
|
|
@@ -31,6 +32,21 @@ electron_1.app.whenReady().then(async () => {
|
|
|
31
32
|
(0, first_run_1.registerFirstRunIpc)();
|
|
32
33
|
(0, menu_1.installAppMenu)(window_1.getMainWindow);
|
|
33
34
|
(0, tray_1.createTray)(window_1.getMainWindow);
|
|
35
|
+
// 启动自动更新检查(后台,不阻塞 UI)。
|
|
36
|
+
// 安装成功后用 app.relaunch() 自动重启以应用新版本(避免单实例锁冲突)。
|
|
37
|
+
void (async () => {
|
|
38
|
+
try {
|
|
39
|
+
await (0, auto_update_js_1.checkAndUpdate)({
|
|
40
|
+
onUpdated: () => {
|
|
41
|
+
electron_1.app.relaunch();
|
|
42
|
+
electron_1.app.exit(0);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 自动更新失败不影响主程序
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
34
50
|
await (0, window_1.createMainWindow)();
|
|
35
51
|
// 首启引导 (在主窗口就绪后弹, 不阻塞 UI)
|
|
36
52
|
const win = (0, window_1.getMainWindow)();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/electron/main.ts"],"names":[],"mappings":";;AAAA;;;;GAIG;AACH,uCAA8C;AAC9C,qCAA+B;AAC/B,qCAA4E;AAC5E,qCAAyC;AACzC,iCAAwC;AACxC,iCAAiD;AACjD,+BAAwC;AACxC,uCAA8C;AAC9C,2CAAqE;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/electron/main.ts"],"names":[],"mappings":";;AAAA;;;;GAIG;AACH,uCAA8C;AAC9C,qCAA+B;AAC/B,qCAA4E;AAC5E,qCAAyC;AACzC,iCAAwC;AACxC,iCAAiD;AACjD,+BAAwC;AACxC,uCAA8C;AAC9C,2CAAqE;AACrE,4DAAyD;AAEzD,IAAA,YAAG,EAAC,qBAAqB,CAAC,CAAC;AAE3B,OAAO;AACP,MAAM,UAAU,GAAG,cAAG,CAAC,yBAAyB,EAAE,CAAC;AACnD,IAAI,CAAC,UAAU,EAAE,CAAC;IAChB,IAAA,YAAG,EAAC,YAAY,CAAC,CAAC;IAClB,cAAG,CAAC,IAAI,EAAE,CAAC;AACb,CAAC;AAED,cAAG,CAAC,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC7B,IAAA,wBAAe,GAAE,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,cAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;IAC9B,IAAA,YAAG,EAAC,cAAc,CAAC,CAAC;IACpB,IAAA,qBAAe,GAAE,CAAC;IAClB,IAAA,2BAAiB,GAAE,CAAC;IACpB,IAAA,+BAAmB,GAAE,CAAC;IACtB,IAAA,qBAAc,EAAC,sBAAa,CAAC,CAAC;IAC9B,IAAA,iBAAU,EAAC,sBAAa,CAAC,CAAC;IAE1B,uBAAuB;IACvB,8CAA8C;IAC9C,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC;YACH,MAAM,IAAA,+BAAc,EAAC;gBACnB,SAAS,EAAE,GAAG,EAAE;oBACd,cAAG,CAAC,QAAQ,EAAE,CAAC;oBACf,cAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACd,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,IAAA,yBAAgB,GAAE,CAAC;IAEzB,0BAA0B;IAC1B,MAAM,GAAG,GAAG,IAAA,sBAAa,GAAE,CAAC;IAC5B,IAAI,GAAG,EAAE,CAAC;QACR,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE;YAC3C,KAAK,IAAA,6BAAiB,EAAC,GAAG,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE;QAC5B,8BAA8B;QAC9B,IAAI,wBAAa,CAAC,aAAa,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAA,yBAAgB,GAAE,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,IAAA,YAAG,EAAC,SAAS,CAAC,CAAC;IACf,IAAA,kBAAW,GAAE,CAAC;IACd,IAAA,sBAAa,GAAE,CAAC;IAChB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,cAAG,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,cAAG,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;IACzB,IAAA,YAAG,EAAC,QAAQ,CAAC,CAAC;IACd,IAAA,kBAAW,GAAE,CAAC;IACd,IAAA,sBAAa,GAAE,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,IAAA,YAAG,EAAC,UAAU,CAAC,CAAC"}
|