@bolloon/bolloon-agent 0.1.2 → 0.1.4
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/bin/bolloon-cli.cjs +15 -7
- package/dist/agents/pi-sdk.js +12 -4
- package/dist/llm/config-store.js +8 -5
- package/dist/web/components/p2p/P2PModal.js +188 -0
- package/dist/web/components/p2p/index.js +264 -226
- package/dist/web/components/p2p/p2p-modal.js +657 -0
- package/dist/web/components/p2p/p2p-tools.js +248 -0
- package/dist/web/server.js +1890 -0
- package/package.json +1 -1
- package/src/web/server.ts +25 -26
package/bin/bolloon-cli.cjs
CHANGED
|
@@ -23,12 +23,18 @@ function printBanner() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function getMainEntry() {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// bin is in: node_modules/.bin/bolloon
|
|
27
|
+
// package is in: node_modules/@bolloon/bolloon-agent/
|
|
28
|
+
// dist is in: node_modules/@bolloon/bolloon-agent/dist/
|
|
29
|
+
const binPath = require.main.filename; // /tmp/node_modules/.bin/bolloon
|
|
30
|
+
const binDir = path.dirname(binPath); // /tmp/node_modules/.bin
|
|
31
|
+
const packageDir = path.dirname(binDir); // /tmp/node_modules/@bolloon/bolloon-agent
|
|
32
|
+
const distIndex = path.join(packageDir, "dist", "index.js");
|
|
28
33
|
if (fs.existsSync(distIndex)) {
|
|
29
34
|
return distIndex;
|
|
30
35
|
}
|
|
31
|
-
|
|
36
|
+
// fallback to src (for development)
|
|
37
|
+
return path.join(packageDir, "src", "index.ts");
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
function parseArgs() {
|
|
@@ -61,10 +67,12 @@ function parseArgs() {
|
|
|
61
67
|
async function startElectron(additionalArgs) {
|
|
62
68
|
try {
|
|
63
69
|
const electron = require("electron");
|
|
64
|
-
const
|
|
65
|
-
|
|
70
|
+
const binPath = require.main.filename;
|
|
71
|
+
const binDir = path.dirname(binPath);
|
|
72
|
+
const packageDir = path.dirname(binDir);
|
|
73
|
+
let mainPath = path.join(packageDir, "dist", "electron.js");
|
|
66
74
|
if (!fs.existsSync(mainPath)) {
|
|
67
|
-
mainPath = path.join(
|
|
75
|
+
mainPath = path.join(packageDir, "src", "electron.ts");
|
|
68
76
|
}
|
|
69
77
|
log("启动 Electron...", CYAN);
|
|
70
78
|
const child = spawn(electron, [mainPath, ...additionalArgs], {
|
|
@@ -109,7 +117,7 @@ async function main() {
|
|
|
109
117
|
const { mode, args } = parseArgs();
|
|
110
118
|
switch (mode) {
|
|
111
119
|
case "version":
|
|
112
|
-
console.log("Bolloon Agent v0.1.
|
|
120
|
+
console.log("Bolloon Agent v0.1.2");
|
|
113
121
|
break;
|
|
114
122
|
case "help":
|
|
115
123
|
printBanner();
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -1481,11 +1481,11 @@ let lastIdentityDid = null;
|
|
|
1481
1481
|
const independentSessions = new Map();
|
|
1482
1482
|
export async function createAgentSession(config, forceNew) {
|
|
1483
1483
|
const incomingDid = config.identityDoc?.did;
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const key = `${sessionKey}:${forceNew ? Date.now() : ''}`;
|
|
1484
|
+
// 如果有独立的 peerId (包含 :),使用它作为 key
|
|
1485
|
+
if (config.peerId && config.peerId.includes(':')) {
|
|
1486
|
+
const key = config.peerId;
|
|
1488
1487
|
if (!forceNew && independentSessions.has(key)) {
|
|
1488
|
+
console.log(`[createAgentSession] 找到现有独立 session, key=${key}`);
|
|
1489
1489
|
return independentSessions.get(key);
|
|
1490
1490
|
}
|
|
1491
1491
|
const session = new PiAgentSession(config);
|
|
@@ -1493,6 +1493,14 @@ export async function createAgentSession(config, forceNew) {
|
|
|
1493
1493
|
console.log(`[createAgentSession] 创建独立 session, key=${key}, DID=${incomingDid}`);
|
|
1494
1494
|
return session;
|
|
1495
1495
|
}
|
|
1496
|
+
// 如果指定了 forceNew 但没有 peerId,生成带时间戳的 key
|
|
1497
|
+
if (forceNew) {
|
|
1498
|
+
const key = `force:${Date.now()}`;
|
|
1499
|
+
const session = new PiAgentSession(config);
|
|
1500
|
+
independentSessions.set(key, session);
|
|
1501
|
+
console.log(`[createAgentSession] 创建强制新 session, key=${key}`);
|
|
1502
|
+
return session;
|
|
1503
|
+
}
|
|
1496
1504
|
// 如果有新的 DID,强制重建 session
|
|
1497
1505
|
if (sessionInstance && lastIdentityDid && incomingDid && lastIdentityDid !== incomingDid) {
|
|
1498
1506
|
console.log(`[createAgentSession] DID 变化 ${lastIdentityDid} -> ${incomingDid},重建 session`);
|
package/dist/llm/config-store.js
CHANGED
|
@@ -143,7 +143,8 @@ class LLMConfigStore {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
// 确保有 activeProvider
|
|
146
|
-
|
|
146
|
+
const activeProvider = loadedConfig.activeProvider;
|
|
147
|
+
if (!activeProvider || !DEFAULT_PROVIDER_CONFIGS[activeProvider]) {
|
|
147
148
|
loadedConfig.activeProvider = 'ollama';
|
|
148
149
|
}
|
|
149
150
|
this.config = loadedConfig;
|
|
@@ -265,11 +266,13 @@ export function getPiSDKConfig() {
|
|
|
265
266
|
if (!config) {
|
|
266
267
|
return { provider: 'ollama' };
|
|
267
268
|
}
|
|
268
|
-
const
|
|
269
|
+
const activeProvider = config.activeProvider;
|
|
270
|
+
const activeConfig = config.providers[activeProvider] || {};
|
|
271
|
+
const defaultConfig = DEFAULT_PROVIDER_CONFIGS[activeProvider] || { baseUrl: '', model: '' };
|
|
269
272
|
return {
|
|
270
|
-
provider:
|
|
273
|
+
provider: activeProvider,
|
|
271
274
|
apiKey: activeConfig.apiKey || undefined,
|
|
272
|
-
baseUrl: activeConfig.baseUrl !==
|
|
273
|
-
model: activeConfig.model !==
|
|
275
|
+
baseUrl: activeConfig.baseUrl !== defaultConfig.baseUrl ? activeConfig.baseUrl : undefined,
|
|
276
|
+
model: activeConfig.model !== defaultConfig.model ? activeConfig.model : undefined
|
|
274
277
|
};
|
|
275
278
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { p2pManager } from './p2p-manager.js';
|
|
4
|
+
export function P2PModal({ visible, onClose }) {
|
|
5
|
+
const [activeTab, setActiveTab] = useState('identity');
|
|
6
|
+
const [initialized, setInitialized] = useState(false);
|
|
7
|
+
const [status, setStatus] = useState('idle');
|
|
8
|
+
const [statusText, setStatusText] = useState('未初始化');
|
|
9
|
+
const [identity, setIdentity] = useState(null);
|
|
10
|
+
const [connectInput, setConnectInput] = useState('');
|
|
11
|
+
const [progress, setProgress] = useState(null);
|
|
12
|
+
const [progressVisible, setProgressVisible] = useState(false);
|
|
13
|
+
const [connectResult, setConnectResult] = useState(null);
|
|
14
|
+
const [history, setHistory] = useState([]);
|
|
15
|
+
const [messages, setMessages] = useState([]);
|
|
16
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
17
|
+
const [peers, setPeers] = useState([]);
|
|
18
|
+
const [persistentConnections, setPersistentConnections] = useState([]);
|
|
19
|
+
const [toast, setToast] = useState(null);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (visible && !initialized) {
|
|
22
|
+
console.log('[P2P Modal] visible=true, initialized=false, calling initP2P');
|
|
23
|
+
initP2P();
|
|
24
|
+
}
|
|
25
|
+
else if (visible && initialized && !identity) {
|
|
26
|
+
// 已初始化但没有identity,可能是首次未触发
|
|
27
|
+
console.log('[P2P Modal] visible=true, initialized=true, but no identity, calling initP2P again');
|
|
28
|
+
initP2P();
|
|
29
|
+
}
|
|
30
|
+
}, [visible, initialized]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (visible) {
|
|
33
|
+
loadActiveTab();
|
|
34
|
+
}
|
|
35
|
+
}, [activeTab, visible]);
|
|
36
|
+
async function initP2P() {
|
|
37
|
+
setStatus('connecting');
|
|
38
|
+
setStatusText('初始化中...');
|
|
39
|
+
try {
|
|
40
|
+
const identity = await p2pManager.init();
|
|
41
|
+
setIdentity(identity);
|
|
42
|
+
setStatus('online');
|
|
43
|
+
setStatusText('已连接');
|
|
44
|
+
setInitialized(true);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
setStatus('error');
|
|
48
|
+
setStatusText('初始化失败');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function loadActiveTab() {
|
|
52
|
+
switch (activeTab) {
|
|
53
|
+
case 'history':
|
|
54
|
+
await loadHistory();
|
|
55
|
+
break;
|
|
56
|
+
case 'messages':
|
|
57
|
+
await loadMessages();
|
|
58
|
+
break;
|
|
59
|
+
case 'connect':
|
|
60
|
+
loadPeers();
|
|
61
|
+
loadPersistentConnections();
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function loadHistory() {
|
|
66
|
+
try {
|
|
67
|
+
const hist = await p2pManager.getHistory();
|
|
68
|
+
setHistory(hist);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.error('[P2P Modal] 加载历史失败:', e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function loadMessages() {
|
|
75
|
+
try {
|
|
76
|
+
const msgs = await p2pManager.getMessages();
|
|
77
|
+
setMessages(msgs);
|
|
78
|
+
const unread = p2pManager.getUnreadCount();
|
|
79
|
+
setUnreadCount(unread);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error('[P2P Modal] 加载消息失败:', e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function loadPeers() {
|
|
86
|
+
const connectedPeers = p2pManager.getConnectedPeers();
|
|
87
|
+
setPeers(connectedPeers);
|
|
88
|
+
}
|
|
89
|
+
async function loadPersistentConnections() {
|
|
90
|
+
try {
|
|
91
|
+
const connections = await p2pManager.getPersistentConnections();
|
|
92
|
+
setPersistentConnections(connections);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
console.error('[P2P Modal] 加载持久连接失败:', e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function handleToggleConnection(connection) {
|
|
99
|
+
const newStatus = connection.status === 'connected' ? 'disconnected' : 'connected';
|
|
100
|
+
const enable = newStatus === 'connected';
|
|
101
|
+
try {
|
|
102
|
+
const success = await p2pManager.toggleConnection(connection, enable);
|
|
103
|
+
if (success) {
|
|
104
|
+
await loadPersistentConnections();
|
|
105
|
+
loadPeers();
|
|
106
|
+
showToast(enable ? '正在连接...' : '已断开');
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
showToast('操作失败');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
showToast('操作失败');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function handleOpenChannel(channelId) {
|
|
117
|
+
showToast(`打开通道: ${channelId}`);
|
|
118
|
+
onClose();
|
|
119
|
+
}
|
|
120
|
+
async function handleConnect() {
|
|
121
|
+
if (!connectInput.trim())
|
|
122
|
+
return;
|
|
123
|
+
setProgressVisible(true);
|
|
124
|
+
setProgress({ stage: 'init', percent: 0, message: '验证输入格式...' });
|
|
125
|
+
setConnectResult(null);
|
|
126
|
+
try {
|
|
127
|
+
const result = await p2pManager.connectAndCreateChannel(connectInput, (p) => setProgress(p));
|
|
128
|
+
if (result.success) {
|
|
129
|
+
setConnectResult({ type: 'success', text: `已连接到 ${result.name || '节点'}` });
|
|
130
|
+
setConnectInput('');
|
|
131
|
+
await loadHistory();
|
|
132
|
+
loadPeers();
|
|
133
|
+
loadPersistentConnections();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
setConnectResult({ type: 'error', text: result.error || '连接失败' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
setConnectResult({ type: 'error', text: e.message });
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
setProgressVisible(false);
|
|
145
|
+
setProgress(null);
|
|
146
|
+
}, 2000);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function handleHistoryAction(action, item) {
|
|
150
|
+
if (action === 'connect') {
|
|
151
|
+
setConnectInput(item.cid);
|
|
152
|
+
setActiveTab('connect');
|
|
153
|
+
}
|
|
154
|
+
else if (action === 'pin') {
|
|
155
|
+
await p2pManager.updateHistory(item.id, { isPinned: !item.isPinned });
|
|
156
|
+
await loadHistory();
|
|
157
|
+
}
|
|
158
|
+
else if (action === 'delete') {
|
|
159
|
+
await p2pManager.deleteHistory(item.id);
|
|
160
|
+
await loadHistory();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function handleMarkAllRead() {
|
|
164
|
+
await p2pManager.messages.markAllRead();
|
|
165
|
+
await loadMessages();
|
|
166
|
+
}
|
|
167
|
+
function copyToClipboard(text) {
|
|
168
|
+
navigator.clipboard.writeText(text);
|
|
169
|
+
showToast('已复制');
|
|
170
|
+
}
|
|
171
|
+
function showToast(msg) {
|
|
172
|
+
setToast(msg);
|
|
173
|
+
setTimeout(() => setToast(null), 2000);
|
|
174
|
+
}
|
|
175
|
+
function handleCopyLink() {
|
|
176
|
+
if (identity) {
|
|
177
|
+
const link = `bolloon://connect?did=${encodeURIComponent(identity.did)}&cid=${encodeURIComponent(identity.cid)}`;
|
|
178
|
+
navigator.clipboard.writeText(link);
|
|
179
|
+
showToast('链接已复制');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function handleExportFile() {
|
|
183
|
+
p2pManager.identity.exportIdentityFile();
|
|
184
|
+
}
|
|
185
|
+
if (!visible)
|
|
186
|
+
return null;
|
|
187
|
+
return (_jsxs("div", { className: "p2p-modal-overlay", onClick: (e) => e.target === e.currentTarget && onClose(), children: [_jsxs("div", { className: "p2p-modal", children: [_jsxs("div", { className: "modal-header", children: [_jsx("h2", { children: "P2P \u7F51\u7EDC" }), _jsx("button", { className: "modal-close", onClick: onClose, children: "\u00D7" })] }), _jsxs("div", { className: "tabs", children: [_jsx("button", { className: `tab ${activeTab === 'identity' ? 'active' : ''}`, onClick: () => setActiveTab('identity'), children: "\u6211\u7684\u8EAB\u4EFD" }), _jsx("button", { className: `tab ${activeTab === 'connect' ? 'active' : ''}`, onClick: () => setActiveTab('connect'), children: "\u8FDE\u63A5" }), _jsx("button", { className: `tab ${activeTab === 'history' ? 'active' : ''}`, onClick: () => setActiveTab('history'), children: "\u5386\u53F2\u8BB0\u5F55" }), _jsxs("button", { className: `tab ${activeTab === 'messages' ? 'active' : ''}`, onClick: () => setActiveTab('messages'), children: ["\u6D88\u606F ", unreadCount > 0 && _jsx("span", { className: "unread-badge", children: unreadCount })] })] }), activeTab === 'identity' && (_jsxs("div", { className: "tab-content active", children: [_jsxs("div", { className: "identity-card", children: [_jsxs("div", { className: "status-row", children: [_jsx("span", { className: `status-indicator ${status}` }), _jsx("span", { children: statusText })] }), _jsxs("div", { className: "info-row", children: [_jsx("span", { className: "info-label", children: "DID:" }), _jsx("code", { className: "info-value", children: identity?.did || '-' }), identity?.did && _jsx("button", { className: "copy-btn", onClick: () => copyToClipboard(identity.did), children: "\uD83D\uDCCB" })] }), _jsxs("div", { className: "info-row", children: [_jsx("span", { className: "info-label", children: "CID:" }), _jsx("code", { className: "info-value", children: identity?.cid || '-' }), identity?.cid && _jsx("button", { className: "copy-btn", onClick: () => copyToClipboard(identity.cid), children: "\uD83D\uDCCB" })] }), _jsxs("div", { className: "info-row", children: [_jsx("span", { className: "info-label", children: "Node ID:" }), _jsx("code", { className: "info-value", children: identity?.irohNodeId || '-' }), identity?.irohNodeId && _jsx("button", { className: "copy-btn", onClick: () => copyToClipboard(identity.irohNodeId), children: "\uD83D\uDCCB" })] })] }), !initialized && _jsx("button", { className: "btn-primary", onClick: initP2P, children: "\u521D\u59CB\u5316 P2P" }), identity && (_jsxs("div", { className: "share-panel", children: [_jsx("h4", { children: "\u5206\u4EAB\u7ED9\u597D\u53CB" }), _jsxs("div", { className: "share-actions", children: [_jsx("button", { className: "btn-secondary", onClick: handleCopyLink, children: "\uD83D\uDCCB \u590D\u5236\u94FE\u63A5" }), _jsx("button", { className: "btn-secondary", onClick: handleExportFile, children: "\uD83D\uDCC1 \u5BFC\u51FA\u6587\u4EF6" })] })] }))] })), activeTab === 'connect' && (_jsxs("div", { className: "tab-content active", children: [_jsxs("div", { className: "connect-form", children: [_jsx("input", { type: "text", placeholder: "\u7C98\u8D34 CID \u6216\u94FE\u63A5...", value: connectInput, onChange: (e) => setConnectInput(e.target.value), onKeyPress: (e) => e.key === 'Enter' && handleConnect() }), _jsx("button", { className: "btn-secondary", onClick: handleConnect, children: "\u8FDE\u63A5 \u25B6" })] }), progressVisible && progress && (_jsxs("div", { className: "progress show", children: [_jsx("div", { className: "progress-bar", children: _jsx("div", { className: "progress-fill", style: { width: `${progress.percent}%` } }) }), _jsx("span", { className: "progress-text", children: progress.message })] })), connectResult && (_jsx("div", { className: `connect-result ${connectResult.type} show`, children: connectResult.text })), _jsxs("div", { className: "persistent-peers-section", children: [_jsxs("h4", { children: ["\u6301\u4E45\u8FDE\u63A5 (", persistentConnections.length, ")"] }), persistentConnections.length === 0 ? (_jsx("div", { className: "empty-hint", children: "\u6682\u65E0\u6301\u4E45\u8FDE\u63A5" })) : (persistentConnections.map((conn) => (_jsxs("div", { className: `persistent-peer-item ${conn.status}`, children: [_jsx("div", { className: "peer-status-indicator", children: _jsx("span", { className: `dot ${conn.status === 'connected' ? 'online' : 'offline'}` }) }), _jsxs("div", { className: "peer-info", children: [_jsxs("div", { className: "peer-name", children: [conn.peerName || 'Unknown', conn.isAutoConnect && _jsx("span", { className: "auto-badge", children: "\u81EA\u52A8" })] }), _jsxs("div", { className: "peer-meta", children: [_jsxs("span", { children: ["DID: ", conn.peerDid?.substring(0, 16), "..."] }), _jsxs("span", { children: ["\u72B6\u6001: ", conn.status === 'connected' ? '已连接' : '未连接'] })] })] }), _jsxs("div", { className: "peer-actions", children: [_jsx("button", { className: `btn-sm ${conn.status === 'connected' ? 'btn-danger' : 'btn-primary'}`, onClick: () => handleToggleConnection(conn), children: conn.status === 'connected' ? '断开' : '连接' }), conn.channelId && (_jsx("button", { className: "btn-sm btn-secondary", onClick: () => handleOpenChannel(conn.channelId), children: "\u5BF9\u8BDD" }))] })] }, conn.id))))] }), _jsxs("div", { className: "peers-section", children: [_jsxs("h4", { children: ["\u5F53\u524D\u8FDE\u63A5 (", peers.length, ")"] }), _jsxs("div", { id: "p2p-peers-list", children: [peers.length === 0 && _jsx("div", { className: "empty-hint", children: "\u6682\u65E0\u8FDE\u63A5" }), peers.map((peer, i) => (_jsxs("div", { className: "peer-item", children: [_jsx("div", { className: "peer-status", children: _jsx("span", { className: "dot online" }) }), _jsxs("div", { className: "peer-info", children: [_jsx("div", { className: "peer-name", children: peer.info?.name || 'Unknown' }), _jsxs("div", { className: "peer-meta", children: [(peer.nodeId || '').substring(0, 16), "..."] })] })] }, i)))] })] })] })), activeTab === 'history' && (_jsxs("div", { className: "tab-content active", children: [_jsx("div", { className: "toolbar", children: _jsx("button", { className: "btn-secondary btn-sm", onClick: loadHistory, children: "\uD83D\uDD04 \u5237\u65B0" }) }), _jsxs("div", { id: "p2p-history-list", children: [history.length === 0 && _jsx("div", { className: "empty-hint", children: "\u6682\u65E0\u8FDE\u63A5\u5386\u53F2" }), history.map((item) => (_jsxs("div", { className: `history-item ${item.isPinned ? 'pinned' : ''}`, children: [_jsx("div", { className: "history-item-icon", children: "\uD83D\uDCAC" }), _jsxs("div", { className: "history-item-info", children: [_jsxs("div", { className: "history-item-name", children: [item.name || 'Unknown', item.isPinned && _jsx("span", { className: "pin-icon", children: "\uD83D\uDCCC" })] }), _jsxs("div", { className: "history-item-meta", children: [_jsxs("span", { children: ["\u4E0A\u6B21: ", new Date(item.lastConnectedAt).toLocaleString()] }), _jsxs("span", { children: ["\u6D88\u606F: ", item.totalMessages || 0] })] })] }), _jsxs("div", { className: "history-item-actions", children: [_jsx("button", { className: "btn-sm btn-secondary", onClick: () => handleHistoryAction('connect', item), children: "\u8FDE\u63A5" }), _jsx("button", { className: "btn-sm btn-secondary", onClick: () => handleHistoryAction('pin', item), children: item.isPinned ? '取消置顶' : '置顶' }), _jsx("button", { className: "btn-sm btn-secondary", onClick: () => handleHistoryAction('delete', item), children: "\u5220\u9664" })] })] }, item.id)))] })] })), activeTab === 'messages' && (_jsxs("div", { className: "tab-content active", children: [_jsx("div", { className: "toolbar", children: _jsx("button", { className: "btn-secondary btn-sm", onClick: handleMarkAllRead, children: "\u5168\u90E8\u5DF2\u8BFB" }) }), _jsxs("div", { id: "p2p-messages-list", children: [messages.length === 0 && _jsx("div", { className: "empty-hint", children: "\u6682\u65E0\u6D88\u606F" }), messages.slice(-20).map((msg) => (_jsxs("div", { className: `message-item ${!msg.isRead ? 'unread' : ''}`, children: [_jsxs("div", { className: "message-header", children: [_jsx("span", { className: "message-sender", children: msg.fromName || msg.fromDid }), _jsx("span", { className: "message-time", children: new Date(msg.timestamp).toLocaleString() })] }), _jsx("div", { className: "message-content", children: msg.content.substring(0, 200) })] }, msg.id)))] })] }))] }), toast && _jsx("div", { className: "toast", children: toast })] }));
|
|
188
|
+
}
|