@bolloon/bolloon-agent 0.1.14 → 0.1.16
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/dist/agents/pi-sdk.js +37 -9
- package/dist/web/client.js +2 -0
- package/dist/web/components/p2p/index.js +226 -264
- package/package.json +2 -2
- package/scripts/build-web.ts +5 -2
- package/src/agents/pi-sdk.ts +34 -10
- package/src/web/client.js +2 -0
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -657
- package/dist/web/components/p2p/p2p-tools.js +0 -248
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* P2P Modal - 纯 TypeScript 版本
|
|
4
|
-
*/
|
|
5
1
|
class P2PModalUI {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
63
|
+
<h3>\u8EAB\u4EFD\u4FE1\u606F</h3>
|
|
78
64
|
<div class="p2p-info-row">
|
|
79
|
-
<span class="label"
|
|
80
|
-
<span class="value">${this.escapeHtml(identityData.name ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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"
|
|
96
|
-
<button class="p2p-btn-secondary" id="p2p-copy-did-btn"
|
|
97
|
-
<button class="p2p-btn-secondary" id="p2p-copy-nodeid-btn"
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
108
|
-
<button class="p2p-close" id="p2p-close-btn"
|
|
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"
|
|
112
|
-
<button class="p2p-tab" data-tab="connect"
|
|
113
|
-
<button class="p2p-tab" data-tab="messages"
|
|
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
|
|
106
|
+
<h3>\u8FDE\u63A5\u5230\u8282\u70B9</h3>
|
|
122
107
|
<div class="p2p-connect-form">
|
|
123
|
-
<input type="text" id="p2p-connect-input" placeholder="
|
|
124
|
-
<button class="p2p-btn-primary" id="p2p-connect-btn"
|
|
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
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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(
|
|
507
|
+
console.log("[P2P Modal] \u5DF2\u521D\u59CB\u5316");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"src/constraint-runtime"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@bolloon/bolloon-agent": "^0.1.
|
|
35
|
+
"@bolloon/bolloon-agent": "^0.1.16",
|
|
36
36
|
"@bolloon/constraint-runtime": "0.1.0",
|
|
37
37
|
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
38
38
|
"@chainsafe/libp2p-yamux": "^8.0.1",
|
package/scripts/build-web.ts
CHANGED
|
@@ -12,8 +12,11 @@ const DIST_WEB = path.join(ROOT, 'dist', 'web');
|
|
|
12
12
|
async function main() {
|
|
13
13
|
console.log('[build-web] 开始构建...');
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
15
|
+
// 重要: 不能 rm -rf 整个 dist/web/, 否则会删掉 build:main 编译出来的
|
|
16
|
+
// dist/web/server.js. 只清理 web 静态资源, 保留 server.js.
|
|
17
|
+
for (const f of ['index.html', 'api-config.html', 'style.css', 'client.js', 'components']) {
|
|
18
|
+
await fs.rm(path.join(DIST_WEB, f), { recursive: true, force: true });
|
|
19
|
+
}
|
|
17
20
|
await fs.mkdir(DIST_WEB, { recursive: true });
|
|
18
21
|
await fs.mkdir(path.join(DIST_WEB, 'components', 'p2p'), { recursive: true });
|
|
19
22
|
|