@bolloon/bolloon-agent 0.3.23 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -474
- package/dist/agents/pi-sdk-tools.js +337 -15
- package/dist/agents/plan-store.js +167 -0
- package/dist/agents/skill-writer.js +193 -0
- package/dist/agents/workflow-pivot-loop.js +86 -25
- package/dist/cli/ink-app.js +116 -0
- package/dist/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js +1 -1
- package/dist/constraint-runtime/src/tools/PolymarketSDK/createOrder.js +1 -1
- package/dist/constraint-runtime/src/tools/PolymarketSDK/getOrders.js +1 -1
- package/dist/index.js +121 -142
- package/dist/network/known-peers.js +52 -8
- package/dist/security/tool-gate.js +4 -0
- package/dist/web/client.js +520 -17
- package/dist/web/index.html +6 -2
- package/dist/web/server.js +481 -29
- package/dist/web/style.css +14 -10
- package/dist/web/ui/message-renderer.js +42 -3
- package/dist/web/ui/step-timeline.js +5 -0
- package/package.json +9 -3
- package/bin/ipfs +0 -0
package/dist/web/client.js
CHANGED
|
@@ -60,6 +60,11 @@
|
|
|
60
60
|
const state = stateMap.get(timelineEl);
|
|
61
61
|
if (!state) return;
|
|
62
62
|
const { steps, expanded, showAll } = state;
|
|
63
|
+
if (steps.length === 0) {
|
|
64
|
+
timelineEl.style.display = "none";
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
timelineEl.style.display = "";
|
|
63
68
|
const titleEl = timelineEl.querySelector("[data-current-tool]");
|
|
64
69
|
if (titleEl) titleEl.textContent = computeTitle(steps);
|
|
65
70
|
const dotsEl = timelineEl.querySelector("[data-dots]");
|
|
@@ -789,7 +794,8 @@
|
|
|
789
794
|
hasStreamingText: () => hasStreamingText,
|
|
790
795
|
injectRecoveredText: () => injectRecoveredText,
|
|
791
796
|
replaceStreamingText: () => replaceStreamingText,
|
|
792
|
-
resetRendererState: () => resetRendererState
|
|
797
|
+
resetRendererState: () => resetRendererState,
|
|
798
|
+
seedDedupState: () => seedDedupState
|
|
793
799
|
});
|
|
794
800
|
function hasStreamingText() {
|
|
795
801
|
return streamingText.length > 0;
|
|
@@ -1000,11 +1006,44 @@
|
|
|
1000
1006
|
container.appendChild(content);
|
|
1001
1007
|
return container;
|
|
1002
1008
|
}
|
|
1009
|
+
function normalizeMarkdownTables(text) {
|
|
1010
|
+
if (!text || !text.includes("|")) return text;
|
|
1011
|
+
const lines = text.split("\n");
|
|
1012
|
+
const out = [];
|
|
1013
|
+
let inTable = false;
|
|
1014
|
+
let inCode = false;
|
|
1015
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1016
|
+
const line = lines[i];
|
|
1017
|
+
const trimmed = line.trim();
|
|
1018
|
+
if (/^\s*```/.test(line)) {
|
|
1019
|
+
inCode = !inCode;
|
|
1020
|
+
out.push(line);
|
|
1021
|
+
inTable = false;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (inCode) {
|
|
1025
|
+
out.push(line);
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (trimmed.startsWith("|") || /^\|?\s*:?-{2,}\s*(\|\s*:?-{2,}\s*)*\|?\s*$/.test(trimmed)) {
|
|
1029
|
+
out.push(line);
|
|
1030
|
+
inTable = true;
|
|
1031
|
+
} else {
|
|
1032
|
+
if (inTable && out.length > 0 && out[out.length - 1].trim() !== "") {
|
|
1033
|
+
out.push("");
|
|
1034
|
+
}
|
|
1035
|
+
inTable = false;
|
|
1036
|
+
out.push(line);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return out.join("\n");
|
|
1040
|
+
}
|
|
1003
1041
|
function buildBubble(text, type) {
|
|
1004
1042
|
const bubble = document.createElement("div");
|
|
1005
1043
|
bubble.className = `bubble bubble-${type}`;
|
|
1006
1044
|
const marked2 = window.marked;
|
|
1007
|
-
|
|
1045
|
+
const normalized = marked2 ? normalizeMarkdownTables(text) : text;
|
|
1046
|
+
bubble.innerHTML = marked2 ? marked2.parse(normalized) : escapeHtml(text);
|
|
1008
1047
|
return bubble;
|
|
1009
1048
|
}
|
|
1010
1049
|
function buildMessageActions(div, rawContent, ctx) {
|
|
@@ -1183,6 +1222,10 @@
|
|
|
1183
1222
|
scrollToBottomTimer = null;
|
|
1184
1223
|
}
|
|
1185
1224
|
}
|
|
1225
|
+
function seedDedupState(lastType, lastContent) {
|
|
1226
|
+
if (lastType === "user") lastUserCommand = lastContent || "";
|
|
1227
|
+
else if (lastType === "ai") lastAiContent = lastContent || "";
|
|
1228
|
+
}
|
|
1186
1229
|
var streamingMessageEl, streamingTextNode, streamingText, lastUserCommand, lastAiContent, stepEventBuffer, scrollToBottomTimer, MessageRenderer;
|
|
1187
1230
|
var init_message_renderer = __esm({
|
|
1188
1231
|
"src/web/ui/message-renderer.ts"() {
|
|
@@ -1204,7 +1247,8 @@
|
|
|
1204
1247
|
flushStepEventBuffer,
|
|
1205
1248
|
escapeHtml,
|
|
1206
1249
|
getMessagesContainerForCurrent,
|
|
1207
|
-
resetRendererState
|
|
1250
|
+
resetRendererState,
|
|
1251
|
+
seedDedupState
|
|
1208
1252
|
};
|
|
1209
1253
|
if (typeof window !== "undefined") {
|
|
1210
1254
|
window.MR = MessageRenderer;
|
|
@@ -1463,6 +1507,7 @@
|
|
|
1463
1507
|
var MR_hasStreamingText = () => _getMR().hasStreamingText?.() ?? false;
|
|
1464
1508
|
var MR_replaceStreamingText = (text) => _getMR().replaceStreamingText?.(text);
|
|
1465
1509
|
var MR_injectRecoveredText = (text, ctx) => _getMR().injectRecoveredText?.(text, ctx ?? getRendererCtx());
|
|
1510
|
+
var MR_seedDedupState = (lastType, lastContent) => _getMR().seedDedupState?.(lastType, lastContent);
|
|
1466
1511
|
var knownToolNames = /* @__PURE__ */ new Set();
|
|
1467
1512
|
function getRendererCtx() {
|
|
1468
1513
|
return {
|
|
@@ -1661,6 +1706,29 @@
|
|
|
1661
1706
|
|
|
1662
1707
|
`;
|
|
1663
1708
|
addMessage2(prefix + (msg.text || "(\u7A7A\u56DE\u590D)"), "ai", false, log);
|
|
1709
|
+
if (msg.channelId && msg.fromPublicKey) {
|
|
1710
|
+
try {
|
|
1711
|
+
const key = `bolloon.rcmCache.${msg.fromPublicKey}.${msg.channelId}`;
|
|
1712
|
+
let arr = [];
|
|
1713
|
+
try {
|
|
1714
|
+
const raw = localStorage.getItem(key);
|
|
1715
|
+
if (raw) arr = JSON.parse(raw);
|
|
1716
|
+
} catch {
|
|
1717
|
+
arr = [];
|
|
1718
|
+
}
|
|
1719
|
+
if (!Array.isArray(arr)) arr = [];
|
|
1720
|
+
const entry = { type: "ai", content: msg.text || "", timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "remote" };
|
|
1721
|
+
const dup = arr.some((m) => m.type === "ai" && m.content === entry.content);
|
|
1722
|
+
if (!dup) {
|
|
1723
|
+
arr.push(entry);
|
|
1724
|
+
try {
|
|
1725
|
+
localStorage.setItem(key, JSON.stringify(arr.slice(-200)));
|
|
1726
|
+
} catch {
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
} catch {
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1664
1732
|
}
|
|
1665
1733
|
log.scrollTop = log.scrollHeight;
|
|
1666
1734
|
} else {
|
|
@@ -1701,6 +1769,28 @@
|
|
|
1701
1769
|
thinkingEl.textContent = "\u{1F4AD} \u5BF9\u65B9\u6B63\u5728\u601D\u8003: " + (msg.partial || "").slice(-200);
|
|
1702
1770
|
log.scrollTop = log.scrollHeight;
|
|
1703
1771
|
}
|
|
1772
|
+
} else if (phase === "step") {
|
|
1773
|
+
const stepType = msg.stepType;
|
|
1774
|
+
if (stepType === "step_start" || stepType === "step_done" || stepType === "step_error") {
|
|
1775
|
+
handleStepEvent2({
|
|
1776
|
+
type: stepType,
|
|
1777
|
+
tool: msg.tool,
|
|
1778
|
+
content: msg.content,
|
|
1779
|
+
success: msg.success,
|
|
1780
|
+
output: msg.output,
|
|
1781
|
+
error: msg.error,
|
|
1782
|
+
args: msg.args
|
|
1783
|
+
});
|
|
1784
|
+
const thinkingEl = document.getElementById("rcm-thinking-live");
|
|
1785
|
+
if (thinkingEl && stepType === "step_start") {
|
|
1786
|
+
thinkingEl.textContent = `\u{1F527} \u5BF9\u65B9\u6B63\u5728\u8C03\u7528\u5DE5\u5177: ${msg.tool || "..."}`;
|
|
1787
|
+
} else if (thinkingEl && stepType === "step_done") {
|
|
1788
|
+
thinkingEl.textContent = `\u2705 \u5BF9\u65B9\u5DE5\u5177\u8C03\u7528\u5B8C\u6210: ${msg.tool || ""}`;
|
|
1789
|
+
} else if (thinkingEl && stepType === "step_error") {
|
|
1790
|
+
thinkingEl.textContent = `\u274C \u5BF9\u65B9\u5DE5\u5177\u8C03\u7528\u5931\u8D25: ${msg.tool || ""}`;
|
|
1791
|
+
}
|
|
1792
|
+
log.scrollTop = log.scrollHeight;
|
|
1793
|
+
}
|
|
1704
1794
|
}
|
|
1705
1795
|
} else if (msg.type === "cross-mention-received") {
|
|
1706
1796
|
const allModals = document.querySelectorAll('.rcm-mention-toast, [id^="rcm-log"]');
|
|
@@ -1715,7 +1805,20 @@
|
|
|
1715
1805
|
}
|
|
1716
1806
|
} else if (msg.type === "remote-channel-update") {
|
|
1717
1807
|
const peerId = msg.peerId;
|
|
1718
|
-
|
|
1808
|
+
let channels2 = msg.channels || [];
|
|
1809
|
+
const removedKey = `bolloon.removedRemoteChannels`;
|
|
1810
|
+
let removedSet = /* @__PURE__ */ new Set();
|
|
1811
|
+
try {
|
|
1812
|
+
removedSet = new Set(JSON.parse(localStorage.getItem(removedKey) || "[]"));
|
|
1813
|
+
} catch {
|
|
1814
|
+
}
|
|
1815
|
+
if (removedSet.size > 0) {
|
|
1816
|
+
const before = channels2.length;
|
|
1817
|
+
channels2 = channels2.filter((c) => !removedSet.has(`${peerId}::${c.id}`));
|
|
1818
|
+
if (channels2.length !== before) {
|
|
1819
|
+
console.log(`[v3] \u8FC7\u6EE4 ${before - channels2.length} \u4E2A\u5DF2\u5220\u9664\u7684\u8FDC\u7AEF channel (${peerId.substring(0, 8)}...)`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1719
1822
|
const peerName = msg.peerName || null;
|
|
1720
1823
|
let group = remoteChannels.find((g) => g.peerId === peerId);
|
|
1721
1824
|
if (!group) {
|
|
@@ -2217,6 +2320,7 @@
|
|
|
2217
2320
|
}
|
|
2218
2321
|
expandedAgents.add(channelId);
|
|
2219
2322
|
console.log("[selectChannel] \u9891\u9053:", channel.name, "session:", currentSessionId);
|
|
2323
|
+
updateSendToolsToggleVisibility();
|
|
2220
2324
|
} else {
|
|
2221
2325
|
console.warn("[selectChannel] channel \u4E0D\u5B58\u5728:", channelId);
|
|
2222
2326
|
if (channelNameEl) channelNameEl.textContent = safeChannelName("(channel \u5DF2\u5220\u9664)");
|
|
@@ -2261,6 +2365,10 @@
|
|
|
2261
2365
|
frag.appendChild(tmpContainer.firstChild);
|
|
2262
2366
|
}
|
|
2263
2367
|
container.appendChild(frag);
|
|
2368
|
+
if (dedupedMsgs.length > 0) {
|
|
2369
|
+
const lastMsg = dedupedMsgs[dedupedMsgs.length - 1];
|
|
2370
|
+
MR_seedDedupState(lastMsg.type, lastMsg.content);
|
|
2371
|
+
}
|
|
2264
2372
|
if (dedupedMsgs.length !== msgs.length) {
|
|
2265
2373
|
console.log(`[loadSession] \u53BB\u91CD ${msgs.length - dedupedMsgs.length} \u6761\u76F8\u90BB\u91CD\u590D\u6D88\u606F (${msgs.length} \u2192 ${dedupedMsgs.length})`);
|
|
2266
2374
|
}
|
|
@@ -2691,6 +2799,8 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2691
2799
|
channelDid,
|
|
2692
2800
|
attachments: attachmentsForSend
|
|
2693
2801
|
// 后端解析为 LLM contextHint
|
|
2802
|
+
// 2026-08-02: 本地对话不传 autoInvokeTools (工具开关只针对远程 P2P 对话),
|
|
2803
|
+
// 本地走 channel 自身 autoInvokeTools 配置
|
|
2694
2804
|
})
|
|
2695
2805
|
});
|
|
2696
2806
|
if (!res.ok) {
|
|
@@ -2703,6 +2813,38 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2703
2813
|
setSendMode("idle");
|
|
2704
2814
|
}
|
|
2705
2815
|
}
|
|
2816
|
+
var sendToolsEnabled = true;
|
|
2817
|
+
try {
|
|
2818
|
+
const saved = localStorage.getItem("bolloon.sendToolsEnabled");
|
|
2819
|
+
if (saved !== null) sendToolsEnabled = saved === "1";
|
|
2820
|
+
} catch {
|
|
2821
|
+
}
|
|
2822
|
+
var sendToolsToggleBtn = document.getElementById("send-tools-toggle");
|
|
2823
|
+
var sendToolsLabel = document.getElementById("send-tools-label");
|
|
2824
|
+
function updateSendToolsToggleUI() {
|
|
2825
|
+
if (!sendToolsToggleBtn || !sendToolsLabel) return;
|
|
2826
|
+
sendToolsLabel.textContent = sendToolsEnabled ? "\u5DE5\u5177:\u5F00" : "\u5DE5\u5177:\u5173";
|
|
2827
|
+
sendToolsToggleBtn.style.borderColor = sendToolsEnabled ? "var(--accent, #4f46e5)" : "var(--border)";
|
|
2828
|
+
sendToolsToggleBtn.style.color = sendToolsEnabled ? "var(--accent, #4f46e5)" : "var(--text-muted)";
|
|
2829
|
+
try {
|
|
2830
|
+
localStorage.setItem("bolloon.sendToolsEnabled", sendToolsEnabled ? "1" : "0");
|
|
2831
|
+
} catch {
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function updateSendToolsToggleVisibility() {
|
|
2835
|
+
if (!sendToolsToggleBtn) return;
|
|
2836
|
+
const ch = channels.find((c) => c.id === currentChannelId);
|
|
2837
|
+
const isRemote = !!(ch && ch.ownerPublicKey) || !ch;
|
|
2838
|
+
sendToolsToggleBtn.style.display = isRemote ? "flex" : "none";
|
|
2839
|
+
}
|
|
2840
|
+
if (sendToolsToggleBtn) {
|
|
2841
|
+
sendToolsToggleBtn.onclick = () => {
|
|
2842
|
+
sendToolsEnabled = !sendToolsEnabled;
|
|
2843
|
+
updateSendToolsToggleUI();
|
|
2844
|
+
if (typeof showSimpleToast === "function") showSimpleToast(sendToolsEnabled ? "\u{1F527} \u672C\u6B21\u53D1\u9001\u5C06\u542F\u7528\u5DE5\u5177\u8C03\u7528" : "\u{1F527} \u672C\u6B21\u53D1\u9001\u5C06\u7981\u7528\u5DE5\u5177\u8C03\u7528");
|
|
2845
|
+
};
|
|
2846
|
+
updateSendToolsToggleUI();
|
|
2847
|
+
}
|
|
2706
2848
|
function persistLastMessageToServer(type, content) {
|
|
2707
2849
|
if (!currentChannelId || !currentSessionId) return;
|
|
2708
2850
|
fetch(`/sessions/${currentChannelId}/${currentSessionId}`, {
|
|
@@ -2911,6 +3053,139 @@ ${data.error || "channel not found"}`, "error");
|
|
|
2911
3053
|
}, true);
|
|
2912
3054
|
refreshMentionChannels();
|
|
2913
3055
|
setInterval(refreshMentionChannels, 5e3);
|
|
3056
|
+
var SLASH_COMMANDS = [
|
|
3057
|
+
{ cmd: "plan", desc: "\u521B\u5EFA\u6267\u884C\u8BA1\u5212 (create_plan)", args: "\u76EE\u6807; \u6B65\u9AA41,\u6B65\u9AA42..." },
|
|
3058
|
+
{ cmd: "todo", desc: "\u52FE\u9009\u8BA1\u5212\u6B65\u9AA4\u5B8C\u6210 (update_plan)", args: "\u8BA1\u5212ID; \u6B65\u9AA4ID; done/blocked" },
|
|
3059
|
+
{ cmd: "review", desc: "\u5BA1\u67E5\u8BA1\u5212\u5B8C\u6210\u5EA6 (review_plan)", args: "\u8BA1\u5212ID; \u603B\u7ED3" },
|
|
3060
|
+
{ cmd: "task", desc: "\u521B\u5EFA\u4EFB\u52A1 (create_task)", args: "\u63CF\u8FF0" },
|
|
3061
|
+
{ cmd: "goal", desc: "\u6682\u505C\u76EE\u6807 (park_goal)", args: "\u76EE\u6807ID; \u539F\u56E0" },
|
|
3062
|
+
{ cmd: "skill", desc: "\u6C89\u6DC0\u6280\u80FD (create_skill)", args: "\u6280\u80FD\u540D; \u63CF\u8FF0; \u6B65\u9AA4" },
|
|
3063
|
+
{ cmd: "add-friend", desc: "\u6DFB\u52A0 P2P \u597D\u53CB", args: "\u516C\u94A5; \u5907\u6CE8" },
|
|
3064
|
+
{ cmd: "help", desc: "\u663E\u793A\u53EF\u7528\u547D\u4EE4", args: "" }
|
|
3065
|
+
];
|
|
3066
|
+
var slashDropdownEl = null;
|
|
3067
|
+
var slashHighlightIdx = 0;
|
|
3068
|
+
var slashAnchor = -1;
|
|
3069
|
+
var slashBlockEnd = -1;
|
|
3070
|
+
function getCurrentSlashQuery() {
|
|
3071
|
+
const pos = input.selectionStart || input.value.length;
|
|
3072
|
+
const before = input.value.slice(0, pos);
|
|
3073
|
+
const m = before.match(/\/([A-Za-z-]{0,20})$/);
|
|
3074
|
+
return m ? { query: m[1], anchor: pos - m[0].length } : null;
|
|
3075
|
+
}
|
|
3076
|
+
function closeSlashDropdown() {
|
|
3077
|
+
if (slashDropdownEl) {
|
|
3078
|
+
slashDropdownEl.remove();
|
|
3079
|
+
slashDropdownEl = null;
|
|
3080
|
+
}
|
|
3081
|
+
slashHighlightIdx = 0;
|
|
3082
|
+
slashAnchor = -1;
|
|
3083
|
+
slashBlockEnd = -1;
|
|
3084
|
+
}
|
|
3085
|
+
function applySlashCommand(cmdObj) {
|
|
3086
|
+
const anchor = slashAnchor;
|
|
3087
|
+
const blockEnd = slashBlockEnd >= 0 ? slashBlockEnd : anchor + 1 + (getCurrentSlashQuery()?.query || "").length;
|
|
3088
|
+
if (anchor < 0 || anchor > input.value.length || input.value[anchor] !== "/") {
|
|
3089
|
+
closeSlashDropdown();
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const before = input.value.slice(0, anchor);
|
|
3093
|
+
const after = input.value.slice(blockEnd);
|
|
3094
|
+
const insert = `/${cmdObj.cmd} `;
|
|
3095
|
+
input.value = before + insert + after;
|
|
3096
|
+
const newPos = before.length + insert.length;
|
|
3097
|
+
input.focus();
|
|
3098
|
+
input.setSelectionRange(newPos, newPos);
|
|
3099
|
+
closeSlashDropdown();
|
|
3100
|
+
if (cmdObj.args && typeof showSimpleToast === "function") {
|
|
3101
|
+
showSimpleToast(`\u{1F4A1} /${cmdObj.cmd} \u7528\u6CD5: ${cmdObj.args}`);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
function renderSlashDropdown(items) {
|
|
3105
|
+
if (!slashDropdownEl) {
|
|
3106
|
+
slashDropdownEl = document.createElement("div");
|
|
3107
|
+
slashDropdownEl.id = "slash-dropdown";
|
|
3108
|
+
slashDropdownEl.style.cssText = "position:fixed;background:#fff;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.15);max-height:240px;overflow-y:auto;z-index:10000;font-size:13px;min-width:280px;";
|
|
3109
|
+
document.body.appendChild(slashDropdownEl);
|
|
3110
|
+
}
|
|
3111
|
+
const rect = input.getBoundingClientRect();
|
|
3112
|
+
slashDropdownEl.style.left = rect.left + "px";
|
|
3113
|
+
slashDropdownEl.style.bottom = window.innerHeight - rect.top + 4 + "px";
|
|
3114
|
+
const headerHtml = `<div style="padding:6px 10px;background:#f9fafb;border-bottom:1px solid #e5e7eb;font-size:11px;color:#6b7280;display:flex;justify-content:space-between;align-items:center;">
|
|
3115
|
+
<span>\u26A1 \u547D\u4EE4 (\u56DE\u8F66\u9009\u4E2D \u2192 \u63D2\u5165\u8F93\u5165\u6846)</span>
|
|
3116
|
+
<span style="color:#9ca3af;">\u2191\u2193 \u79FB\u52A8 \xB7 Esc \u5173\u95ED</span>
|
|
3117
|
+
</div>`;
|
|
3118
|
+
if (items.length === 0) {
|
|
3119
|
+
slashDropdownEl.innerHTML = headerHtml + '<div style="padding:10px 12px;color:#6b7280;font-size:12px;">\u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4</div>';
|
|
3120
|
+
} else {
|
|
3121
|
+
const rows = items.map((c, i) => {
|
|
3122
|
+
const bg = i === slashHighlightIdx ? "#eff6ff" : "#fff";
|
|
3123
|
+
const borderLeft = i === slashHighlightIdx ? "3px solid #93c5fd" : "3px solid transparent";
|
|
3124
|
+
return `<div class="slash-item" data-idx="${i}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
|
|
3125
|
+
<span style="font-weight:600;color:#4f46e5;min-width:70px;">/${c.cmd}</span>
|
|
3126
|
+
<span style="flex:1;color:#374151;">${c.desc}</span>
|
|
3127
|
+
</div>`;
|
|
3128
|
+
}).join("");
|
|
3129
|
+
slashDropdownEl.innerHTML = headerHtml + rows;
|
|
3130
|
+
slashDropdownEl.querySelectorAll(".slash-item").forEach((el, i) => {
|
|
3131
|
+
el.onclick = () => applySlashCommand(items[i]);
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
function updateSlashDropdown() {
|
|
3136
|
+
const m = getCurrentSlashQuery();
|
|
3137
|
+
if (!m) {
|
|
3138
|
+
closeSlashDropdown();
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
if (slashAnchor === -1) slashAnchor = m.anchor;
|
|
3142
|
+
slashBlockEnd = m.anchor + 1 + m.query.length;
|
|
3143
|
+
const q = m.query.toLowerCase();
|
|
3144
|
+
const filtered = SLASH_COMMANDS.filter((c) => c.cmd.startsWith(q)).slice(0, 8);
|
|
3145
|
+
if (slashHighlightIdx >= filtered.length) slashHighlightIdx = 0;
|
|
3146
|
+
renderSlashDropdown(filtered);
|
|
3147
|
+
}
|
|
3148
|
+
var _origInputHandler = input.oninput;
|
|
3149
|
+
input.addEventListener("input", () => {
|
|
3150
|
+
const pos = input.selectionStart || input.value.length;
|
|
3151
|
+
const before = input.value.slice(0, pos);
|
|
3152
|
+
if (before.endsWith("/") || slashDropdownEl && getCurrentSlashQuery()) {
|
|
3153
|
+
updateSlashDropdown();
|
|
3154
|
+
} else if (!getCurrentMentionQuery()) {
|
|
3155
|
+
closeSlashDropdown();
|
|
3156
|
+
}
|
|
3157
|
+
});
|
|
3158
|
+
input.addEventListener("keydown", (e) => {
|
|
3159
|
+
if (!slashDropdownEl) return;
|
|
3160
|
+
const items = slashDropdownEl.querySelectorAll(".slash-item");
|
|
3161
|
+
if (e.key === "ArrowDown") {
|
|
3162
|
+
e.preventDefault();
|
|
3163
|
+
e.stopPropagation();
|
|
3164
|
+
if (items.length === 0) return;
|
|
3165
|
+
slashHighlightIdx = (slashHighlightIdx + 1) % items.length;
|
|
3166
|
+
const q = (getCurrentSlashQuery()?.query || "").toLowerCase();
|
|
3167
|
+
updateSlashDropdown();
|
|
3168
|
+
} else if (e.key === "ArrowUp") {
|
|
3169
|
+
e.preventDefault();
|
|
3170
|
+
e.stopPropagation();
|
|
3171
|
+
if (items.length === 0) return;
|
|
3172
|
+
slashHighlightIdx = (slashHighlightIdx - 1 + items.length) % items.length;
|
|
3173
|
+
updateSlashDropdown();
|
|
3174
|
+
} else if (e.key === "Enter" || e.key === "Tab") {
|
|
3175
|
+
if (items.length > 0) {
|
|
3176
|
+
e.preventDefault();
|
|
3177
|
+
e.stopPropagation();
|
|
3178
|
+
const q = (getCurrentSlashQuery()?.query || "").toLowerCase();
|
|
3179
|
+
const filtered = SLASH_COMMANDS.filter((c) => c.cmd.startsWith(q)).slice(0, 8);
|
|
3180
|
+
const cur = filtered[slashHighlightIdx];
|
|
3181
|
+
if (cur) applySlashCommand(cur);
|
|
3182
|
+
}
|
|
3183
|
+
} else if (e.key === "Escape") {
|
|
3184
|
+
e.preventDefault();
|
|
3185
|
+
e.stopPropagation();
|
|
3186
|
+
closeSlashDropdown();
|
|
3187
|
+
}
|
|
3188
|
+
}, true);
|
|
2914
3189
|
function setupMentionAutocomplete(inputEl) {
|
|
2915
3190
|
if (!inputEl || inputEl.__mentionBound) return;
|
|
2916
3191
|
inputEl.__mentionBound = true;
|
|
@@ -3395,6 +3670,53 @@ ${data.error || "channel not found"}`, "error");
|
|
|
3395
3670
|
if (nameEl) nameEl.textContent = identity.name || "\u533F\u540D";
|
|
3396
3671
|
if (didEl) didEl.textContent = identity.didShort ? `did:key:${identity.didShort}` : "";
|
|
3397
3672
|
if (letterEl) letterEl.textContent = letter;
|
|
3673
|
+
if (nameEl) {
|
|
3674
|
+
nameEl.onclick = () => {
|
|
3675
|
+
const current = nameEl.textContent || "";
|
|
3676
|
+
const input2 = document.createElement("input");
|
|
3677
|
+
input2.type = "text";
|
|
3678
|
+
input2.value = current;
|
|
3679
|
+
input2.maxLength = 40;
|
|
3680
|
+
input2.style.cssText = "width:100%;background:var(--bg);border:1px solid var(--accent);border-radius:4px;color:var(--text);font-size:12px;padding:2px 6px;";
|
|
3681
|
+
nameEl.replaceWith(input2);
|
|
3682
|
+
input2.focus();
|
|
3683
|
+
input2.select();
|
|
3684
|
+
const commit = async (save) => {
|
|
3685
|
+
const newName = input2.value.trim();
|
|
3686
|
+
if (save && newName && newName !== current) {
|
|
3687
|
+
try {
|
|
3688
|
+
const r = await fetch("/api/user/identity", {
|
|
3689
|
+
method: "PUT",
|
|
3690
|
+
headers: { "Content-Type": "application/json" },
|
|
3691
|
+
body: JSON.stringify({ name: newName })
|
|
3692
|
+
});
|
|
3693
|
+
if (r.ok) {
|
|
3694
|
+
const updated = await r.json();
|
|
3695
|
+
nameEl.textContent = updated.name || newName;
|
|
3696
|
+
const letterEl2 = document.getElementById("avatar-letter");
|
|
3697
|
+
if (letterEl2) letterEl2.textContent = (updated.name || "?").charAt(0).toUpperCase();
|
|
3698
|
+
if (typeof showSimpleToast === "function") showSimpleToast("\u2713 \u540D\u5B57\u5DF2\u66F4\u65B0");
|
|
3699
|
+
}
|
|
3700
|
+
} catch (e) {
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
if (!input2.isConnected) return;
|
|
3704
|
+
nameEl.textContent = input2.value.trim() || current;
|
|
3705
|
+
input2.replaceWith(nameEl);
|
|
3706
|
+
};
|
|
3707
|
+
input2.onblur = () => commit(true);
|
|
3708
|
+
input2.onkeydown = (e) => {
|
|
3709
|
+
if (e.key === "Enter") {
|
|
3710
|
+
e.preventDefault();
|
|
3711
|
+
input2.blur();
|
|
3712
|
+
}
|
|
3713
|
+
if (e.key === "Escape") {
|
|
3714
|
+
input2.blur();
|
|
3715
|
+
commit(false);
|
|
3716
|
+
}
|
|
3717
|
+
};
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3398
3720
|
} catch (e) {
|
|
3399
3721
|
}
|
|
3400
3722
|
}
|
|
@@ -4368,13 +4690,24 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4368
4690
|
style="background:transparent;border:1px solid var(--border);color:var(--text);cursor:pointer;width:22px;height:22px;border-radius:4px;font-size:12px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">\u{1F4E4}</button>
|
|
4369
4691
|
</div>
|
|
4370
4692
|
<div class="remote-peer-channels" style="margin-top:4px;margin-left:8px;">
|
|
4371
|
-
${peerChannels.length === 0 ? '<div style="font-size:10px;color:var(--text-muted);padding:2px 4px;">(\u5BF9\u65B9\u8FD8\u6CA1\u5206\u4EAB channel \u7ED9\u4F60)</div>' :
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4693
|
+
${peerChannels.length === 0 ? '<div style="font-size:10px;color:var(--text-muted);padding:2px 4px;">(\u5BF9\u65B9\u8FD8\u6CA1\u5206\u4EAB channel \u7ED9\u4F60)</div>' : (() => {
|
|
4694
|
+
let removedSet = /* @__PURE__ */ new Set();
|
|
4695
|
+
try {
|
|
4696
|
+
removedSet = new Set(JSON.parse(localStorage.getItem("bolloon.removedRemoteChannels") || "[]"));
|
|
4697
|
+
} catch {
|
|
4698
|
+
}
|
|
4699
|
+
const visible = peerChannels.filter((c) => !removedSet.has(`${peer.publicKey}::${c.id}`));
|
|
4700
|
+
if (visible.length === 0) return '<div style="font-size:10px;color:var(--text-muted);padding:2px 4px;">(\u5DF2\u5168\u90E8\u79FB\u9664)</div>';
|
|
4701
|
+
return visible.map((c) => `
|
|
4702
|
+
<div class="remote-channel-row" data-peer-id="${escapeHtml2(peer.publicKey)}" data-channel-id="${escapeHtml2(c.id)}"
|
|
4703
|
+
style="display:flex;align-items:center;gap:6px;padding:4px 6px;cursor:pointer;border-radius:4px;font-size:12px;">
|
|
4704
|
+
<span>\u{1F916}</span>
|
|
4705
|
+
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(safeChannelName(c.name, ""))}">${escapeHtml2(safeChannelName(c.name))}</span>
|
|
4706
|
+
<button class="remote-channel-del" data-peer-id="${escapeHtml2(peer.publicKey)}" data-channel-id="${escapeHtml2(c.id)}" title="\u4ECE\u672C\u5730\u79FB\u9664 (\u4E0D\u518D\u663E\u793A\u8BE5\u8FDC\u7AEF channel)"
|
|
4707
|
+
style="background:transparent;border:1px solid var(--border);color:var(--text-muted);cursor:pointer;width:20px;height:20px;border-radius:4px;font-size:11px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">\u{1F5D1}\uFE0F</button>
|
|
4708
|
+
</div>
|
|
4709
|
+
`).join("");
|
|
4710
|
+
})()}
|
|
4378
4711
|
</div>
|
|
4379
4712
|
</li>
|
|
4380
4713
|
`;
|
|
@@ -4396,6 +4729,33 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4396
4729
|
openRemoteChannelChat(peerId, channelId, channelName);
|
|
4397
4730
|
});
|
|
4398
4731
|
});
|
|
4732
|
+
list.querySelectorAll(".remote-channel-del").forEach((btn) => {
|
|
4733
|
+
btn.addEventListener("click", (e) => {
|
|
4734
|
+
e.stopPropagation();
|
|
4735
|
+
const peerId = btn.dataset.peerId;
|
|
4736
|
+
const channelId = btn.dataset.channelId;
|
|
4737
|
+
try {
|
|
4738
|
+
const key = "bolloon.removedRemoteChannels";
|
|
4739
|
+
let arr = [];
|
|
4740
|
+
try {
|
|
4741
|
+
arr = JSON.parse(localStorage.getItem(key) || "[]");
|
|
4742
|
+
} catch {
|
|
4743
|
+
arr = [];
|
|
4744
|
+
}
|
|
4745
|
+
if (!Array.isArray(arr)) arr = [];
|
|
4746
|
+
const entry = `${peerId}::${channelId}`;
|
|
4747
|
+
if (!arr.includes(entry)) arr.push(entry);
|
|
4748
|
+
localStorage.setItem(key, JSON.stringify(arr));
|
|
4749
|
+
} catch {
|
|
4750
|
+
}
|
|
4751
|
+
const group = remoteChannels.find((g) => g.peerId === peerId);
|
|
4752
|
+
if (group && Array.isArray(group.channels)) {
|
|
4753
|
+
group.channels = group.channels.filter((c) => c.id !== channelId);
|
|
4754
|
+
}
|
|
4755
|
+
renderRemoteChannels();
|
|
4756
|
+
showSimpleToast("\u{1F5D1}\uFE0F \u5DF2\u4ECE\u672C\u5730\u79FB\u9664\u8BE5\u8FDC\u7AEF channel (\u5BF9\u65B9\u91CD\u65B0\u5206\u4EAB\u540E\u4F1A\u518D\u6B21\u51FA\u73B0, \u9664\u975E\u5237\u65B0\u540E\u4ECD\u88AB\u8FC7\u6EE4)");
|
|
4757
|
+
});
|
|
4758
|
+
});
|
|
4399
4759
|
list.querySelectorAll(".remote-peer-header").forEach((row) => {
|
|
4400
4760
|
const shareBtn = row.querySelector(".peer-share-btn");
|
|
4401
4761
|
if (shareBtn) {
|
|
@@ -4465,6 +4825,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4465
4825
|
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;">${escapeHtml2(currentNotes)}</textarea>
|
|
4466
4826
|
</div>
|
|
4467
4827
|
<div class="friend-req-actions">
|
|
4828
|
+
<button id="epm-delete" class="friend-req-btn-deny" style="border-color:var(--danger,#e05d5d);color:var(--danger,#e05d5d);margin-right:auto;">\u{1F5D1}\uFE0F \u5220\u9664\u597D\u53CB</button>
|
|
4468
4829
|
<button id="epm-cancel" class="friend-req-btn-deny">\u53D6\u6D88</button>
|
|
4469
4830
|
<button id="epm-save" class="friend-req-btn-accept">\u4FDD\u5B58</button>
|
|
4470
4831
|
</div>
|
|
@@ -4474,6 +4835,30 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4474
4835
|
document.body.insertAdjacentHTML("beforeend", html);
|
|
4475
4836
|
const close = () => document.getElementById("edit-peer-modal")?.remove();
|
|
4476
4837
|
document.getElementById("epm-cancel").onclick = close;
|
|
4838
|
+
document.getElementById("epm-delete").onclick = async () => {
|
|
4839
|
+
if (!confirm(`\u786E\u5B9A\u5220\u9664\u597D\u53CB "${currentName}" \u5417\uFF1F
|
|
4840
|
+
\u5BF9\u65B9\u5206\u4EAB\u7ED9\u4F60\u7684 channel \u5C06\u4E0D\u518D\u663E\u793A\uFF0C\u4F60\u5206\u4EAB\u7ED9\u5BF9\u65B9\u7684 channel \u4E5F\u4F1A\u64A4\u56DE\u3002`)) return;
|
|
4841
|
+
try {
|
|
4842
|
+
const delKey = peerPublicKey || peerName;
|
|
4843
|
+
const r = await fetch(`/api/p2p-peers/${encodeURIComponent(delKey)}`, { method: "DELETE" });
|
|
4844
|
+
if (!r.ok) {
|
|
4845
|
+
const data = await r.json().catch(() => ({}));
|
|
4846
|
+
throw new Error(data.error || `HTTP ${r.status}`);
|
|
4847
|
+
}
|
|
4848
|
+
console.log("[v3] \u5220\u9664\u597D\u53CB\u6210\u529F:", currentName);
|
|
4849
|
+
showSimpleToast(`\u2705 \u5DF2\u5220\u9664 ${currentName}`);
|
|
4850
|
+
close();
|
|
4851
|
+
const r2 = await fetch("/api/p2p-peers");
|
|
4852
|
+
if (r2.ok) {
|
|
4853
|
+
const d2 = await r2.json();
|
|
4854
|
+
knownPeers = Array.isArray(d2.peers) ? d2.peers : [];
|
|
4855
|
+
}
|
|
4856
|
+
renderRemoteChannels();
|
|
4857
|
+
} catch (err) {
|
|
4858
|
+
console.error("[v3] \u5220\u9664\u597D\u53CB\u5931\u8D25:", err);
|
|
4859
|
+
alert("\u5220\u9664\u5931\u8D25: " + (err.message || err));
|
|
4860
|
+
}
|
|
4861
|
+
};
|
|
4477
4862
|
document.getElementById("epm-save").onclick = async () => {
|
|
4478
4863
|
const newName = document.getElementById("epm-name").value.trim() || currentName;
|
|
4479
4864
|
const newNotes = document.getElementById("epm-notes").value;
|
|
@@ -4601,6 +4986,10 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4601
4986
|
</div>
|
|
4602
4987
|
<div id="rcm-log" class="messages remote-chat-log"></div>
|
|
4603
4988
|
<div class="remote-chat-input-row">
|
|
4989
|
+
<button id="rcm-tools-toggle" class="remote-chat-tools-toggle" title="\u672C\u6B21\u53D1\u9001\u662F\u5426\u5141\u8BB8\u5BF9\u65B9\u8C03\u7528\u5DE5\u5177 (\u70B9\u51FB\u5207\u6362)"
|
|
4990
|
+
style="display:flex;align-items:center;gap:4px;background:transparent;border:1px solid var(--border,#444);color:var(--text-muted,#909088);border-radius:6px;padding:5px 9px;font-size:11px;cursor:pointer;white-space:nowrap;flex-shrink:0;">
|
|
4991
|
+
\u{1F527} <span id="rcm-tools-label">\u5DE5\u5177:\u5F00</span>
|
|
4992
|
+
</button>
|
|
4604
4993
|
<input id="rcm-input" type="text" placeholder="\u8F93\u5165\u6D88\u606F, \u53D1\u9001\u5230\u8FDC\u7AEF channel..." class="remote-chat-input">
|
|
4605
4994
|
<button id="rcm-send" class="remote-chat-btn-send">\u53D1\u9001</button>
|
|
4606
4995
|
</div>
|
|
@@ -4613,6 +5002,16 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4613
5002
|
const sendBtn2 = document.getElementById("rcm-send");
|
|
4614
5003
|
const thinkingEl = document.getElementById("rcm-thinking");
|
|
4615
5004
|
let historyRefreshTimer = null;
|
|
5005
|
+
const overlayEl = document.getElementById("remote-chat-modal");
|
|
5006
|
+
overlayEl.addEventListener("mousedown", (e) => {
|
|
5007
|
+
if (e.target === overlayEl) {
|
|
5008
|
+
if (historyRefreshTimer) {
|
|
5009
|
+
clearInterval(historyRefreshTimer);
|
|
5010
|
+
historyRefreshTimer = null;
|
|
5011
|
+
}
|
|
5012
|
+
overlayEl.remove();
|
|
5013
|
+
}
|
|
5014
|
+
});
|
|
4616
5015
|
document.getElementById("rcm-close").onclick = () => {
|
|
4617
5016
|
if (historyRefreshTimer) {
|
|
4618
5017
|
clearInterval(historyRefreshTimer);
|
|
@@ -4714,11 +5113,22 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4714
5113
|
log.scrollTop = log.scrollHeight;
|
|
4715
5114
|
}, 50);
|
|
4716
5115
|
}
|
|
5116
|
+
try {
|
|
5117
|
+
const cacheMsgs = msgs.map((m) => ({
|
|
5118
|
+
type: m.type === "user" ? "user" : "ai",
|
|
5119
|
+
content: m.content || "",
|
|
5120
|
+
timestamp: m.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
5121
|
+
source: m.source || "remote"
|
|
5122
|
+
}));
|
|
5123
|
+
writeRcmCache(cacheMsgs);
|
|
5124
|
+
} catch {
|
|
5125
|
+
}
|
|
4717
5126
|
}
|
|
4718
5127
|
const doSend = async () => {
|
|
4719
5128
|
const text = inputEl.value.trim();
|
|
4720
5129
|
if (!text) return;
|
|
4721
5130
|
append(text, "user");
|
|
5131
|
+
cacheRemoteMessage(peerPublicKey, channelId, { type: "user", content: text, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4722
5132
|
inputEl.value = "";
|
|
4723
5133
|
sendBtn2.disabled = true;
|
|
4724
5134
|
sendBtn2.textContent = "...";
|
|
@@ -4726,7 +5136,8 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4726
5136
|
const res = await fetch("/api/remote-channels/chat-send", {
|
|
4727
5137
|
method: "POST",
|
|
4728
5138
|
headers: { "Content-Type": "application/json" },
|
|
4729
|
-
|
|
5139
|
+
// 2026-08-02: 透传工具开关 (P2P 🔧 toggle, 只对本次远端消息生效)
|
|
5140
|
+
body: JSON.stringify({ targetPublicKey: peerPublicKey, channelId, text, autoInvokeTools: rcmToolsEnabled })
|
|
4730
5141
|
});
|
|
4731
5142
|
const data = await res.json();
|
|
4732
5143
|
if (!res.ok) throw new Error(data.error || "send failed");
|
|
@@ -4744,7 +5155,94 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4744
5155
|
setupMentionAutocomplete(inputEl);
|
|
4745
5156
|
inputEl.focus();
|
|
4746
5157
|
startV3GlobalSSE();
|
|
4747
|
-
|
|
5158
|
+
let rcmToolsEnabled = true;
|
|
5159
|
+
try {
|
|
5160
|
+
const saved = localStorage.getItem("bolloon.rcmToolsEnabled");
|
|
5161
|
+
if (saved !== null) rcmToolsEnabled = saved === "1";
|
|
5162
|
+
} catch {
|
|
5163
|
+
}
|
|
5164
|
+
const rcmToolsBtn = document.getElementById("rcm-tools-toggle");
|
|
5165
|
+
const rcmToolsLabel = document.getElementById("rcm-tools-label");
|
|
5166
|
+
function updateRcmToolsUI() {
|
|
5167
|
+
if (!rcmToolsLabel) return;
|
|
5168
|
+
rcmToolsLabel.textContent = rcmToolsEnabled ? "\u5DE5\u5177:\u5F00" : "\u5DE5\u5177:\u5173";
|
|
5169
|
+
if (rcmToolsBtn) {
|
|
5170
|
+
rcmToolsBtn.style.borderColor = rcmToolsEnabled ? "#4f46e5" : "var(--border,#444)";
|
|
5171
|
+
rcmToolsBtn.style.color = rcmToolsEnabled ? "#4f46e5" : "var(--text-muted,#909088)";
|
|
5172
|
+
}
|
|
5173
|
+
try {
|
|
5174
|
+
localStorage.setItem("bolloon.rcmToolsEnabled", rcmToolsEnabled ? "1" : "0");
|
|
5175
|
+
} catch {
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
if (rcmToolsBtn) {
|
|
5179
|
+
rcmToolsBtn.onclick = () => {
|
|
5180
|
+
rcmToolsEnabled = !rcmToolsEnabled;
|
|
5181
|
+
updateRcmToolsUI();
|
|
5182
|
+
};
|
|
5183
|
+
updateRcmToolsUI();
|
|
5184
|
+
}
|
|
5185
|
+
const rcmCacheKey = `bolloon.rcmCache.${peerPublicKey}.${channelId}`;
|
|
5186
|
+
const MAX_CACHE_MSGS = 200;
|
|
5187
|
+
function readRcmCache() {
|
|
5188
|
+
try {
|
|
5189
|
+
const raw = localStorage.getItem(rcmCacheKey);
|
|
5190
|
+
if (!raw) return [];
|
|
5191
|
+
const arr = JSON.parse(raw);
|
|
5192
|
+
return Array.isArray(arr) ? arr : [];
|
|
5193
|
+
} catch {
|
|
5194
|
+
return [];
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
function writeRcmCache(msgs) {
|
|
5198
|
+
try {
|
|
5199
|
+
const trimmed = Array.isArray(msgs) ? msgs.slice(-MAX_CACHE_MSGS) : [];
|
|
5200
|
+
localStorage.setItem(rcmCacheKey, JSON.stringify(trimmed));
|
|
5201
|
+
} catch {
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
function cacheRemoteMessage(pk, chId, msg) {
|
|
5205
|
+
const key = `bolloon.rcmCache.${pk}.${chId}`;
|
|
5206
|
+
let arr = [];
|
|
5207
|
+
try {
|
|
5208
|
+
const raw = localStorage.getItem(key);
|
|
5209
|
+
if (raw) arr = JSON.parse(raw);
|
|
5210
|
+
} catch {
|
|
5211
|
+
arr = [];
|
|
5212
|
+
}
|
|
5213
|
+
if (!Array.isArray(arr)) arr = [];
|
|
5214
|
+
const dup = arr.some((m) => m.type === msg.type && m.content === msg.content && m.timestamp === msg.timestamp);
|
|
5215
|
+
if (!dup) {
|
|
5216
|
+
arr.push(msg);
|
|
5217
|
+
try {
|
|
5218
|
+
localStorage.setItem(key, JSON.stringify(arr.slice(-MAX_CACHE_MSGS)));
|
|
5219
|
+
} catch {
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
const cached = readRcmCache();
|
|
5224
|
+
if (cached.length > 0) {
|
|
5225
|
+
log.innerHTML = "";
|
|
5226
|
+
for (const m of cached) {
|
|
5227
|
+
const type = m.type === "user" ? "user" : "ai";
|
|
5228
|
+
let prefix = "";
|
|
5229
|
+
if (m.type === "user") {
|
|
5230
|
+
prefix = m.source === "remote" ? `\u{1F310} \u8FDC\u7AEF\u8BBF\u5BA2
|
|
5231
|
+
|
|
5232
|
+
` : "";
|
|
5233
|
+
} else {
|
|
5234
|
+
prefix = m.source === "remote" ? `\u{1F916} \u8FDC\u7AEF LLM
|
|
5235
|
+
|
|
5236
|
+
` : "";
|
|
5237
|
+
}
|
|
5238
|
+
addMessage2(prefix + (m.content || ""), type, false, log, [], m.timestamp);
|
|
5239
|
+
}
|
|
5240
|
+
thinkingEl.style.display = "none";
|
|
5241
|
+
loadHistory(true);
|
|
5242
|
+
log.scrollTop = log.scrollHeight;
|
|
5243
|
+
} else {
|
|
5244
|
+
loadHistory(false);
|
|
5245
|
+
}
|
|
4748
5246
|
historyRefreshTimer = setInterval(() => loadHistory(true), 15e3);
|
|
4749
5247
|
}
|
|
4750
5248
|
var showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
|
|
@@ -4844,6 +5342,9 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4844
5342
|
<label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u7533\u8BF7\u6D88\u606F\uFF08\u53EF\u9009\uFF09</label>
|
|
4845
5343
|
<input id="afm-msg" type="text" value="\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB, \u5171\u4EAB channel \u534F\u4F5C"
|
|
4846
5344
|
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;margin-bottom:12px;">
|
|
5345
|
+
<label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u5907\u6CE8\uFF08\u81EA\u6211\u4ECB\u7ECD/\u6765\u6E90, \u5BF9\u65B9\u63A5\u53D7\u65F6\u4F1A\u770B\u5230, \u4FBF\u4E8E\u5206\u8FA8\uFF09</label>
|
|
5346
|
+
<textarea id="afm-note" rows="2" placeholder="\u5982: \u6211\u662F\u5C0F\u5251\u7684 Bolloon, \u6765\u81EA\u676D\u5DDE, \u60F3\u4E00\u8D77\u505A P2P \u667A\u80FD\u4F53\u534F\u4F5C\u6D4B\u8BD5"
|
|
5347
|
+
style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;margin-bottom:12px;"></textarea>
|
|
4847
5348
|
<div id="afm-status" style="display:none;font-size:12px;margin-bottom:8px;"></div>
|
|
4848
5349
|
<p style="margin:0;color:var(--text-muted);font-size:11px;">\u5BF9\u65B9\u9700\u5DF2\u542F\u52A8 Bolloon \u5E76\u5728\u7EBF. \u63A5\u53D7\u540E\u53CC\u65B9\u4E92\u52A0\u597D\u53CB, \u5BF9\u65B9\u5206\u4EAB\u7684 channel \u4F1A\u81EA\u52A8\u51FA\u73B0.</p>
|
|
4849
5350
|
</div>
|
|
@@ -4868,6 +5369,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4868
5369
|
const name = document.getElementById("afm-name").value.trim();
|
|
4869
5370
|
const publicKey = document.getElementById("afm-pk").value.trim();
|
|
4870
5371
|
const message = document.getElementById("afm-msg").value.trim();
|
|
5372
|
+
const note = document.getElementById("afm-note")?.value.trim() || "";
|
|
4871
5373
|
if (!publicKey) {
|
|
4872
5374
|
setStatus("\u8BF7\u7C98\u8D34\u5BF9\u65B9\u7684 publicKey", "#b91c1c");
|
|
4873
5375
|
return;
|
|
@@ -4886,7 +5388,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4886
5388
|
const res = await fetch("/api/friend-request", {
|
|
4887
5389
|
method: "POST",
|
|
4888
5390
|
headers: { "Content-Type": "application/json" },
|
|
4889
|
-
body: JSON.stringify({ targetPublicKey: publicKey, name: name || void 0, message: message || void 0 })
|
|
5391
|
+
body: JSON.stringify({ targetPublicKey: publicKey, name: name || void 0, message: message || void 0, note: note || void 0 })
|
|
4890
5392
|
});
|
|
4891
5393
|
const data = await res.json();
|
|
4892
5394
|
if (res.status === 502) {
|
|
@@ -4925,6 +5427,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4925
5427
|
}
|
|
4926
5428
|
function showFriendRequestModal(req) {
|
|
4927
5429
|
document.getElementById("friend-request-modal")?.remove();
|
|
5430
|
+
const reqNote = req.note || req.message || "";
|
|
4928
5431
|
const html = `
|
|
4929
5432
|
<div id="friend-request-modal" class="friend-req-overlay">
|
|
4930
5433
|
<div class="friend-req-shell">
|
|
@@ -4936,12 +5439,12 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4936
5439
|
</div>
|
|
4937
5440
|
</div>
|
|
4938
5441
|
<div class="friend-req-body">
|
|
4939
|
-
|
|
5442
|
+
${reqNote ? `<div style="margin:0 0 10px;padding:8px 10px;background:var(--bg-active);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--text);white-space:pre-wrap;word-break:break-word;">\u{1F4AC} ${escapeHtml2(reqNote)}</div>` : ""}
|
|
4940
5443
|
<p style="margin:0;color:var(--text-muted);font-size:11px;">\u63A5\u53D7\u540E: \u53CC\u65B9\u4E92\u52A0\u597D\u53CB, \u5BF9\u65B9\u5206\u4EAB\u7684 channel \u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 P2P \u597D\u53CB\u533A.</p>
|
|
4941
5444
|
</div>
|
|
4942
5445
|
<div class="friend-req-actions">
|
|
4943
5446
|
<button id="frm-deny" class="friend-req-btn-deny">\u62D2\u7EDD</button>
|
|
4944
|
-
<button id="frm-accept" class="friend-req-btn-accept">\
|
|
5447
|
+
<button id="frm-accept" class="friend-req-btn-accept" style="font-weight:700;">\u2705 \u4E00\u952E\u901A\u8FC7</button>
|
|
4945
5448
|
</div>
|
|
4946
5449
|
</div>
|
|
4947
5450
|
</div>
|
|
@@ -4955,7 +5458,7 @@ ${data.error || "channel not found"}`, "error");
|
|
|
4955
5458
|
const res = await fetch("/api/friend-accept", {
|
|
4956
5459
|
method: "POST",
|
|
4957
5460
|
headers: { "Content-Type": "application/json" },
|
|
4958
|
-
body: JSON.stringify({ fromPublicKey: req.fromPublicKey, name: req.fromName })
|
|
5461
|
+
body: JSON.stringify({ fromPublicKey: req.fromPublicKey, name: req.fromName, requestId: req.requestId })
|
|
4959
5462
|
});
|
|
4960
5463
|
const data = await res.json();
|
|
4961
5464
|
if (!res.ok) throw new Error(data.error || "accept failed");
|