@bolloon/bolloon-agent 0.6.5 → 0.6.7
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/chain/chain-settlement.js +0 -1
- package/dist/agents/chat-segmenter.js +4 -16
- package/dist/agents/contacts/human-directory.js +155 -0
- package/dist/agents/contacts/tools.js +79 -0
- package/dist/agents/experience-review.js +0 -3
- package/dist/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/agents/goal-resume.js +0 -4
- package/dist/agents/ha-daemon.js +85 -0
- package/dist/agents/ha-supervisor.js +146 -0
- package/dist/agents/intent-classifier.js +1 -1
- package/dist/agents/iteration-budget.js +12 -0
- package/dist/agents/knowledge-organizer.js +0 -2
- package/dist/agents/lesson-to-skill.js +0 -1
- package/dist/agents/loop-review.js +2 -2
- package/dist/agents/memory-store.js +384 -0
- package/dist/agents/opportunity-match.js +15 -3
- package/dist/agents/opportunity-web-search.js +39 -0
- package/dist/agents/p2p-chat-tools.js +1 -2
- package/dist/agents/parse-tool-call.js +9 -22
- package/dist/agents/pi-harness.js +1 -2
- package/dist/agents/pi-sdk-session-manager.js +8 -0
- package/dist/agents/pi-sdk-tools.js +180 -5
- package/dist/agents/pi-sdk.js +210 -503
- package/dist/agents/process-runner.js +28 -9
- package/dist/agents/react-loop.js +9 -17
- package/dist/agents/runner-resolver.js +4 -3
- package/dist/agents/skill-loader.js +22 -15
- package/dist/agents/skills-manager.js +1 -25
- package/dist/agents/workflow-pivot-loop.js +152 -61
- package/dist/bootstrap/context-collector.js +67 -7
- package/dist/bootstrap/context-hierarchy.js +13 -1
- package/dist/bootstrap/persona-loader.js +26 -0
- package/dist/cli/ink-app.js +44 -17
- package/dist/cli/intent-command.js +22 -12
- package/dist/cli/loading-tui.js +8 -2
- package/dist/cli/log-gate.js +40 -8
- package/dist/cli/mention-data.js +1 -0
- package/dist/cli/reply-hygiene.js +22 -0
- package/dist/cli/trace-line.js +61 -2
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
- package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/contacts/human-directory.js +204 -0
- package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -1
- package/dist/electron-build/agents/contacts/tools.js +112 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +0 -3
- package/dist/electron-build/agents/experience-review.js.map +1 -1
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
- package/dist/electron-build/agents/goal-resume.js +0 -4
- package/dist/electron-build/agents/goal-resume.js.map +1 -1
- package/dist/electron-build/agents/intent-classifier.js +1 -1
- package/dist/electron-build/agents/lesson-to-skill.js +0 -1
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
- package/dist/electron-build/agents/loop-review.js +2 -2
- package/dist/electron-build/agents/loop-review.js.map +1 -1
- package/dist/electron-build/agents/memory-recall.js.map +1 -1
- package/dist/electron-build/agents/memory-store.js +430 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +9 -22
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-harness.js +1 -2
- package/dist/electron-build/agents/pi-harness.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +208 -501
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +28 -9
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +4 -3
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/skill-loader.js +22 -16
- package/dist/electron-build/agents/skill-loader.js.map +1 -1
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
- package/dist/electron-build/agents/skills-manager.js +0 -24
- package/dist/electron-build/agents/skills-manager.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-engine.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +68 -7
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-loader.js +27 -0
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/electron-preload.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +31 -2
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/model-runtime.js +8 -1
- package/dist/electron-build/kernel/model-runtime.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +20 -0
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/embedding-adapter.js +75 -0
- package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +112 -38
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/system-prompt/registry.js +8 -6
- package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
- package/dist/electron-build/network/agent-network.js.map +1 -1
- package/dist/electron-build/orbitdb/memory-sync.js +76 -0
- package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
- package/dist/electron-build/security/tool-gate.js +2 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -1
- package/dist/electron-build/setup/setup-store.js +34 -6
- package/dist/electron-build/setup/setup-store.js.map +1 -1
- package/dist/electron-build/social/heartbeat.js +18 -5
- package/dist/electron-build/social/heartbeat.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/index.js +250 -12
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +74 -35
- package/dist/ios/icons/icon-1400x1400.png +0 -0
- package/dist/ios/icons/icon-1400x1400.webp +0 -0
- package/dist/ios/index.html +122 -108
- package/dist/ios/mobile-agent.js +572 -45
- package/dist/ios/mobile-contacts.js +0 -4
- package/dist/ios/mobile-core.js +8079 -4590
- package/dist/ios/mobile-harness.js +1004 -0
- package/dist/ios/mobile-memory.js +317 -0
- package/dist/ios/mobile-orbitdb.js +194 -0
- package/dist/ios/mobile-persona.js +80 -0
- package/dist/ios/mobile-privacy.js +2 -3
- package/dist/ios/mobile.css +286 -21
- package/dist/ios/mobile.html +122 -108
- package/dist/ios/mobile.js +434 -152
- package/dist/ios/routes-contacts.js +94 -1
- package/dist/ios/routes-world.js +13 -2
- package/dist/ios/server.js +54 -41
- package/dist/ios/style.css +13 -5
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +4 -18
- package/dist/ios/ui/step-timeline.js +14 -1
- package/dist/kernel/channel-actor.js +30 -2
- package/dist/kernel/gate-scan.js +2 -2
- package/dist/kernel/ha/ha-arbiter.js +177 -0
- package/dist/kernel/ha/ha-guard.js +151 -0
- package/dist/kernel/index.js +36 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +5 -5
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/config-store.js +20 -0
- package/dist/llm/embedding-adapter.js +69 -0
- package/dist/llm/pi-ai.js +110 -37
- package/dist/llm/system-prompt/registry.js +8 -6
- package/dist/orbitdb/memory-sync.js +70 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
- package/dist/security/tool-gate.js +2 -0
- package/dist/setup/setup-store.js +34 -6
- package/dist/social/heartbeat.js +18 -5
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +74 -35
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +37 -25
- package/dist/web/mobile-agent.js +572 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +8079 -4590
- package/dist/web/mobile-harness.js +1004 -0
- package/dist/web/mobile-memory.js +317 -0
- package/dist/web/mobile-orbitdb.js +194 -0
- package/dist/web/mobile-persona.js +80 -0
- package/dist/web/mobile-privacy.js +2 -3
- package/dist/web/mobile.css +286 -21
- package/dist/web/mobile.html +122 -108
- package/dist/web/mobile.js +434 -152
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +13 -2
- package/dist/web/server.js +54 -41
- package/dist/web/style.css +13 -5
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +4 -18
- package/dist/web/ui/step-timeline.js +14 -1
- package/package.json +3 -2
- package/scripts/build-web.ts +16 -1
package/dist/ios/mobile.js
CHANGED
|
@@ -69,21 +69,66 @@
|
|
|
69
69
|
});
|
|
70
70
|
} catch (e) {}
|
|
71
71
|
|
|
72
|
-
const TITLES = { main: '首页', friends: '
|
|
72
|
+
const TITLES = { main: '首页', friends: '通讯录', tasks: '任务', me: '我' };
|
|
73
73
|
let currentTab = 'main';
|
|
74
74
|
function switchTab(tab) {
|
|
75
75
|
currentTab = tab;
|
|
76
76
|
$$('.page, .page-container').forEach((p) => { p.hidden = p.dataset.tab !== tab; });
|
|
77
77
|
$$('.tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === tab));
|
|
78
78
|
$('#topbar-title').textContent = TITLES[tab] || '会话';
|
|
79
|
-
const cs = $('#btn-create-session'); if (cs) cs.hidden = tab !== 'main';
|
|
80
79
|
const ta = $('#topbar-actions'); if (ta) ta.hidden = tab === 'me'; // 我 页不显示 加号/刷新
|
|
81
80
|
if (tab === 'friends') { loadContacts(); loadP2PStatus(); }
|
|
82
|
-
|
|
81
|
+
// 2026-10-06: 网络 + 任务合一 —— 任务 tab 同时加载网络与任务数据
|
|
82
|
+
if (tab === 'tasks') { loadAgentControl(); loadApprovals(); loadNetMembers(); loadAgentServices(); loadX402Info(); try { refreshAll(); } catch (e) {} }
|
|
83
83
|
if (tab === 'main') { loadAgentCovers(); }
|
|
84
84
|
window.__mobileTouch?.('tab', tab);
|
|
85
85
|
}
|
|
86
86
|
$$('.tab').forEach((t) => t.addEventListener('click', () => switchTab(t.dataset.tab)));
|
|
87
|
+
// 2026-10-07: 任务 tab 对话框模式 — 网络收成仪表盘工具 (覆盖层开关)
|
|
88
|
+
function openTaskNetwork() {
|
|
89
|
+
const p = $('#task-network-panel'); if (p) p.hidden = false;
|
|
90
|
+
// 打开时加载网络数据
|
|
91
|
+
try { loadAgentControl(); loadApprovals(); loadNetMembers(); loadAgentServices(); loadX402Info(); } catch (e) {}
|
|
92
|
+
}
|
|
93
|
+
function closeTaskNetwork() {
|
|
94
|
+
const p = $('#task-network-panel'); if (p) p.hidden = true;
|
|
95
|
+
}
|
|
96
|
+
const dashNet = $('#task-dash-network'); if (dashNet) dashNet.addEventListener('click', openTaskNetwork);
|
|
97
|
+
const netBack = $('#task-network-back'); if (netBack) netBack.addEventListener('click', closeTaskNetwork);
|
|
98
|
+
|
|
99
|
+
// 任务对话框: 派活 → runLocalAgent (手机自治循环, 回显到消息流)
|
|
100
|
+
async function sendTask() {
|
|
101
|
+
const input = $('#task-input');
|
|
102
|
+
if (!input) return;
|
|
103
|
+
const text = input.value.trim();
|
|
104
|
+
if (!text) return;
|
|
105
|
+
input.value = '';
|
|
106
|
+
appendTaskMsg('user', text);
|
|
107
|
+
const empty = $('#task-chat-empty'); if (empty) empty.hidden = true;
|
|
108
|
+
try {
|
|
109
|
+
const a = window.BolloonCore && window.BolloonCore.agent;
|
|
110
|
+
let reply;
|
|
111
|
+
if (a && a.runLocal) reply = await a.runLocal(text);
|
|
112
|
+
else if (a && a.runLoop) reply = await a.runLoop(text);
|
|
113
|
+
else reply = '手机 Agent 未就绪';
|
|
114
|
+
appendTaskMsg('ai', String(reply || '(无回复)'));
|
|
115
|
+
} catch (e) {
|
|
116
|
+
appendTaskMsg('ai', '[执行失败] ' + (e.message || e));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function appendTaskMsg(role, text) {
|
|
120
|
+
const chat = $('#task-chat');
|
|
121
|
+
if (!chat) return;
|
|
122
|
+
const el = document.createElement('div');
|
|
123
|
+
el.className = 'task-msg ' + (role === 'user' ? 'task-msg-user' : 'task-msg-ai');
|
|
124
|
+
el.textContent = text;
|
|
125
|
+
chat.appendChild(el);
|
|
126
|
+
chat.scrollTop = chat.scrollHeight;
|
|
127
|
+
}
|
|
128
|
+
const taskInput = $('#task-input');
|
|
129
|
+
const taskSend = $('#task-input-send');
|
|
130
|
+
if (taskSend) taskSend.addEventListener('click', sendTask);
|
|
131
|
+
if (taskInput) taskInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendTask(); });
|
|
87
132
|
|
|
88
133
|
async function loadConversations() {
|
|
89
134
|
try {
|
|
@@ -114,26 +159,58 @@
|
|
|
114
159
|
}
|
|
115
160
|
|
|
116
161
|
async function loadContacts() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
try {
|
|
163
|
+
let peers = [];
|
|
164
|
+
try { peers = await api.get('/api/peers'); } catch { peers = []; }
|
|
165
|
+
const list = $('#contacts-list');
|
|
166
|
+
if (!list) return;
|
|
167
|
+
list.innerHTML = '';
|
|
168
|
+
if (!Array.isArray(peers) || peers.length === 0) {
|
|
169
|
+
list.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-muted)">暂无联系人</div>';
|
|
170
|
+
const idx = $('#contacts-index'); if (idx) idx.innerHTML = '';
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// 通讯录式: 按首字母分组 (2026-10-07, 微信通讯录风格)
|
|
174
|
+
const groups = {};
|
|
175
|
+
peers.forEach((p) => {
|
|
176
|
+
const name = p.name || p.publicKey?.slice(0, 12) || '好友';
|
|
177
|
+
const letter = (name.charAt(0) || '#').toUpperCase();
|
|
178
|
+
const g = /[A-Z]/.test(letter) ? letter : '#';
|
|
179
|
+
if (!groups[g]) groups[g] = [];
|
|
180
|
+
groups[g].push({ name, peer: p });
|
|
181
|
+
});
|
|
182
|
+
const letters = Object.keys(groups).sort((a, b) => (a === '#' ? 1 : b === '#' ? -1 : a.localeCompare(b)));
|
|
183
|
+
letters.forEach((letter) => {
|
|
184
|
+
const groupEl = document.createElement('div');
|
|
185
|
+
groupEl.className = 'contacts-group';
|
|
186
|
+
groupEl.setAttribute('data-letter', letter);
|
|
187
|
+
const head = document.createElement('div');
|
|
188
|
+
head.className = 'contacts-group-head';
|
|
189
|
+
head.textContent = letter;
|
|
190
|
+
groupEl.appendChild(head);
|
|
191
|
+
groups[letter].forEach((c) => {
|
|
192
|
+
const el = document.createElement('div');
|
|
193
|
+
el.className = 'conv-item contact-row';
|
|
194
|
+
el.innerHTML = `<div class="conv-avatar contact-avatar">${escapeHtml(c.name.charAt(0))}</div>
|
|
195
|
+
<div class="conv-body"><div class="conv-name">${escapeHtml(c.name)}</div>
|
|
196
|
+
<div class="contact-sub">${c.peer.online ? '在线' : '离线'}${c.peer.publicKey ? ' · ' + escapeHtml(String(c.peer.publicKey).slice(0, 10)) + '…' : ''}</div></div>`;
|
|
197
|
+
groupEl.appendChild(el);
|
|
198
|
+
});
|
|
199
|
+
list.appendChild(groupEl);
|
|
200
|
+
});
|
|
201
|
+
// 右侧字母索引条
|
|
202
|
+
const idx = $('#contacts-index');
|
|
203
|
+
if (idx) {
|
|
204
|
+
idx.innerHTML = letters.map((l) => `<div class="contacts-index-letter" data-letter="${l}">${l === '#' ? '#' : l}</div>`).join('');
|
|
205
|
+
idx.querySelectorAll('.contacts-index-letter').forEach((el) => {
|
|
206
|
+
el.addEventListener('click', () => {
|
|
207
|
+
const target = list.querySelector(`.contacts-group[data-letter="${el.dataset.letter}"]`);
|
|
208
|
+
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
} catch (e) { /* 忽略 */ }
|
|
213
|
+
}
|
|
137
214
|
|
|
138
215
|
// 智能体控制 = MCP 控制 + Skills 控制 (不是屏幕触控!)
|
|
139
216
|
// - MCP 工具: 真正的工具调用 (gateway_status / join / register …)
|
|
@@ -376,15 +453,15 @@
|
|
|
376
453
|
try {
|
|
377
454
|
const s = await api.get('/api/auth/status');
|
|
378
455
|
$('#me-name').textContent = s.loggedIn ? (s.name || '未登录') : '未登录';
|
|
379
|
-
$('#me-did').textContent = s.did ? ('
|
|
456
|
+
$('#me-did').textContent = s.did ? ('身份证明: ' + (s.didShort || s.did)) : '';
|
|
380
457
|
if (avatarEl && !av) avatarEl.textContent = (s.loggedIn && s.name ? s.name : 'U').charAt(0);
|
|
381
458
|
$('#login-label').textContent = s.loggedIn ? '已登录' : '登录';
|
|
382
459
|
} catch (e) { /* 默认 */ }
|
|
383
|
-
//
|
|
460
|
+
// 身份证明 (连接标识)
|
|
384
461
|
try {
|
|
385
462
|
const net = await api.get('/api/network/status');
|
|
386
463
|
const p2p = net && net.nodeId;
|
|
387
|
-
$('#me-p2p').textContent = p2p ? ('
|
|
464
|
+
$('#me-p2p').textContent = p2p ? ('连接标识: ' + p2p.slice(0, 14) + '…') : '';
|
|
388
465
|
} catch (e) { /* 未连接 */ }
|
|
389
466
|
}
|
|
390
467
|
|
|
@@ -411,10 +488,10 @@
|
|
|
411
488
|
<div class="avatar" id="identity-avatar" style="width:72px;height:72px"></div>
|
|
412
489
|
<div><div class="profile-name">${escapeHtml(name)}</div><div style="font-size:12px;color:var(--text-muted)">人类认证身份</div></div>
|
|
413
490
|
</div>
|
|
414
|
-
<div class="identity-row"><div class="k"
|
|
415
|
-
<div class="identity-row"><div class="k"
|
|
491
|
+
<div class="identity-row"><div class="k">身份证明 (全局唯一)</div><div class="v">${escapeHtml(did || '未生成')}</div></div>
|
|
492
|
+
<div class="identity-row"><div class="k">连接标识</div><div class="v">${escapeHtml(p2p || '未连接')}</div></div>
|
|
416
493
|
<div class="identity-row"><div class="k">创建时间</div><div class="v">${escapeHtml(created || '未知')}</div></div>
|
|
417
|
-
<div class="identity-row"><div class="k">说明</div><div class="v"
|
|
494
|
+
<div class="identity-row"><div class="k">说明</div><div class="v">身份证明是全局唯一的人类认证身份; 连接标识是本机的通信编号, 两者不同。</div></div>`;
|
|
418
495
|
const av = localStorage.getItem('bolloon_avatar');
|
|
419
496
|
const ia = body.querySelector('#identity-avatar');
|
|
420
497
|
if (ia) { if (av) { ia.style.backgroundImage = 'url(' + av + ')'; ia.style.backgroundSize = 'cover'; ia.textContent = ''; } else ia.textContent = (name || 'U').charAt(0); }
|
|
@@ -515,16 +592,20 @@
|
|
|
515
592
|
const cards = $('#world-feed-cards');
|
|
516
593
|
if (!feed || !cards) return;
|
|
517
594
|
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const data = await
|
|
521
|
-
const opps = (data.opportunities || []).slice(0, 6);
|
|
595
|
+
// 2026-10-16 (leo: 首页卡片按「附近 + 通讯录」动态传送): 三源聚合 (桌面机会流 + 通讯录卡 + 附近卡)
|
|
596
|
+
// 不再只拉桌面机会流; 来源不上屏 (leo: 主动推送不贴元解释文案)
|
|
597
|
+
const data = await api.get('/api/world/cards');
|
|
598
|
+
const opps = ((data && data.opportunities) || []).slice(0, 6);
|
|
522
599
|
if (!opps.length) { feed.hidden = true; return; }
|
|
523
600
|
feed.hidden = false;
|
|
524
601
|
worldFeedShown = true;
|
|
525
602
|
cards.innerHTML = opps.map((o) => {
|
|
526
603
|
const pct = Math.round(o.score * 100);
|
|
527
|
-
|
|
604
|
+
// 2026-10-05: 来源标签 (与电脑端同口径) — ✉ 外部投递 / 🔎 AI 搜索 / ○ 世界变化
|
|
605
|
+
let tag = '世界变化';
|
|
606
|
+
if (o.reason === 'inbox') tag = '✉ 外部投递';
|
|
607
|
+
else if (o.reason === 'search') tag = '🔎 AI 搜索';
|
|
608
|
+
else if (o.reason === 'match') tag = '与你匹配';
|
|
528
609
|
return `<div class="world-feed-card">
|
|
529
610
|
<div class="world-feed-card-top">
|
|
530
611
|
<span class="world-feed-title">${escapeHtml(String(o.title || '').slice(0, 34))}</span>
|
|
@@ -541,7 +622,7 @@
|
|
|
541
622
|
btn.addEventListener('click', async () => {
|
|
542
623
|
const id = btn.dataset.wfId;
|
|
543
624
|
const action = btn.dataset.wfAction;
|
|
544
|
-
try { await
|
|
625
|
+
try { await api.post('/api/opportunities/' + id + '/' + action, {}); } catch (e) {}
|
|
545
626
|
const card = btn.closest('.world-feed-card');
|
|
546
627
|
if (card) {
|
|
547
628
|
card.style.opacity = '0.3';
|
|
@@ -554,12 +635,41 @@
|
|
|
554
635
|
feed.hidden = true; // 拉不到就不显示世界区 (不阻塞首页)
|
|
555
636
|
}
|
|
556
637
|
// 持续流入: 世界区可见时每 45s 自动刷新 (AI 持续观察, 不是人刷新)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
638
|
+
if (feed && !feed.hidden && worldFeedShown && !worldFeedTimer) {
|
|
639
|
+
worldFeedTimer = setInterval(() => { loadWorldFeed(); }, 45000);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
561
642
|
|
|
562
|
-
|
|
643
|
+
// === 2026-10-07: 手机首页底部输入框 — 告诉世界你在做什么 (对应桌面世界模式) ===
|
|
644
|
+
async function tellWorld() {
|
|
645
|
+
const input = $('#world-input');
|
|
646
|
+
if (!input) return;
|
|
647
|
+
const text = input.value.trim();
|
|
648
|
+
if (!text) return;
|
|
649
|
+
input.value = '';
|
|
650
|
+
try {
|
|
651
|
+
// 声明意图 (与桌面同 API; 桌面不可达时走 BolloonCore.resolve 转发)
|
|
652
|
+
const res = await api.post('/api/intents', { text, priority: 3 });
|
|
653
|
+
if (!res || !res.ok) console.info('[world] 声明意图失败');
|
|
654
|
+
// 输入即主动搜索 (leo: 声明后立刻搜机会)
|
|
655
|
+
try { await api.post('/api/world/search', { topic: text }); } catch (e) {}
|
|
656
|
+
// 刷新机会流
|
|
657
|
+
await loadWorldFeed();
|
|
658
|
+
if (window.showToast) showToast('🌍 已告诉世界,正在为你匹配机会…');
|
|
659
|
+
} catch (e) {
|
|
660
|
+
console.info('[world] 声明失败', e);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function bindWorldInput() {
|
|
664
|
+
const input = $('#world-input');
|
|
665
|
+
const send = $('#world-input-send');
|
|
666
|
+
if (input) input.addEventListener('keydown', (e) => { if (e.key === 'Enter') tellWorld(); });
|
|
667
|
+
if (send) send.addEventListener('click', tellWorld);
|
|
668
|
+
}
|
|
669
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindWorldInput);
|
|
670
|
+
else bindWorldInput();
|
|
671
|
+
|
|
672
|
+
// === 卡片封面 (docs/fig 导出 → src/web/covers, 每个 agent 唯一不重复) ===
|
|
563
673
|
let _coverList = null;
|
|
564
674
|
async function loadCovers() {
|
|
565
675
|
if (_coverList) return _coverList;
|
|
@@ -638,8 +748,8 @@
|
|
|
638
748
|
allAgentCards.push({
|
|
639
749
|
id: p.id || p.publicKey?.slice(0, 12) || `peer-${Date.now()}`,
|
|
640
750
|
agentId: p.id || p.publicKey?.slice(0, 12),
|
|
641
|
-
name: p.name || '
|
|
642
|
-
desc: '
|
|
751
|
+
name: p.name || '未知联系人',
|
|
752
|
+
desc: '通讯录智能体',
|
|
643
753
|
avatar: null,
|
|
644
754
|
peer: { name: p.name || '好友', online: p.online || false },
|
|
645
755
|
status: p.online ? 'online' : 'offline',
|
|
@@ -992,7 +1102,7 @@
|
|
|
992
1102
|
<div style="flex:1;font-weight:600">搜索</div>
|
|
993
1103
|
</div>
|
|
994
1104
|
<div style="padding:12px;display:flex;flex-direction:column;gap:10px;flex:1;min-height:0">
|
|
995
|
-
<input id="search-input" type="search" autocomplete="off" placeholder="搜智能体 /
|
|
1105
|
+
<input id="search-input" type="search" autocomplete="off" placeholder="搜智能体 / 通讯录:名称、身份证明、编号"
|
|
996
1106
|
style="padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-hover);color:var(--text);font-size:14px">
|
|
997
1107
|
<div id="search-hint" style="font-size:12px;color:var(--text-muted)">正在建立索引…</div>
|
|
998
1108
|
<div id="search-results" style="flex:1;overflow:auto;-webkit-overflow-scrolling:touch"></div>
|
|
@@ -1012,10 +1122,10 @@
|
|
|
1012
1122
|
act: () => { page.remove(); openChat(c); },
|
|
1013
1123
|
})),
|
|
1014
1124
|
...peers.map((p) => ({
|
|
1015
|
-
tag: '
|
|
1125
|
+
tag: '通讯录', name: p.name || String(p.publicKey || p.id || '联系人').slice(0, 12),
|
|
1016
1126
|
sub: String(p.publicKey || p.id || p.address || '').slice(0, 28),
|
|
1017
1127
|
hay: [p.name, p.publicKey, p.id, p.address, p.did].filter(Boolean).join(' '),
|
|
1018
|
-
act: () => alert(
|
|
1128
|
+
act: () => alert(`联系人信息\n名称: ${p.name || '(未命名)'}\n编号: ${p.publicKey || p.id || '—'}\n地址: ${p.address || '—'}`),
|
|
1019
1129
|
})),
|
|
1020
1130
|
...services.map((s) => ({
|
|
1021
1131
|
tag: '全局智能体',
|
|
@@ -1032,10 +1142,10 @@
|
|
|
1032
1142
|
const render = (q) => {
|
|
1033
1143
|
const k = String(q || '').trim().toLowerCase();
|
|
1034
1144
|
const hits = rows.filter((r) => !k || r.hay.includes(k) || r.name.toLowerCase().includes(k));
|
|
1035
|
-
if (hint) hint.innerHTML = `本机智能体 ${channels.length} ·
|
|
1145
|
+
if (hint) hint.innerHTML = `本机智能体 ${channels.length} · 通讯录 ${peers.length} · 全局智能体 ${services.length}` + (k ? ` · 命中 ${hits.length}` : '');
|
|
1036
1146
|
if (!box) return;
|
|
1037
1147
|
if (!hits.length) {
|
|
1038
|
-
box.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-muted);font-size:13px">没有匹配「${escapeHtml(String(q))}
|
|
1148
|
+
box.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-muted);font-size:13px">没有匹配「${escapeHtml(String(q))}」的智能体或联系人</div>`;
|
|
1039
1149
|
return;
|
|
1040
1150
|
}
|
|
1041
1151
|
let html = '';
|
|
@@ -1935,11 +2045,11 @@
|
|
|
1935
2045
|
<div class="conv-item" id="settings-data"><span class="list-icon">${ICONS.chip}</span><span style="flex:1;min-width:0"><span style="display:block">本机数据</span><span class="conv-preview" style="display:block">读取中…</span></span><span class="list-arrow">›</span></div>
|
|
1936
2046
|
<div class="conv-item" id="settings-desktop"><span class="list-icon">${ICONS.globe}</span><span>电脑端同步</span><span class="list-arrow">›</span></div>
|
|
1937
2047
|
<div class="conv-item" id="settings-chain"><span class="list-icon">${ICONS.chip}</span><span>链上配置 (RPC/网络)</span><span class="list-arrow">›</span></div>
|
|
1938
|
-
<div class="conv-item" id="settings-accessibility"><span class="list-icon">${ICONS.globe}</span><span>完全访问权限<span class="conv-preview" style="display:block" id="access-preview">默认开启</span></span><span class="list-arrow">›</span></div>
|
|
1939
2048
|
<div class="conv-item" id="settings-ipfs"><span class="list-icon">${ICONS.chip}</span><span>IPFS 存储</span><span class="list-arrow">›</span></div>
|
|
1940
2049
|
<div class="conv-item" id="settings-helia"><span class="list-icon">${ICONS.globe}</span><span>本机 IPFS 节点</span><span class="list-arrow">›</span></div>
|
|
1941
2050
|
<div class="conv-item" id="settings-selfcard"><span class="list-icon">${ICONS.chip}</span><span id="selfcard-text">显示本机卡片: 开</span></div>
|
|
1942
|
-
<div class="conv-item" id="settings-did"><span class="list-icon">${ICONS.idcard}</span><span>DID
|
|
2051
|
+
<div class="conv-item" id="settings-did"><span class="list-icon">${ICONS.idcard}</span><span>DID 与身份</span><span class="list-arrow">›</span></div>
|
|
2052
|
+
<div class="conv-item" id="settings-persona"><span class="list-icon">${ICONS.chip}</span><span>智能体性格</span><span class="list-arrow">›</span></div>
|
|
1943
2053
|
<div class="conv-item" id="settings-privacy"><span class="list-icon">${ICONS.chip}</span><span>隐私政策与个人信息</span><span class="list-arrow">›</span></div>
|
|
1944
2054
|
<div class="conv-item" id="settings-wipe"><span class="list-icon">${ICONS.trash}</span><span style="flex:1;min-width:0"><span style="display:block">清除本机数据(注销)</span><span class="conv-preview" style="display:block">删除本机身份、智能体、会话与钱包</span></span><span class="list-arrow">›</span></div>
|
|
1945
2055
|
<div class="conv-item" id="settings-filing"><span class="list-icon">${ICONS.idcard}</span><span id="filing-text">APP 备案号:备案办理中</span></div>
|
|
@@ -1968,48 +2078,6 @@
|
|
|
1968
2078
|
const next = currentTheme === 'auto' ? 'light' : (currentTheme === 'light' ? 'dark' : 'auto');
|
|
1969
2079
|
applyTheme(next, true);
|
|
1970
2080
|
});
|
|
1971
|
-
// 完全访问权限 (屏幕点按/滑动, 电脑端 phone.tap / phone.swipe 的前提) — 默认开启
|
|
1972
|
-
const ACCESS_AUTO_KEY = 'bolloon_access_auto_guided';
|
|
1973
|
-
const refreshAccessPreview = async () => {
|
|
1974
|
-
const el = document.querySelector('#access-preview');
|
|
1975
|
-
if (!el) return;
|
|
1976
|
-
const cap = window.Capacitor;
|
|
1977
|
-
const b = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
1978
|
-
if (!b) { el.textContent = '仅手机 App'; return; }
|
|
1979
|
-
let st = {};
|
|
1980
|
-
try { st = (await b.touchStatus()) || {}; } catch (e) { st = {}; }
|
|
1981
|
-
el.textContent = st.ready ? '已开启' : '未开启 · 点这里开';
|
|
1982
|
-
};
|
|
1983
|
-
void refreshAccessPreview();
|
|
1984
|
-
// 默认开启: 首次启动自动把用户带到系统开关页 (只引导一次, 不反复打扰)
|
|
1985
|
-
void (async () => {
|
|
1986
|
-
try {
|
|
1987
|
-
const cap = window.Capacitor;
|
|
1988
|
-
const b = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
1989
|
-
if (!b) return;
|
|
1990
|
-
let st = {};
|
|
1991
|
-
try { st = (await b.touchStatus()) || {}; } catch (e) { return; }
|
|
1992
|
-
if (st.ready) return;
|
|
1993
|
-
if (localStorage.getItem(ACCESS_AUTO_KEY) === '1') return;
|
|
1994
|
-
localStorage.setItem(ACCESS_AUTO_KEY, '1');
|
|
1995
|
-
await b.openAccessibilitySettings();
|
|
1996
|
-
} catch (e) {}
|
|
1997
|
-
})();
|
|
1998
|
-
$('#settings-accessibility').addEventListener('click', async () => {
|
|
1999
|
-
const cap = window.Capacitor;
|
|
2000
|
-
const bridge = cap && cap.Plugins && cap.Plugins.RokidBridge;
|
|
2001
|
-
if (!bridge) { alert('完全访问权限只在手机 App 里有:电脑端 / 网页版没有这项。'); return; }
|
|
2002
|
-
let st = {};
|
|
2003
|
-
try { st = (await bridge.touchStatus()) || {}; } catch (e) { st = {}; }
|
|
2004
|
-
if (st.ready) { alert('完全访问权限已开启:智能体可以在这台手机上点按、滑动屏幕(电脑端也能远程帮你点)。\n\n想关掉:系统设置 → 辅助功能 → 已开启的服务 → Bolloon Agent,关掉即可。'); return; }
|
|
2005
|
-
try {
|
|
2006
|
-
alert('马上打开系统设置,把「Bolloon Agent」的完全访问权限打开。\n(这一项在有些手机叫「辅助功能」,有些叫「无障碍」,指同一个开关;打开后一直有效,随时可以关掉。)');
|
|
2007
|
-
await bridge.openAccessibilitySettings();
|
|
2008
|
-
void refreshAccessPreview();
|
|
2009
|
-
}
|
|
2010
|
-
catch (e) { alert('没打开成功,请手动去:系统设置 → 辅助功能 → 已开启的服务 → Bolloon Agent,打开即可。'); }
|
|
2011
|
-
});
|
|
2012
|
-
|
|
2013
2081
|
// 本机数据: 直接读 IndexedDB 快照 (智能体/会话/消息都在本机, 重开 App 不会丢)
|
|
2014
2082
|
const dataEl = $('#settings-data');
|
|
2015
2083
|
if (dataEl) {
|
|
@@ -2063,7 +2131,122 @@
|
|
|
2063
2131
|
loadAgentCovers();
|
|
2064
2132
|
showToast(hidden ? '已显示本机卡片' : '已隐藏本机卡片');
|
|
2065
2133
|
});
|
|
2066
|
-
$('#settings-did').addEventListener('click', () =>
|
|
2134
|
+
$('#settings-did').addEventListener('click', () => void openIdentityPage());
|
|
2135
|
+
$('#settings-persona').addEventListener('click', () => void openPersonaPage());
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// === 身份页 (身份证明 / 签名 / 邮箱 / 手机号) ===
|
|
2139
|
+
// 2026-10-07: 完整身份系统 — DIAP did:key (与桌面同构) + 附带条件 (邮箱/手机号/备注)
|
|
2140
|
+
// 2026-10-16 (leo: 显示端降低认知负担): 不外露 DID/Ed25519 术语 — 「身份证明」「身份密钥」「签名能力」
|
|
2141
|
+
async function openIdentityPage() {
|
|
2142
|
+
if ($('#identity-page')) return;
|
|
2143
|
+
const page = mkOverlayPage('identity-page', '身份与证明', '<div style="font-size:12px;color:var(--text-muted)">读取中…</div>');
|
|
2144
|
+
const body = page.querySelector('#identity-page-body');
|
|
2145
|
+
try {
|
|
2146
|
+
const core = window.BolloonCore || (await import('./mobile-core.js')).core;
|
|
2147
|
+
const ag = core && core.agent;
|
|
2148
|
+
const st = await ag.identityStatus();
|
|
2149
|
+
body.innerHTML = `
|
|
2150
|
+
<div class="list">
|
|
2151
|
+
<div class="list-item"><span class="list-icon">${ICONS.idcard}</span><span style="flex:1"><span style="display:block">身份证明</span><span class="conv-preview" style="display:block;word-break:break-all">${escapeHtml(st.did || '未生成')}</span></span></div>
|
|
2152
|
+
<div class="list-item"><span class="list-icon">${ICONS.chip}</span><span style="flex:1"><span style="display:block">身份密钥</span><span class="conv-preview" style="display:block;word-break:break-all">${st.publicKey ? escapeHtml(st.publicKey.slice(0, 32) + '…') : '(老设备哈希身份,无签名)'}</span></span></div>
|
|
2153
|
+
<div class="list-item"><span class="list-icon">${ICONS.chip}</span><span style="flex:1"><span style="display:block">名字</span><span class="conv-preview" style="display:block">${escapeHtml(st.name || '未登录')}</span></span></div>
|
|
2154
|
+
</div>
|
|
2155
|
+
<div class="section-label">附带身份条件(仅存本机)</div>
|
|
2156
|
+
<div class="list">
|
|
2157
|
+
<div class="list-item"><span class="list-icon">✉️</span><span style="flex:1"><span style="display:block">邮箱</span><span class="conv-preview" id="id-email" style="display:block">${escapeHtml(st.email || '未设置')}</span></span></div>
|
|
2158
|
+
<div class="list-item"><span class="list-icon">📱</span><span style="flex:1"><span style="display:block">手机号</span><span class="conv-preview" id="id-phone" style="display:block">${escapeHtml(st.phone || '未设置')}</span></span></div>
|
|
2159
|
+
<div class="list-item"><span class="list-icon">📝</span><span style="flex:1"><span style="display:block">备注</span><span class="conv-preview" id="id-note" style="display:block">${escapeHtml(st.note || '未设置')}</span></span></div>
|
|
2160
|
+
<button class="sheet-choice" id="id-edit" style="width:100%;margin-top:8px">编辑邮箱 / 手机号 / 备注</button>
|
|
2161
|
+
</div>
|
|
2162
|
+
<div class="section-label">签名能力</div>
|
|
2163
|
+
<div class="list">
|
|
2164
|
+
<button class="sheet-choice" id="id-sign-test" style="width:100%">测试签名(本地签 + 验,证明身份可签名)</button>
|
|
2165
|
+
</div>`;
|
|
2166
|
+
const editBtn = page.querySelector('#id-edit');
|
|
2167
|
+
if (editBtn) editBtn.addEventListener('click', () => {
|
|
2168
|
+
const email = prompt('邮箱:', st.email || '');
|
|
2169
|
+
if (email === null) return;
|
|
2170
|
+
const phone = prompt('手机号:', st.phone || '');
|
|
2171
|
+
if (phone === null) return;
|
|
2172
|
+
const note = prompt('备注:', st.note || '');
|
|
2173
|
+
if (note === null) return;
|
|
2174
|
+
void (async () => {
|
|
2175
|
+
const updated = await ag.updateProfile({ email, phone, note });
|
|
2176
|
+
const e = page.querySelector('#id-email'); if (e) e.textContent = updated.email || '未设置';
|
|
2177
|
+
const p = page.querySelector('#id-phone'); if (p) p.textContent = updated.phone || '未设置';
|
|
2178
|
+
const n = page.querySelector('#id-note'); if (n) n.textContent = updated.note || '未设置';
|
|
2179
|
+
showToast('✅ 身份条件已更新');
|
|
2180
|
+
})();
|
|
2181
|
+
});
|
|
2182
|
+
const signBtn = page.querySelector('#id-sign-test');
|
|
2183
|
+
if (signBtn) signBtn.addEventListener('click', () => {
|
|
2184
|
+
void (async () => {
|
|
2185
|
+
signBtn.textContent = '签名中…';
|
|
2186
|
+
try {
|
|
2187
|
+
const s = await ag.sign('bolloon-mobile-sign-test');
|
|
2188
|
+
const ok = await ag.verifySign('bolloon-mobile-sign-test', s.signature, s.publicKey);
|
|
2189
|
+
signBtn.textContent = ok ? `✅ 签名+验签通过 (${s.did.slice(0, 16)}…)` : '❌ 验签失败';
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
signBtn.textContent = '签名不可用: ' + String((e && e.message) || e).slice(0, 40);
|
|
2192
|
+
}
|
|
2193
|
+
})();
|
|
2194
|
+
});
|
|
2195
|
+
} catch (e) {
|
|
2196
|
+
body.innerHTML = '<div style="padding:20px;color:var(--text-muted)">身份读取失败: ' + escapeHtml(String((e && e.message) || e)) + '</div>';
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// === 智能体性格页 (Personal AI) ===
|
|
2201
|
+
// 2026-10-07: mobile-persona 配置文件 — 性格/价值观/兴趣/说话方式, 注入 agent 系统提示
|
|
2202
|
+
async function openPersonaPage() {
|
|
2203
|
+
if ($('#persona-page')) return;
|
|
2204
|
+
const page = mkOverlayPage('persona-page', '智能体性格', '<div style="font-size:12px;color:var(--text-muted)">读取中…</div>');
|
|
2205
|
+
const body = page.querySelector('#persona-page-body');
|
|
2206
|
+
try {
|
|
2207
|
+
const persona = await import('./mobile-persona.js');
|
|
2208
|
+
const p = persona.loadPersona();
|
|
2209
|
+
body.innerHTML = `
|
|
2210
|
+
<div style="padding:14px;font-size:12px;color:var(--text-muted);line-height:1.7">配置手机智能体的「性格文件」—— 会注入到每次对话的系统提示里,决定它怎么说话、看重什么、不做什么。存本机。</div>
|
|
2211
|
+
<div class="section-label">名字</div>
|
|
2212
|
+
<div style="padding:0 14px"><input id="ps-name" value="${escapeHtml(p.name)}" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2213
|
+
<div class="section-label">性格</div>
|
|
2214
|
+
<div style="padding:0 14px"><input id="ps-personality" value="${escapeHtml(p.personality)}" placeholder="例如:活泼、严谨、毒舌…" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2215
|
+
<div class="section-label">说话方式</div>
|
|
2216
|
+
<div style="padding:0 14px"><input id="ps-style" value="${escapeHtml(p.style)}" placeholder="例如:简洁中文,先结论后细节" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2217
|
+
<div class="section-label">价值观(逗号分隔)</div>
|
|
2218
|
+
<div style="padding:0 14px"><input id="ps-values" value="${escapeHtml(p.values.join('、'))}" placeholder="本地优先、隐私优先…" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2219
|
+
<div class="section-label">兴趣(逗号分隔)</div>
|
|
2220
|
+
<div style="padding:0 14px"><input id="ps-interests" value="${escapeHtml(p.interests.join('、'))}" placeholder="P2P、AI、知识系统…" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2221
|
+
<div class="section-label">不做的事(逗号分隔)</div>
|
|
2222
|
+
<div style="padding:0 14px"><input id="ps-boundaries" value="${escapeHtml(p.boundaries.join('、'))}" placeholder="不编造数据、不假装能做…" style="width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:14px" /></div>
|
|
2223
|
+
<div style="padding:14px">
|
|
2224
|
+
<button class="sheet-choice" id="ps-save" style="width:100%">保存性格</button>
|
|
2225
|
+
<button class="sheet-choice sheet-cancel" id="ps-reset" style="width:100%;margin-top:8px">恢复默认</button>
|
|
2226
|
+
</div>`;
|
|
2227
|
+
const saveBtn = page.querySelector('#ps-save');
|
|
2228
|
+
if (saveBtn) saveBtn.addEventListener('click', () => {
|
|
2229
|
+
const v = (id) => { const el = page.querySelector(id); return el ? String(el.value || '').trim() : ''; };
|
|
2230
|
+
persona.savePersona({
|
|
2231
|
+
name: v('#ps-name'),
|
|
2232
|
+
personality: v('#ps-personality'),
|
|
2233
|
+
style: v('#ps-style'),
|
|
2234
|
+
values: v('#ps-values').split(/[、,]/).map((s) => s.trim()).filter(Boolean),
|
|
2235
|
+
interests: v('#ps-interests').split(/[、,]/).map((s) => s.trim()).filter(Boolean),
|
|
2236
|
+
boundaries: v('#ps-boundaries').split(/[、,]/).map((s) => s.trim()).filter(Boolean),
|
|
2237
|
+
});
|
|
2238
|
+
showToast('✅ 性格已保存,下次对话生效');
|
|
2239
|
+
});
|
|
2240
|
+
const resetBtn = page.querySelector('#ps-reset');
|
|
2241
|
+
if (resetBtn) resetBtn.addEventListener('click', () => {
|
|
2242
|
+
persona.resetPersona();
|
|
2243
|
+
showToast('已恢复默认性格');
|
|
2244
|
+
page.remove();
|
|
2245
|
+
void openPersonaPage();
|
|
2246
|
+
});
|
|
2247
|
+
} catch (e) {
|
|
2248
|
+
body.innerHTML = '<div style="padding:20px;color:var(--text-muted)">性格读取失败: ' + escapeHtml(String((e && e.message) || e)) + '</div>';
|
|
2249
|
+
}
|
|
2067
2250
|
}
|
|
2068
2251
|
|
|
2069
2252
|
// === 模型配置 (供应商 / 模型 / 地址) ===
|
|
@@ -2525,23 +2708,10 @@
|
|
|
2525
2708
|
}
|
|
2526
2709
|
}
|
|
2527
2710
|
|
|
2528
|
-
// 创建智能体:
|
|
2529
|
-
async function createSession() {
|
|
2530
|
-
showSheet('#create-sheet');
|
|
2531
|
-
try {
|
|
2532
|
-
await api.post('/api/channels/create', {});
|
|
2533
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
2534
|
-
switchTab('main');
|
|
2535
|
-
loadAgentCovers();
|
|
2536
|
-
} catch (e) {
|
|
2537
|
-
alert('创建失败: ' + (e.message || e));
|
|
2538
|
-
} finally {
|
|
2539
|
-
hideSheet('#create-sheet');
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2711
|
+
// 创建智能体: 已由首页世界流替代 (2026-10-07: createSession 无调用, 删除)
|
|
2542
2712
|
|
|
2543
|
-
|
|
2544
|
-
|
|
2713
|
+
// 添加好友: 弹出选择 sheet (扫码 / 手动)
|
|
2714
|
+
function addFriend() { showSheet('#addfriend-sheet'); }
|
|
2545
2715
|
async function addFriendManual() {
|
|
2546
2716
|
hideSheet('#addfriend-sheet');
|
|
2547
2717
|
const addr = prompt('输入好友地址 (multiaddr, 如 /ip4/10.0.2.2/tcp/54188/ws)', '');
|
|
@@ -2549,7 +2719,7 @@
|
|
|
2549
2719
|
try {
|
|
2550
2720
|
const r = await api.post('/api/peers/add', { addr });
|
|
2551
2721
|
alert(r && r.ok ? (r.connected ? '已连接好友' : '已记录好友地址, 连接中...') : ((r && r.error) || '添加失败'));
|
|
2552
|
-
if (currentTab === 'network') loadContacts();
|
|
2722
|
+
if (currentTab === 'network' || currentTab === 'tasks') loadContacts();
|
|
2553
2723
|
} catch (e) {
|
|
2554
2724
|
alert('添加失败: ' + (e.message || e));
|
|
2555
2725
|
}
|
|
@@ -2564,6 +2734,52 @@
|
|
|
2564
2734
|
inp.click();
|
|
2565
2735
|
}
|
|
2566
2736
|
|
|
2737
|
+
// === 手机号搜索添加好友 (2026-10-15, 手机原生手机号功能) ===
|
|
2738
|
+
// 目录数据在电脑端 (human-directory.json), 手机端只转发; 电脑端不可达 → 明确报错不假装。
|
|
2739
|
+
function openPhoneSearch() {
|
|
2740
|
+
hideSheet('#addfriend-sheet');
|
|
2741
|
+
showSheet('#phone-search-sheet');
|
|
2742
|
+
const inp = $('#phone-search-input');
|
|
2743
|
+
if (inp) { inp.value = ''; inp.focus(); }
|
|
2744
|
+
const result = $('#phone-search-result');
|
|
2745
|
+
if (result) { result.style.display = 'none'; result.innerHTML = ''; }
|
|
2746
|
+
}
|
|
2747
|
+
async function doPhoneSearch() {
|
|
2748
|
+
const inp = $('#phone-search-input');
|
|
2749
|
+
const result = $('#phone-search-result');
|
|
2750
|
+
if (!inp || !result) return;
|
|
2751
|
+
const phone = (inp.value || '').replace(/[^\d+]/g, '');
|
|
2752
|
+
if (!/^\+?\d{8,15}$/.test(phone)) { result.style.display = 'block'; result.innerHTML = '<div style="color:var(--danger,#e05555);font-size:13px">请输入正确的手机号 (8-15 位数字)</div>'; return; }
|
|
2753
|
+
result.style.display = 'block';
|
|
2754
|
+
result.innerHTML = '<div style="color:var(--text-secondary);font-size:13px">搜索中…</div>';
|
|
2755
|
+
try {
|
|
2756
|
+
const r = await api.post('/api/phone/directory/lookup', { phone });
|
|
2757
|
+
if (r && r.found) {
|
|
2758
|
+
const name = r.displayName || String(r.did || '').slice(0, 12);
|
|
2759
|
+
result.innerHTML = `<div style="font-size:14px;margin-bottom:6px">✅ 找到账号: <b>${escapeHtml(name)}</b></div>
|
|
2760
|
+
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:10px">身份证明: ${escapeHtml(String(r.did || ''))}</div>
|
|
2761
|
+
<button class="sheet-choice" id="phone-add-btn" style="width:100%">发送好友申请</button>`;
|
|
2762
|
+
const btn = $('#phone-add-btn');
|
|
2763
|
+
if (btn) btn.addEventListener('click', async () => {
|
|
2764
|
+
btn.disabled = true; btn.textContent = '发送中…';
|
|
2765
|
+
const a = await api.post('/api/phone/directory/add-friend', { phone });
|
|
2766
|
+
btn.disabled = false;
|
|
2767
|
+
if (a && a.ok !== false) {
|
|
2768
|
+
btn.textContent = '✅ 已发送 (对方同意后建立好友)';
|
|
2769
|
+
if (a.friend && a.friend.requestId) result.innerHTML += `<div style="font-size:12px;color:var(--text-secondary);margin-top:6px">申请号: ${escapeHtml(String(a.friend.requestId).slice(0, 12))}… 可到「待处理申请」查看</div>`;
|
|
2770
|
+
} else {
|
|
2771
|
+
btn.textContent = '重试发送';
|
|
2772
|
+
result.innerHTML += `<div style="color:var(--danger,#e05555);font-size:12px;margin-top:6px">发送失败: ${escapeHtml((a && a.error) || '未知原因')}</div>`;
|
|
2773
|
+
}
|
|
2774
|
+
});
|
|
2775
|
+
} else {
|
|
2776
|
+
result.innerHTML = '<div style="color:var(--text-secondary);font-size:13px">未找到该手机号的账号 (对方未发布目录或已撤销)。</div>';
|
|
2777
|
+
}
|
|
2778
|
+
} catch (e) {
|
|
2779
|
+
result.innerHTML = `<div style="color:var(--danger,#e05555);font-size:13px">查询失败: ${escapeHtml((e && e.message) || e || '未知错误')}</div>`;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2567
2783
|
// === 复制 (助记词/私钥/地址 快捷复制; 全局委托 [data-copy]) ===
|
|
2568
2784
|
async function copyText(t) {
|
|
2569
2785
|
const txt = String(t || '');
|
|
@@ -2586,7 +2802,9 @@
|
|
|
2586
2802
|
setTimeout(() => { b.textContent = old; }, 1200);
|
|
2587
2803
|
});
|
|
2588
2804
|
|
|
2589
|
-
// === 登录页 (
|
|
2805
|
+
// === 登录页 (手机号验证码登录 + 昵称兜底) ===
|
|
2806
|
+
// 2026-10-15: 手机原生手机号登录 — 输手机号 → 发验证码 → 验证通过 → 身份绑定手机号。
|
|
2807
|
+
// 验证码链路转发电脑端 /api/contacts/bind + verify (与桌面同一 OTP 通道)。
|
|
2590
2808
|
function openLoginPage() {
|
|
2591
2809
|
const page = document.createElement('div');
|
|
2592
2810
|
page.className = 'chat-page';
|
|
@@ -2597,13 +2815,61 @@
|
|
|
2597
2815
|
<div style="flex:1;font-weight:600">登录</div>
|
|
2598
2816
|
</div>
|
|
2599
2817
|
<div style="padding:12px;display:flex;flex-direction:column;gap:12px">
|
|
2600
|
-
<div style="font-size:13px;color:var(--text-secondary)"
|
|
2818
|
+
<div style="font-size:13px;color:var(--text-secondary)">手机号登录: 输手机号 → 收验证码 → 登录。身份仍由本机 DID 承载, 手机号只是登录/查找凭证。</div>
|
|
2819
|
+
<input id="login-phone" type="tel" inputmode="numeric" placeholder="手机号 (如 13812345678)" style="${_walletInput}">
|
|
2820
|
+
<div style="display:flex;gap:8px">
|
|
2821
|
+
<input id="login-code" inputmode="numeric" placeholder="验证码" style="${_walletInput};flex:1" disabled>
|
|
2822
|
+
<button id="login-sendcode" style="${_walletBtn};width:110px;flex:none">发送验证码</button>
|
|
2823
|
+
</div>
|
|
2824
|
+
<button id="login-submit" style="${_walletBtn}">手机号登录</button>
|
|
2825
|
+
<div style="border-top:1px solid var(--border);margin-top:4px;padding-top:12px;font-size:12px;color:var(--text-secondary)">或者用昵称登录 (本机身份, 不上传):</div>
|
|
2601
2826
|
<input id="login-name" placeholder="昵称 (如 觉者)" style="${_walletInput}">
|
|
2602
|
-
<button id="login-submit" style="${_walletBtn}"
|
|
2827
|
+
<button id="login-name-submit" style="${_walletBtn}">昵称登录</button>
|
|
2603
2828
|
</div>`;
|
|
2604
2829
|
document.body.appendChild(page);
|
|
2605
2830
|
$('#login-back').addEventListener('click', () => page.remove());
|
|
2606
|
-
|
|
2831
|
+
|
|
2832
|
+
// 手机号登录: 登录成功后把手机号写进身份 profile (手机号即凭证)
|
|
2833
|
+
const phoneInput = $('#login-phone');
|
|
2834
|
+
const codeInput = $('#login-code');
|
|
2835
|
+
const sendBtn = $('#login-sendcode');
|
|
2836
|
+
const submitBtn = $('#login-submit');
|
|
2837
|
+
let _pending = { contactId: '', challengeId: '' };
|
|
2838
|
+
sendBtn.addEventListener('click', async () => {
|
|
2839
|
+
const phone = (phoneInput.value || '').replace(/[^\d+]/g, '');
|
|
2840
|
+
if (!/^\+?\d{8,15}$/.test(phone)) { alert('请输入正确的手机号 (8-15 位数字)'); return; }
|
|
2841
|
+
sendBtn.disabled = true; sendBtn.textContent = '发送中…';
|
|
2842
|
+
try {
|
|
2843
|
+
const r = await api.post('/api/phone/auth/bind', { phone });
|
|
2844
|
+
if (!r || r.ok === false) { alert((r && r.error) || '发送失败'); sendBtn.disabled = false; sendBtn.textContent = '发送验证码'; return; }
|
|
2845
|
+
_pending = { contactId: String(r.contactId || ''), challengeId: String(r.challengeId || '') };
|
|
2846
|
+
codeInput.disabled = false; codeInput.focus();
|
|
2847
|
+
sendBtn.textContent = '已发送 ✓';
|
|
2848
|
+
// 本地落盘通道会回显验证码 (方便无短信设备的调试; 真 SMS 通道不回显)
|
|
2849
|
+
if (r.otpForLocalSink) { showToast ? showToast('验证码: ' + String(r.otpForLocalSink)) : alert('验证码: ' + String(r.otpForLocalSink)); }
|
|
2850
|
+
setTimeout(() => { sendBtn.disabled = false; sendBtn.textContent = '重新发送'; }, 30000);
|
|
2851
|
+
} catch (e) { alert('发送失败: ' + (e.message || e)); sendBtn.disabled = false; sendBtn.textContent = '发送验证码'; }
|
|
2852
|
+
});
|
|
2853
|
+
submitBtn.addEventListener('click', async () => {
|
|
2854
|
+
const code = (codeInput.value || '').trim();
|
|
2855
|
+
if (!_pending.contactId || !code) { alert('先发送验证码并输入验证码'); return; }
|
|
2856
|
+
submitBtn.disabled = true; submitBtn.textContent = '验证中…';
|
|
2857
|
+
try {
|
|
2858
|
+
const v = await api.post('/api/phone/auth/verify', { contactId: _pending.contactId, challengeId: _pending.challengeId, code });
|
|
2859
|
+
if (!v || v.ok === false) { alert((v && v.error) || '验证失败'); submitBtn.disabled = false; submitBtn.textContent = '手机号登录'; return; }
|
|
2860
|
+
// 验证通过 → 昵称默认取手机号, 登录本机身份, 手机号写进 profile
|
|
2861
|
+
const phone = (phoneInput.value || '').replace(/[^\d+]/g, '');
|
|
2862
|
+
await api.post('/api/auth/login', { name: '用户' + phone.slice(-4) });
|
|
2863
|
+
try { await api.post('/api/phone/profile/phone', { phone }); } catch { /* 本地 profile 更新失败不阻塞登录 */ }
|
|
2864
|
+
page.remove();
|
|
2865
|
+
await loadMe();
|
|
2866
|
+
switchTab('me');
|
|
2867
|
+
await autoSyncDesktop();
|
|
2868
|
+
} catch (e) { alert('验证失败: ' + (e.message || e)); submitBtn.disabled = false; submitBtn.textContent = '手机号登录'; }
|
|
2869
|
+
});
|
|
2870
|
+
// 手机号 → 本机身份 profile (只存本机; 电脑端目录发布仍需用户在电脑端做)
|
|
2871
|
+
// 昵称登录兜底
|
|
2872
|
+
$('#login-name-submit').addEventListener('click', async () => {
|
|
2607
2873
|
const name = (($('#login-name') || {}).value || '').trim();
|
|
2608
2874
|
try {
|
|
2609
2875
|
await api.post('/api/auth/login', { name });
|
|
@@ -2627,13 +2893,12 @@
|
|
|
2627
2893
|
bindAvatarInput();
|
|
2628
2894
|
$('#item-login').addEventListener('click', () => { openLoginPage(); });
|
|
2629
2895
|
$('#item-logout').addEventListener('click', async () => {
|
|
2630
|
-
if (!confirm('确定注销? 将清除本机登录态 (
|
|
2896
|
+
if (!confirm('确定注销? 将清除本机登录态 (身份证明保留, 不影响频道/连接)。')) return;
|
|
2631
2897
|
try { await api.post('/api/auth/logout', {}); await loadMe(); } catch (e) { alert('注销失败: ' + (e.message || e)); }
|
|
2632
2898
|
});
|
|
2633
2899
|
$('#btn-add').addEventListener('click', addFriend);
|
|
2634
2900
|
const bIdx = $('#btn-index'); if (bIdx) bIdx.addEventListener('click', () => void openIndexPanel());
|
|
2635
2901
|
const bSearch = $('#btn-search'); if (bSearch) bSearch.addEventListener('click', () => void openSearch());
|
|
2636
|
-
const cs = $('#btn-create-session'); if (cs) cs.addEventListener('click', createSession);
|
|
2637
2902
|
const csScan = $('#choice-scan'); if (csScan) csScan.addEventListener('click', addFriendScan);
|
|
2638
2903
|
const csMan = $('#choice-manual'); if (csMan) csMan.addEventListener('click', addFriendManual);
|
|
2639
2904
|
const csCan = $('#choice-cancel'); if (csCan) csCan.addEventListener('click', () => hideSheet('#addfriend-sheet'));
|
|
@@ -2645,8 +2910,8 @@
|
|
|
2645
2910
|
if (first) openTradeCall(first); else openTradeHistory();
|
|
2646
2911
|
});
|
|
2647
2912
|
$('#item-p2p-id').addEventListener('click', async () => {
|
|
2648
|
-
try { const net = await api.get('/api/network/status'); const p2p = net && net.nodeId; alert('
|
|
2649
|
-
catch (e) { alert('
|
|
2913
|
+
try { const net = await api.get('/api/network/status'); const p2p = net && net.nodeId; alert('连接标识 (本机通信编号):\n' + (p2p || '未连接')); }
|
|
2914
|
+
catch (e) { alert('连接标识: 获取失败'); }
|
|
2650
2915
|
});
|
|
2651
2916
|
// #1 一键入网 (全球智能体网络): 点一下 → 默认 prompt 交给智能体执行
|
|
2652
2917
|
const joinGlobalBtn = $('#item-join-global');
|
|
@@ -2670,16 +2935,33 @@
|
|
|
2670
2935
|
// #2b 连接好友 (点按式) + 附近设备 + 待处理申请
|
|
2671
2936
|
const addFriendBtn = $('#item-add-friend');
|
|
2672
2937
|
if (addFriendBtn) addFriendBtn.addEventListener('click', () => showSheet('#addfriend-sheet'));
|
|
2938
|
+
const cPhone = $('#choice-phone');
|
|
2939
|
+
if (cPhone) cPhone.addEventListener('click', () => openPhoneSearch());
|
|
2673
2940
|
const cNearby = $('#choice-nearby');
|
|
2674
2941
|
if (cNearby) cNearby.addEventListener('click', () => { hideSheet('#addfriend-sheet'); openNearbySheet('friend'); });
|
|
2675
2942
|
const cRequests = $('#choice-requests');
|
|
2676
2943
|
if (cRequests) cRequests.addEventListener('click', () => { hideSheet('#addfriend-sheet'); openRequestsSheet(); });
|
|
2944
|
+
// #2c 手机号搜索添加好友 sheet 的按键绑定
|
|
2945
|
+
const psDo = $('#phone-search-do');
|
|
2946
|
+
if (psDo) psDo.addEventListener('click', () => { void doPhoneSearch(); });
|
|
2947
|
+
const psClose = $('#phone-search-close');
|
|
2948
|
+
if (psClose) psClose.addEventListener('click', () => hideSheet('#phone-search-sheet'));
|
|
2949
|
+
const psInput = $('#phone-search-input');
|
|
2950
|
+
if (psInput) psInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); void doPhoneSearch(); } });
|
|
2677
2951
|
const nearbyBtn = $('#item-nearby');
|
|
2678
2952
|
if (nearbyBtn) nearbyBtn.addEventListener('click', () => openNearbySheet('friend'));
|
|
2679
2953
|
const nearbyRefresh = $('#nearby-refresh');
|
|
2680
2954
|
if (nearbyRefresh) nearbyRefresh.addEventListener('click', () => loadNearbyList(_nearbyMode));
|
|
2681
2955
|
const nearbyClose = $('#nearby-close');
|
|
2682
2956
|
if (nearbyClose) nearbyClose.addEventListener('click', () => hideSheet('#nearby-sheet'));
|
|
2957
|
+
// 2026-10-16 (leo: 首页卡片动态传送): P2P 收到附近卡 → 即时刷新首页 (不等 45s 轮询)
|
|
2958
|
+
try {
|
|
2959
|
+
window.BolloonCore && window.BolloonCore.events && window.BolloonCore.events.subscribe((m) => {
|
|
2960
|
+
if (m && m.type === 'world-card') loadWorldFeed();
|
|
2961
|
+
});
|
|
2962
|
+
} catch (e) { /* 事件订阅失败不影响其它功能 */ }
|
|
2963
|
+
// 连上对端即要一次卡 (动态传送主动触发; 无 peer 静默)
|
|
2964
|
+
api.get('/api/world/cards/request').then(() => {}).catch(() => {});
|
|
2683
2965
|
// 微信息 (x402 付费信息): 详情 → 购买并验真 / 只看元数据
|
|
2684
2966
|
const x402Buy = $('#x402-buy');
|
|
2685
2967
|
if (x402Buy) x402Buy.addEventListener('click', () => buyX402Info());
|
|
@@ -2719,7 +3001,7 @@
|
|
|
2719
3001
|
const r2 = await (window.BolloonCore && window.BolloonCore.gateway && window.BolloonCore.gateway.join(text));
|
|
2720
3002
|
alert(r2 ? (r2.output || '已处理') : '这个二维码不是好友地址 (也不是入网链接)');
|
|
2721
3003
|
}
|
|
2722
|
-
if (currentTab === 'network') { loadContacts(); loadP2PStatus(); }
|
|
3004
|
+
if (currentTab === 'network' || currentTab === 'tasks') { loadContacts(); loadP2PStatus(); }
|
|
2723
3005
|
return;
|
|
2724
3006
|
}
|
|
2725
3007
|
const r = await (window.BolloonCore && window.BolloonCore.gateway && window.BolloonCore.gateway.join(text));
|
|
@@ -3120,7 +3402,7 @@
|
|
|
3120
3402
|
if (!msg || msg.type !== 'ui' || !msg.action) return;
|
|
3121
3403
|
const d = msg.data || {};
|
|
3122
3404
|
switch (msg.action) {
|
|
3123
|
-
case 'switchTab': if (d.tab && ['main', 'friends', '
|
|
3405
|
+
case 'switchTab': if (d.tab && ['main', 'friends', 'tasks', 'me'].includes(d.tab)) switchTab(d.tab); break;
|
|
3124
3406
|
case 'openSettings': openSettings(); break;
|
|
3125
3407
|
case 'showToast': alert(d.message || ''); break;
|
|
3126
3408
|
case 'goBack': closeCardDetail(); closeChat(); break;
|
|
@@ -3245,7 +3527,7 @@
|
|
|
3245
3527
|
return false;
|
|
3246
3528
|
}
|
|
3247
3529
|
function setupGestures() {
|
|
3248
|
-
const TAB_ORDER = ['main', 'friends', '
|
|
3530
|
+
const TAB_ORDER = ['main', 'friends', 'tasks', 'me'];
|
|
3249
3531
|
let sx = 0, sy = 0, st = 0, active = false, startTarget = null;
|
|
3250
3532
|
document.addEventListener('touchstart', (e) => {
|
|
3251
3533
|
if (e.touches.length !== 1) { active = false; return; }
|
|
@@ -3705,39 +3987,39 @@
|
|
|
3705
3987
|
|
|
3706
3988
|
// ── 渲染: 公告板 ──────────────────────────────────────────────────────────
|
|
3707
3989
|
function renderBoard() {
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3716
|
-
items.forEach((it, i) => {
|
|
3717
|
-
const r = row(
|
|
3718
|
-
String(it.capability || '') + ' · ' + String(it.idShort || '') + ' · ' + String((it.statusLabel || {})[lang()] || ''),
|
|
3719
|
-
'',
|
|
3720
|
-
);
|
|
3721
|
-
const sub = line('', '');
|
|
3722
|
-
sub.style.fontSize = '12px';
|
|
3723
|
-
sub.style.color = 'var(--text-secondary)';
|
|
3724
|
-
sub.appendChild(document.createTextNode((lang() === 'en' ? 'budget ' : '预算 ') + safeText(it.budgetLabel || '—') + ' · '));
|
|
3725
|
-
sub.appendChild(document.createTextNode(lang() === 'en' ? 'closes ' : '截止 '));
|
|
3726
|
-
sub.appendChild(relDeltaNode(it.deadlineInMs));
|
|
3727
|
-
r.firstChild.appendChild(sub);
|
|
3728
|
-
if (it.claimable) {
|
|
3729
|
-
const b = mk('button', 'sheet-choice');
|
|
3730
|
-
b.style.flex = '0 0 auto';
|
|
3731
|
-
bilingual(b, '发进群', 'Post to group');
|
|
3732
|
-
b.setAttribute('data-announce', String(i));
|
|
3733
|
-
b.addEventListener('click', () => askAnnounceToGroup(i));
|
|
3734
|
-
r.appendChild(b);
|
|
3990
|
+
const list = $('#tasks-board');
|
|
3991
|
+
if (!list) return;
|
|
3992
|
+
clear(list);
|
|
3993
|
+
const d = state.board || {};
|
|
3994
|
+
const items = d.items || [];
|
|
3995
|
+
if (!items.length) {
|
|
3996
|
+
list.appendChild(line('sheet-text', lang() === 'en' ? 'Board is empty' : '板上暂时没有公告'));
|
|
3735
3997
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3998
|
+
// 2026-10-07: 双栏瀑布流卡片 (发现页风格)
|
|
3999
|
+
items.forEach((it, i) => {
|
|
4000
|
+
const card = mk('div', 'board-card');
|
|
4001
|
+
const cap = mk('div', 'board-card-cap');
|
|
4002
|
+
cap.textContent = String(it.capability || it.idShort || '任务');
|
|
4003
|
+
card.appendChild(cap);
|
|
4004
|
+
const status = mk('div', 'board-card-status');
|
|
4005
|
+
status.textContent = String((it.statusLabel || {})[lang()] || '');
|
|
4006
|
+
card.appendChild(status);
|
|
4007
|
+
const sub = mk('div', 'board-card-sub');
|
|
4008
|
+
sub.textContent = (lang() === 'en' ? 'budget ' : '预算 ') + safeText(it.budgetLabel || '—')
|
|
4009
|
+
+ ' · ' + (lang() === 'en' ? 'closes ' : '截止 ') + relDeltaText(it.deadlineInMs);
|
|
4010
|
+
card.appendChild(sub);
|
|
4011
|
+
if (it.claimable) {
|
|
4012
|
+
const b = mk('button', 'board-card-btn');
|
|
4013
|
+
b.textContent = lang() === 'en' ? 'Post to group' : '发进群';
|
|
4014
|
+
b.setAttribute('data-announce', String(i));
|
|
4015
|
+
b.addEventListener('click', () => askAnnounceToGroup(i));
|
|
4016
|
+
card.appendChild(b);
|
|
4017
|
+
}
|
|
4018
|
+
list.appendChild(card);
|
|
4019
|
+
});
|
|
4020
|
+
const notes = Array.isArray(d.notes) ? d.notes : [];
|
|
4021
|
+
if (notes.length) list.appendChild(line('sheet-text', notes.join(' · ')));
|
|
4022
|
+
}
|
|
3741
4023
|
|
|
3742
4024
|
// ── 渲染: 飞轮进度 (只读; 状态来自 goal-flywheel 的五类用户可见状态) ────────
|
|
3743
4025
|
function renderFlywheel() {
|