@bolloon/bolloon-agent 0.1.11 → 0.1.13

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.
Files changed (42) hide show
  1. package/dist/agents/p2p-chat-tools.js +321 -0
  2. package/dist/agents/p2p-document-tools.js +121 -1
  3. package/dist/agents/workflow-pivot-loop.js +4 -4
  4. package/dist/cli-entry.js +1 -1
  5. package/dist/documents/reader.js +5 -0
  6. package/dist/documents/store.js +1 -1
  7. package/dist/llm/pi-ai.js +6 -5
  8. package/dist/network/iroh-discovery.js +2 -1
  9. package/dist/network/iroh-transport.js +15 -2
  10. package/dist/network/p2p.js +9 -8
  11. package/dist/network/storage/adapters/json-adapter.js +16 -1
  12. package/dist/network/storage/index.js +2 -1
  13. package/dist/pi-ecosystem-judgment/index.js +43 -115
  14. package/dist/social/channels/channel-heartbeat-agent.js +1 -1
  15. package/dist/utils/auto-update.js +15 -1
  16. package/dist/web/components/p2p/index.js +226 -264
  17. package/dist/web/index.html +12 -0
  18. package/package.json +3 -1
  19. package/scripts/build-web.ts +1 -1
  20. package/scripts/postinstall.js +1 -1
  21. package/src/agents/p2p-chat-tools.ts +383 -0
  22. package/src/agents/p2p-document-tools.ts +151 -1
  23. package/src/agents/workflow-pivot-loop.ts +13 -12
  24. package/src/bollharness-integration/channel-judgment-engine.ts +1 -1
  25. package/src/cli-entry.ts +1 -1
  26. package/src/documents/reader.ts +5 -0
  27. package/src/documents/store.ts +1 -1
  28. package/src/llm/pi-ai.ts +6 -5
  29. package/src/network/iroh-discovery.ts +2 -1
  30. package/src/network/iroh-transport.ts +15 -2
  31. package/src/network/p2p.ts +9 -8
  32. package/src/network/storage/adapters/json-adapter.ts +17 -2
  33. package/src/network/storage/index.ts +19 -3
  34. package/src/social/channels/channel-heartbeat-agent.ts +1 -1
  35. package/src/utils/auto-update.ts +17 -1
  36. package/src/web/server.ts +149 -0
  37. package/tsconfig.electron.json +1 -1
  38. package/tsconfig.json +1 -1
  39. package/dist/web/components/p2p/P2PModal.js +0 -188
  40. package/dist/web/components/p2p/p2p-modal.js +0 -657
  41. package/dist/web/components/p2p/p2p-tools.js +0 -248
  42. package/dist/web/server.js +0 -1890
@@ -1,19 +1,14 @@
1
- "use strict";
2
- /**
3
- * P2P Modal - 纯 TypeScript 版本
4
- */
5
1
  class P2PModalUI {
6
- modal = null;
7
- overlay = null;
8
- connectedPeers = new Map();
9
- constructor() {
10
- this.createModal();
11
- }
12
- createModal() {
13
- // 创建遮罩层
14
- this.overlay = document.createElement('div');
15
- this.overlay.className = 'p2p-modal-overlay';
16
- this.overlay.style.cssText = `
2
+ modal = null;
3
+ overlay = null;
4
+ connectedPeers = /* @__PURE__ */ new Map();
5
+ constructor() {
6
+ this.createModal();
7
+ }
8
+ createModal() {
9
+ this.overlay = document.createElement("div");
10
+ this.overlay.className = "p2p-modal-overlay";
11
+ this.overlay.style.cssText = `
17
12
  display: none;
18
13
  position: fixed;
19
14
  inset: 0;
@@ -22,10 +17,9 @@ class P2PModalUI {
22
17
  align-items: center;
23
18
  justify-content: center;
24
19
  `;
25
- // 创建弹窗
26
- this.modal = document.createElement('div');
27
- this.modal.className = 'p2p-modal';
28
- this.modal.style.cssText = `
20
+ this.modal = document.createElement("div");
21
+ this.modal.className = "p2p-modal";
22
+ this.modal.style.cssText = `
29
23
  width: 90%;
30
24
  max-width: 650px;
31
25
  max-height: 90vh;
@@ -36,81 +30,72 @@ class P2PModalUI {
36
30
  display: flex;
37
31
  flex-direction: column;
38
32
  `;
39
- this.overlay.appendChild(this.modal);
40
- document.body.appendChild(this.overlay);
41
- // 点击遮罩关闭
42
- this.overlay.addEventListener('click', (e) => {
43
- if (e.target === this.overlay)
44
- this.hide();
45
- });
46
- this.render();
47
- }
48
- async render() {
49
- if (!this.modal)
50
- return;
51
- // 加载连接历史
52
- await this.loadConnectionHistory();
53
- // 获取身份信息
54
- let identityHtml = '<div class="p2p-loading">加载中...</div>';
55
- let identityData = { name: '未知', did: '未知', cid: '未知' };
56
- try {
57
- // 尝试获取 iroh 身份(包含 CID)
58
- const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
59
- const irohData = await irohResp.json();
60
- if (irohData.cid) {
61
- identityData = {
62
- name: irohData.name || irohData.name?.split('-').slice(-1)[0] || 'Bolloon',
63
- did: irohData.did || '未知',
64
- cid: irohData.cid || '未知',
65
- nodeId: irohData.irohNodeId ? irohData.irohNodeId.substring(0, 20) + '...' : '未知'
66
- };
67
- }
68
- else {
69
- // 回退到 /api/identity
70
- const resp = await fetch('/api/identity');
71
- identityData = await resp.json();
72
- identityData.cid = identityData.cid || '未初始化';
73
- identityData.nodeId = identityData.nodeId || '未知';
74
- }
75
- identityHtml = `
33
+ this.overlay.appendChild(this.modal);
34
+ document.body.appendChild(this.overlay);
35
+ this.overlay.addEventListener("click", (e) => {
36
+ if (e.target === this.overlay) this.hide();
37
+ });
38
+ this.render();
39
+ }
40
+ async render() {
41
+ if (!this.modal) return;
42
+ await this.loadConnectionHistory();
43
+ let identityHtml = '<div class="p2p-loading">\u52A0\u8F7D\u4E2D...</div>';
44
+ let identityData = { name: "\u672A\u77E5", did: "\u672A\u77E5", cid: "\u672A\u77E5" };
45
+ try {
46
+ const irohResp = await fetch("/api/iroh/init", { method: "POST" });
47
+ const irohData = await irohResp.json();
48
+ if (irohData.cid) {
49
+ identityData = {
50
+ name: irohData.name || irohData.name?.split("-").slice(-1)[0] || "Bolloon",
51
+ did: irohData.did || "\u672A\u77E5",
52
+ cid: irohData.cid || "\u672A\u77E5",
53
+ nodeId: irohData.irohNodeId ? irohData.irohNodeId.substring(0, 20) + "..." : "\u672A\u77E5"
54
+ };
55
+ } else {
56
+ const resp = await fetch("/api/identity");
57
+ identityData = await resp.json();
58
+ identityData.cid = identityData.cid || "\u672A\u521D\u59CB\u5316";
59
+ identityData.nodeId = identityData.nodeId || "\u672A\u77E5";
60
+ }
61
+ identityHtml = `
76
62
  <div class="p2p-section">
77
- <h3>身份信息</h3>
63
+ <h3>\u8EAB\u4EFD\u4FE1\u606F</h3>
78
64
  <div class="p2p-info-row">
79
- <span class="label">名称:</span>
80
- <span class="value">${this.escapeHtml(identityData.name || '未知')}</span>
65
+ <span class="label">\u540D\u79F0:</span>
66
+ <span class="value">${this.escapeHtml(identityData.name || "\u672A\u77E5")}</span>
81
67
  </div>
82
68
  <div class="p2p-info-row">
83
69
  <span class="label">CID:</span>
84
- <span class="value mono">${this.escapeHtml(identityData.cid || '未知')}</span>
70
+ <span class="value mono">${this.escapeHtml(identityData.cid || "\u672A\u77E5")}</span>
85
71
  </div>
86
72
  <div class="p2p-info-row">
87
73
  <span class="label">DID:</span>
88
- <span class="value mono">${this.escapeHtml(identityData.did || '未知')}</span>
74
+ <span class="value mono">${this.escapeHtml(identityData.did || "\u672A\u77E5")}</span>
89
75
  </div>
90
76
  <div class="p2p-info-row">
91
77
  <span class="label">Node ID:</span>
92
- <span class="value mono">${this.escapeHtml(identityData.nodeId || '未知')}</span>
78
+ <span class="value mono">${this.escapeHtml(identityData.nodeId || "\u672A\u77E5")}</span>
93
79
  </div>
94
80
  <div class="p2p-actions">
95
- <button class="p2p-btn-secondary" id="p2p-copy-cid-btn">复制 CID</button>
96
- <button class="p2p-btn-secondary" id="p2p-copy-did-btn">复制 DID</button>
97
- <button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn">复制 Node ID</button>
81
+ <button class="p2p-btn-secondary" id="p2p-copy-cid-btn">\u590D\u5236 CID</button>
82
+ <button class="p2p-btn-secondary" id="p2p-copy-did-btn">\u590D\u5236 DID</button>
83
+ <button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn">\u590D\u5236 Node ID</button>
98
84
  </div>
99
85
  </div>
100
86
  `;
101
- }
102
- catch {
103
- identityHtml = '<div class="p2p-error">无法加载身份信息</div>';
104
- }
105
- this.modal.innerHTML = `
87
+ } catch {
88
+ identityHtml = '<div class="p2p-error">\u65E0\u6CD5\u52A0\u8F7D\u8EAB\u4EFD\u4FE1\u606F</div>';
89
+ }
90
+ this.modal.innerHTML = `
106
91
  <div class="p2p-header">
107
- <h2>P2P 网络</h2>
108
- <button class="p2p-close" id="p2p-close-btn">×</button>
92
+ <h2>P2P \u7F51\u7EDC</h2>
93
+ <button class="p2p-close" id="p2p-close-btn">\xD7</button>
109
94
  </div>
110
95
  <div class="p2p-tabs">
111
- <button class="p2p-tab active" data-tab="identity">身份</button>
112
- <button class="p2p-tab" data-tab="connect">连接</button>
113
- <button class="p2p-tab" data-tab="messages">消息</button>
96
+ <button class="p2p-tab active" data-tab="identity">\u8EAB\u4EFD</button>
97
+ <button class="p2p-tab" data-tab="connect">\u8FDE\u63A5</button>
98
+ <button class="p2p-tab" data-tab="messages">\u6D88\u606F</button>
114
99
  </div>
115
100
  <div class="p2p-content">
116
101
  <div class="p2p-tab-content active" data-tab="identity">
@@ -118,207 +103,187 @@ class P2PModalUI {
118
103
  </div>
119
104
  <div class="p2p-tab-content" data-tab="connect">
120
105
  <div class="p2p-section">
121
- <h3>连接到节点</h3>
106
+ <h3>\u8FDE\u63A5\u5230\u8282\u70B9</h3>
122
107
  <div class="p2p-connect-form">
123
- <input type="text" id="p2p-connect-input" placeholder="输入节点 ID CID">
124
- <button class="p2p-btn-primary" id="p2p-connect-btn">连接</button>
108
+ <input type="text" id="p2p-connect-input" placeholder="\u8F93\u5165\u8282\u70B9 ID \u6216 CID">
109
+ <button class="p2p-btn-primary" id="p2p-connect-btn">\u8FDE\u63A5</button>
125
110
  </div>
126
111
  <div id="p2p-connect-result" class="p2p-result"></div>
127
112
  </div>
128
113
  <div class="p2p-section">
129
- <h3>已连接的节点</h3>
114
+ <h3>\u5DF2\u8FDE\u63A5\u7684\u8282\u70B9</h3>
130
115
  <div id="p2p-connected-peers" class="p2p-connected-peers">
131
- 暂无连接
116
+ \u6682\u65E0\u8FDE\u63A5
132
117
  </div>
133
118
  </div>
134
119
  </div>
135
120
  <div class="p2p-tab-content" data-tab="messages">
136
121
  <div class="p2p-section">
137
- <h3>收到的消息</h3>
122
+ <h3>\u6536\u5230\u7684\u6D88\u606F</h3>
138
123
  <div id="p2p-messages-list" class="p2p-messages">
139
- 暂无消息
124
+ \u6682\u65E0\u6D88\u606F
140
125
  </div>
141
126
  </div>
142
127
  </div>
143
128
  </div>
144
129
  `;
145
- // 绑定事件
146
- this.bindEvents();
147
- }
148
- bindEvents() {
149
- if (!this.modal)
150
- return;
151
- // 关闭按钮
152
- const closeBtn = this.modal.querySelector('#p2p-close-btn');
153
- closeBtn?.addEventListener('click', () => this.hide());
154
- // 标签切换
155
- this.modal.querySelectorAll('.p2p-tab').forEach(tab => {
156
- tab.addEventListener('click', (e) => {
157
- const target = e.target;
158
- const tabName = target.dataset.tab;
159
- if (tabName)
160
- this.switchTab(tabName);
161
- });
162
- });
163
- // 复制 CID
164
- const copyCidBtn = this.modal.querySelector('#p2p-copy-cid-btn');
165
- copyCidBtn?.addEventListener('click', async () => {
166
- try {
167
- const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
168
- const data = await irohResp.json();
169
- if (data.cid) {
170
- await navigator.clipboard.writeText(data.cid);
171
- this.showToast('CID 已复制到剪贴板');
172
- }
173
- }
174
- catch (e) {
175
- console.error('复制失败:', e);
176
- }
177
- });
178
- // 复制 DID
179
- const copyDidBtn = this.modal.querySelector('#p2p-copy-did-btn');
180
- copyDidBtn?.addEventListener('click', async () => {
181
- try {
182
- const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
183
- const data = await irohResp.json();
184
- if (data.did) {
185
- await navigator.clipboard.writeText(data.did);
186
- this.showToast('DID 已复制到剪贴板');
187
- }
188
- }
189
- catch (e) {
190
- console.error('复制失败:', e);
191
- }
192
- });
193
- // 复制 Node ID
194
- const copyNodeIdBtn = this.modal.querySelector('#p2p-copy-nodeid-btn');
195
- copyNodeIdBtn?.addEventListener('click', async () => {
196
- try {
197
- const irohResp = await fetch('/api/iroh/init', { method: 'POST' });
198
- const data = await irohResp.json();
199
- if (data.irohNodeId) {
200
- await navigator.clipboard.writeText(data.irohNodeId);
201
- this.showToast('Node ID 已复制到剪贴板');
202
- }
203
- }
204
- catch (e) {
205
- console.error('复制失败:', e);
206
- }
207
- });
208
- // 连接按钮
209
- const connectBtn = this.modal.querySelector('#p2p-connect-btn');
210
- connectBtn?.addEventListener('click', () => this.handleConnect());
211
- }
212
- switchTab(tabName) {
213
- if (!this.modal)
214
- return;
215
- this.modal.querySelectorAll('.p2p-tab').forEach(tab => {
216
- tab.classList.toggle('active', tab.getAttribute('data-tab') === tabName);
217
- });
218
- this.modal.querySelectorAll('.p2p-tab-content').forEach(content => {
219
- content.classList.toggle('active', content.getAttribute('data-tab') === tabName);
220
- });
221
- }
222
- async handleConnect() {
223
- const input = document.getElementById('p2p-connect-input');
224
- const result = document.getElementById('p2p-connect-result');
225
- if (!input || !result)
226
- return;
227
- const cid = input.value.trim();
228
- if (!cid)
229
- return;
230
- result.innerHTML = '<div class="p2p-loading">连接中...</div>';
231
- result.className = 'p2p-result';
232
- try {
233
- const resp = await fetch('/api/iroh/connect', {
234
- method: 'POST',
235
- headers: { 'Content-Type': 'application/json' },
236
- body: JSON.stringify({ cid })
237
- });
238
- const data = await resp.json();
239
- if (data.ok) {
240
- result.innerHTML = `<div class="p2p-success">连接成功!节点: ${this.escapeHtml(data.nodeName || cid.substring(0, 16))}...</div>`;
241
- result.className = 'p2p-result success';
242
- // 清空输入框
243
- input.value = '';
244
- // 保存已连接节点到后端
245
- try {
246
- await fetch('/api/p2p/history', {
247
- method: 'POST',
248
- headers: { 'Content-Type': 'application/json' },
249
- body: JSON.stringify({
250
- cid: cid, // 输入的 CID 或 Node ID
251
- irohNodeId: data.targetNodeId,
252
- did: data.targetNodeId, // 用 Node ID 作为 DID
253
- name: data.nodeName || cid.substring(0, 16)
254
- })
255
- });
256
- }
257
- catch (e) {
258
- console.error('保存连接失败:', e);
259
- }
260
- }
261
- else {
262
- result.innerHTML = `<div class="p2p-error">${this.escapeHtml(data.error || '连接失败')}</div>`;
263
- result.className = 'p2p-result error';
264
- }
130
+ this.bindEvents();
131
+ }
132
+ bindEvents() {
133
+ if (!this.modal) return;
134
+ const closeBtn = this.modal.querySelector("#p2p-close-btn");
135
+ closeBtn?.addEventListener("click", () => this.hide());
136
+ this.modal.querySelectorAll(".p2p-tab").forEach((tab) => {
137
+ tab.addEventListener("click", (e) => {
138
+ const target = e.target;
139
+ const tabName = target.dataset.tab;
140
+ if (tabName) this.switchTab(tabName);
141
+ });
142
+ });
143
+ const copyCidBtn = this.modal.querySelector("#p2p-copy-cid-btn");
144
+ copyCidBtn?.addEventListener("click", async () => {
145
+ try {
146
+ const irohResp = await fetch("/api/iroh/init", { method: "POST" });
147
+ const data = await irohResp.json();
148
+ if (data.cid) {
149
+ await navigator.clipboard.writeText(data.cid);
150
+ this.showToast("CID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
265
151
  }
266
- catch (e) {
267
- const msg = e instanceof Error ? e.message : '连接失败';
268
- result.innerHTML = `<div class="p2p-error">${this.escapeHtml(msg)}</div>`;
269
- result.className = 'p2p-result error';
152
+ } catch (e) {
153
+ console.error("\u590D\u5236\u5931\u8D25:", e);
154
+ }
155
+ });
156
+ const copyDidBtn = this.modal.querySelector("#p2p-copy-did-btn");
157
+ copyDidBtn?.addEventListener("click", async () => {
158
+ try {
159
+ const irohResp = await fetch("/api/iroh/init", { method: "POST" });
160
+ const data = await irohResp.json();
161
+ if (data.did) {
162
+ await navigator.clipboard.writeText(data.did);
163
+ this.showToast("DID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
270
164
  }
271
- }
272
- async loadConnectionHistory() {
273
- try {
274
- const resp = await fetch('/api/p2p/history');
275
- const history = await resp.json();
276
- this.connectedPeers.clear();
277
- for (const entry of history) {
278
- this.connectedPeers.set(entry.irohNodeId || entry.did, {
279
- nodeId: entry.irohNodeId || entry.did,
280
- name: entry.name || 'Unknown',
281
- time: entry.lastConnectedAt || Date.now()
282
- });
283
- }
284
- this.updateConnectedPeersDisplay();
165
+ } catch (e) {
166
+ console.error("\u590D\u5236\u5931\u8D25:", e);
167
+ }
168
+ });
169
+ const copyNodeIdBtn = this.modal.querySelector("#p2p-copy-nodeid-btn");
170
+ copyNodeIdBtn?.addEventListener("click", async () => {
171
+ try {
172
+ const irohResp = await fetch("/api/iroh/init", { method: "POST" });
173
+ const data = await irohResp.json();
174
+ if (data.irohNodeId) {
175
+ await navigator.clipboard.writeText(data.irohNodeId);
176
+ this.showToast("Node ID \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F");
285
177
  }
286
- catch (e) {
287
- console.error('加载连接历史失败:', e);
178
+ } catch (e) {
179
+ console.error("\u590D\u5236\u5931\u8D25:", e);
180
+ }
181
+ });
182
+ const connectBtn = this.modal.querySelector("#p2p-connect-btn");
183
+ connectBtn?.addEventListener("click", () => this.handleConnect());
184
+ }
185
+ switchTab(tabName) {
186
+ if (!this.modal) return;
187
+ this.modal.querySelectorAll(".p2p-tab").forEach((tab) => {
188
+ tab.classList.toggle("active", tab.getAttribute("data-tab") === tabName);
189
+ });
190
+ this.modal.querySelectorAll(".p2p-tab-content").forEach((content) => {
191
+ content.classList.toggle("active", content.getAttribute("data-tab") === tabName);
192
+ });
193
+ }
194
+ async handleConnect() {
195
+ const input = document.getElementById("p2p-connect-input");
196
+ const result = document.getElementById("p2p-connect-result");
197
+ if (!input || !result) return;
198
+ const cid = input.value.trim();
199
+ if (!cid) return;
200
+ result.innerHTML = '<div class="p2p-loading">\u8FDE\u63A5\u4E2D...</div>';
201
+ result.className = "p2p-result";
202
+ try {
203
+ const resp = await fetch("/api/iroh/connect", {
204
+ method: "POST",
205
+ headers: { "Content-Type": "application/json" },
206
+ body: JSON.stringify({ cid })
207
+ });
208
+ const data = await resp.json();
209
+ if (data.ok) {
210
+ result.innerHTML = `<div class="p2p-success">\u8FDE\u63A5\u6210\u529F\uFF01\u8282\u70B9: ${this.escapeHtml(data.nodeName || cid.substring(0, 16))}...</div>`;
211
+ result.className = "p2p-result success";
212
+ input.value = "";
213
+ try {
214
+ await fetch("/api/p2p/history", {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify({
218
+ cid,
219
+ // 输入的 CID 或 Node ID
220
+ irohNodeId: data.targetNodeId,
221
+ did: data.targetNodeId,
222
+ // 用 Node ID 作为 DID
223
+ name: data.nodeName || cid.substring(0, 16)
224
+ })
225
+ });
226
+ } catch (e) {
227
+ console.error("\u4FDD\u5B58\u8FDE\u63A5\u5931\u8D25:", e);
288
228
  }
229
+ } else {
230
+ result.innerHTML = `<div class="p2p-error">${this.escapeHtml(data.error || "\u8FDE\u63A5\u5931\u8D25")}</div>`;
231
+ result.className = "p2p-result error";
232
+ }
233
+ } catch (e) {
234
+ const msg = e instanceof Error ? e.message : "\u8FDE\u63A5\u5931\u8D25";
235
+ result.innerHTML = `<div class="p2p-error">${this.escapeHtml(msg)}</div>`;
236
+ result.className = "p2p-result error";
289
237
  }
290
- updateConnectedPeersDisplay() {
291
- const container = document.getElementById('p2p-connected-peers');
292
- if (!container)
293
- return;
294
- if (this.connectedPeers.size === 0) {
295
- container.innerHTML = '<div class="p2p-empty">暂无连接</div>';
296
- return;
297
- }
298
- let html = '';
299
- for (const [nodeId, info] of this.connectedPeers) {
300
- const shortId = nodeId.substring(0, 16) + '...';
301
- const time = new Date(info.time).toLocaleTimeString();
302
- html += `
238
+ }
239
+ async loadConnectionHistory() {
240
+ try {
241
+ const resp = await fetch("/api/p2p/history");
242
+ const history = await resp.json();
243
+ this.connectedPeers.clear();
244
+ for (const entry of history) {
245
+ this.connectedPeers.set(entry.irohNodeId || entry.did, {
246
+ nodeId: entry.irohNodeId || entry.did,
247
+ name: entry.name || "Unknown",
248
+ time: entry.lastConnectedAt || Date.now()
249
+ });
250
+ }
251
+ this.updateConnectedPeersDisplay();
252
+ } catch (e) {
253
+ console.error("\u52A0\u8F7D\u8FDE\u63A5\u5386\u53F2\u5931\u8D25:", e);
254
+ }
255
+ }
256
+ updateConnectedPeersDisplay() {
257
+ const container = document.getElementById("p2p-connected-peers");
258
+ if (!container) return;
259
+ if (this.connectedPeers.size === 0) {
260
+ container.innerHTML = '<div class="p2p-empty">\u6682\u65E0\u8FDE\u63A5</div>';
261
+ return;
262
+ }
263
+ let html = "";
264
+ for (const [nodeId, info] of this.connectedPeers) {
265
+ const shortId = nodeId.substring(0, 16) + "...";
266
+ const time = new Date(info.time).toLocaleTimeString();
267
+ html += `
303
268
  <div class="p2p-peer-item">
304
269
  <span class="p2p-peer-name">${this.escapeHtml(info.name || shortId)}</span>
305
270
  <span class="p2p-peer-id">${shortId}</span>
306
271
  <span class="p2p-peer-time">${time}</span>
307
272
  </div>
308
273
  `;
309
- }
310
- container.innerHTML = html;
311
- }
312
- escapeHtml(str) {
313
- const div = document.createElement('div');
314
- div.textContent = str;
315
- return div.innerHTML;
316
274
  }
317
- showToast(message) {
318
- const toast = document.createElement('div');
319
- toast.className = 'p2p-toast';
320
- toast.textContent = message;
321
- toast.style.cssText = `
275
+ container.innerHTML = html;
276
+ }
277
+ escapeHtml(str) {
278
+ const div = document.createElement("div");
279
+ div.textContent = str;
280
+ return div.innerHTML;
281
+ }
282
+ showToast(message) {
283
+ const toast = document.createElement("div");
284
+ toast.className = "p2p-toast";
285
+ toast.textContent = message;
286
+ toast.style.cssText = `
322
287
  position: fixed;
323
288
  bottom: 24px;
324
289
  left: 50%;
@@ -331,23 +296,22 @@ class P2PModalUI {
331
296
  font-size: 14px;
332
297
  z-index: 2000;
333
298
  `;
334
- document.body.appendChild(toast);
335
- setTimeout(() => toast.remove(), 2000);
336
- }
337
- show() {
338
- if (this.overlay) {
339
- this.overlay.style.display = 'flex';
340
- this.render();
341
- }
299
+ document.body.appendChild(toast);
300
+ setTimeout(() => toast.remove(), 2e3);
301
+ }
302
+ show() {
303
+ if (this.overlay) {
304
+ this.overlay.style.display = "flex";
305
+ this.render();
342
306
  }
343
- hide() {
344
- if (this.overlay) {
345
- this.overlay.style.display = 'none';
346
- }
307
+ }
308
+ hide() {
309
+ if (this.overlay) {
310
+ this.overlay.style.display = "none";
347
311
  }
312
+ }
348
313
  }
349
- // 样式
350
- const style = document.createElement('style');
314
+ const style = document.createElement("style");
351
315
  style.textContent = `
352
316
  .p2p-modal-overlay { display: none; }
353
317
  .p2p-header {
@@ -536,10 +500,8 @@ style.textContent = `
536
500
  }
537
501
  `;
538
502
  document.head.appendChild(style);
539
- // 全局实例
540
503
  const p2pModal = new P2PModalUI();
541
- // 导出到 window
542
504
  window.p2pModal = p2pModal;
543
505
  window.showP2PModal = () => p2pModal.show();
544
506
  window.hideP2PModal = () => p2pModal.hide();
545
- console.log('[P2P Modal] 已初始化');
507
+ console.log("[P2P Modal] \u5DF2\u521D\u59CB\u5316");
@@ -4,6 +4,18 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Bolloon Agent</title>
7
+
8
+ <!-- Favicon -->
9
+ <link rel="icon" type="image/x-icon" href="/icons/favicon.ico">
10
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
11
+ <link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
12
+
13
+ <!-- Apple Touch Icon -->
14
+ <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
15
+
16
+ <!-- PWA Manifest -->
17
+ <link rel="manifest" href="/manifest.json">
18
+
7
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
21
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+SC:wght@400;500;600&display=swap" rel="stylesheet">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli.js",
@@ -47,12 +47,14 @@
47
47
  "@multiformats/multiaddr": "^13.0.3",
48
48
  "@noble/hashes": "^1.3.0",
49
49
  "@rayhanadev/iroh": "^0.1.1",
50
+ "b4a": "^1.8.1",
50
51
  "dotenv": "^17.4.2",
51
52
  "esbuild": "^0.24.0",
52
53
  "express": "^5.2.1",
53
54
  "libp2p": "^3.3.0",
54
55
  "mammoth": "^1.6.0",
55
56
  "pdf-parse": "^1.1.4",
57
+ "platform": "^1.3.6",
56
58
  "react": "^18.3.0",
57
59
  "react-dom": "^18.3.0"
58
60
  },
@@ -29,7 +29,7 @@ async function main() {
29
29
  ];
30
30
 
31
31
  try {
32
- execSync(`npx tsc --ignoreConfig --outDir dist/web/components/p2p --declaration false --skipLibCheck --target ES2022 --module ESNext --moduleResolution bundler ${moduleFiles.join(' ')}`, {
32
+ execSync(`npx tsc --outDir dist/web/components/p2p --declaration false --skipLibCheck --target ES2022 --module ESNext --moduleResolution bundler ${moduleFiles.join(' ')}`, {
33
33
  cwd: ROOT,
34
34
  stdio: 'inherit'
35
35
  });
@@ -47,7 +47,7 @@ function initUserDirs() {
47
47
  const configPath = path.join(bolloonDir, 'config.json');
48
48
  if (!fs.existsSync(configPath)) {
49
49
  const defaultConfig = {
50
- version: '0.1.11',
50
+ version: '0.1.12',
51
51
  initializedAt: new Date().toISOString(),
52
52
  defaults: {
53
53
  port: 54188,