@jesonliu/lark-claudecode-bridge 1.4.3 → 2.0.0
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/assets/web/css/main.css +766 -574
- package/assets/web/index.html +37 -17
- package/assets/web/js/auth.js +217 -0
- package/assets/web/js/bootstrap.js +36 -34
- package/assets/web/js/core.js +4 -3
- package/assets/web/js/main.js +79 -20
- package/assets/web/js/markdown.js +64 -0
- package/assets/web/js/pages/apps.js +338 -251
- package/assets/web/js/pages/chats.js +446 -0
- package/assets/web/js/pages/claude.js +243 -180
- package/assets/web/js/pages/mcp.js +59 -43
- package/assets/web/js/pages/memory.js +39 -43
- package/assets/web/js/pages/overview.js +344 -76
- package/assets/web/js/pages/permissions.js +40 -32
- package/assets/web/js/pages/plugins.js +51 -37
- package/assets/web/js/pages/skills.js +68 -92
- package/assets/web/js/pages/slash.js +31 -23
- package/assets/web/js/pages/workspaces.js +114 -73
- package/assets/web/js/theme.js +21 -18
- package/assets/web/js/ui.js +75 -32
- package/dist/bin/lcb.js +15 -0
- package/dist/bin/lcb.js.map +1 -1
- package/dist/gateway/feishu-gateway.d.ts +11 -0
- package/dist/gateway/feishu-gateway.js +5 -0
- package/dist/gateway/feishu-gateway.js.map +1 -1
- package/dist/gateway/gateway-registry.d.ts +16 -0
- package/dist/gateway/gateway-registry.js +10 -0
- package/dist/gateway/gateway-registry.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/web/chats-api.d.ts +77 -0
- package/dist/web/chats-api.js +475 -0
- package/dist/web/chats-api.js.map +1 -0
- package/dist/web/logs-api.d.ts +37 -0
- package/dist/web/logs-api.js +141 -0
- package/dist/web/logs-api.js.map +1 -0
- package/dist/web/server.js +125 -6
- package/dist/web/server.js.map +1 -1
- package/dist/web/webauth.d.ts +44 -0
- package/dist/web/webauth.js +177 -0
- package/dist/web/webauth.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// ============ MCP
|
|
1
|
+
// ============ MCP 管理 ============
|
|
2
2
|
// 后端:GET /api/mcp · POST /api/mcp/action(add/update/remove)· POST /api/mcp/check(状态探测)
|
|
3
3
|
// 四来源:bridge 自管(mcp/servers.json,页面管理 + 注入生效)/ 用户级·本机(~/.claude.json,展示+可删)/
|
|
4
|
-
// 项目级(工作区 .mcp.json,只读)/
|
|
4
|
+
// 项目级(工作区 .mcp.json,只读)/ 已装插件内(只读;未启用时标「(未启用)」)。
|
|
5
|
+
// 搜索只匹配「名称」列;过滤只切换行显隐不重建 DOM(兼容中文输入法组合输入)。
|
|
5
6
|
import { $, esc, toast, api } from '../core.js';
|
|
6
7
|
import { openDrawer, closeDrawer, confirmDialog } from '../ui.js';
|
|
7
8
|
|
|
@@ -15,33 +16,45 @@ const sourceTag = (s) => {
|
|
|
15
16
|
if (s.source === 'plugin') return `插件 · ${esc(s.pluginName || '?')}${s.pluginEnabled ? '' : '(未启用)'}`;
|
|
16
17
|
return SOURCE_TAG[s.source] || s.source;
|
|
17
18
|
};
|
|
19
|
+
const sourceTagClass = (s) => (s.source === 'bridge' ? 'info' : s.source === 'plugin' ? 'off' : 'ghost');
|
|
18
20
|
/** 来源是否可删(project / plugin 只读) */
|
|
19
21
|
const isDeletable = (s) => s.source !== 'project' && s.source !== 'plugin';
|
|
20
22
|
|
|
21
23
|
function renderMcp(el) {
|
|
22
24
|
el.innerHTML = `
|
|
23
|
-
<div class="
|
|
24
|
-
<h3>MCP Servers</h3>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
<div class="panel">
|
|
26
|
+
<header class="panel-head"><h3>MCP Servers</h3>
|
|
27
|
+
<div class="panel-actions">
|
|
28
|
+
<input type="search" id="mcpSearch" placeholder="过滤:名称…" style="width:190px;padding:4px 10px;font-size:12.5px">
|
|
29
|
+
<button class="btn sm" id="mcpRefresh">刷新</button>
|
|
30
|
+
<button class="btn primary sm" id="mcpAdd">+ 添加 MCP</button>
|
|
31
|
+
</div></header>
|
|
32
|
+
<div class="panel-body">
|
|
33
|
+
<table><thead><tr>
|
|
34
|
+
<th style="width:150px">名字</th><th>类型 / 连接</th><th style="width:150px">来源</th><th style="width:150px">状态 <span class="tip" data-tip="点击状态格检测该 server">ⓘ</span></th><th style="width:130px">操作</th>
|
|
35
|
+
</tr></thead>
|
|
36
|
+
<tbody id="mcpBody"><tr><td colspan="5" class="hint">加载中…</td></tr></tbody>
|
|
37
|
+
</table>
|
|
38
|
+
<div class="mini-note" id="mcpBridgePath" style="margin-top:10px"></div>
|
|
29
39
|
</div>
|
|
30
|
-
<
|
|
31
|
-
<table><thead><tr>
|
|
32
|
-
<th style="width:150px">名字</th><th>类型 / 连接</th><th style="width:130px">来源</th><th style="width:130px">状态</th><th style="width:130px"></th>
|
|
33
|
-
</tr></thead>
|
|
34
|
-
<tbody id="mcpBody"><tr><td colspan="5" class="desc">加载中…</td></tr></tbody>
|
|
35
|
-
</table>
|
|
36
|
-
<div class="desc" id="mcpBridgePath" style="margin-top:10px"></div>
|
|
40
|
+
<footer class="panel-foot">stdio 走 command + args(本地进程),http/sse 走 url(远程服务);页面添加落 bridge 自管目录、任务级热生效;本机 / 项目 / 插件来源只读(本机可删)。</footer>
|
|
37
41
|
</div>`;
|
|
38
42
|
$('#mcpRefresh').onclick = () => void loadMcp();
|
|
39
43
|
$('#mcpAdd').onclick = addMcp;
|
|
40
|
-
|
|
44
|
+
bindSearch();
|
|
41
45
|
void loadMcp();
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
/**
|
|
48
|
+
/** 搜索过滤:只匹配名称列;只切换 tr 显隐不重建 DOM;组合输入(中文 IME)期间暂停 */
|
|
49
|
+
function bindSearch() {
|
|
50
|
+
const inp = $('#mcpSearch');
|
|
51
|
+
let composing = false;
|
|
52
|
+
inp.addEventListener('compositionstart', () => { composing = true; });
|
|
53
|
+
inp.addEventListener('compositionend', () => { composing = false; filterMcpRows(); });
|
|
54
|
+
inp.oninput = () => { if (!composing) filterMcpRows(); };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 搜索过滤:遍历 tr.dataset.searchText 切换 hidden(关键词只命中名称) */
|
|
45
58
|
function filterMcpRows() {
|
|
46
59
|
const q = ($('#mcpSearch').value || '').trim().toLowerCase();
|
|
47
60
|
const body = $('#mcpBody');
|
|
@@ -63,7 +76,7 @@ async function loadMcp() {
|
|
|
63
76
|
try {
|
|
64
77
|
data = await api('GET', '/api/mcp');
|
|
65
78
|
} catch (e) {
|
|
66
|
-
body.innerHTML = `<tr><td colspan="5" class="
|
|
79
|
+
body.innerHTML = `<tr><td colspan="5" class="hint">加载失败:${esc(e.message)}</td></tr>`;
|
|
67
80
|
return;
|
|
68
81
|
}
|
|
69
82
|
const bp = $('#mcpBridgePath');
|
|
@@ -76,25 +89,26 @@ async function loadMcp() {
|
|
|
76
89
|
}
|
|
77
90
|
const servers = data.servers || [];
|
|
78
91
|
if (servers.length === 0) {
|
|
79
|
-
body.innerHTML = `<tr class="empty-row"><td colspan="5" class="
|
|
92
|
+
body.innerHTML = `<tr class="empty-row"><td colspan="5" class="empty">暂无 MCP server<br><span class="mini-note">可点右上「+ 添加 MCP」,或在终端用 claude mcp add 添加(本机用户级会出现在此列表)。</span></td></tr>`;
|
|
80
93
|
return;
|
|
81
94
|
}
|
|
82
95
|
body.innerHTML = '';
|
|
83
96
|
for (const s of servers) {
|
|
97
|
+
// 类型标签分色:stdio=本地进程(蓝)· http/sse=远程服务(橙)
|
|
84
98
|
const conn = s.config.type === 'http' || s.config.type === 'sse'
|
|
85
|
-
?
|
|
86
|
-
:
|
|
87
|
-
const tag = sourceTag(s);
|
|
99
|
+
? `<span class="tag warn">${esc(String(s.config.type))}</span> <span class="mono" style="font-size:11.5px">${esc(String(s.config.url ?? ''))}</span>`
|
|
100
|
+
: `<span class="tag info">${esc(String(s.config.type ?? 'stdio'))}</span> <span class="mono" style="font-size:11.5px">${esc(String(s.config.command ?? ''))}${s.config.args ? ' ' + esc(JSON.stringify(s.config.args)) : ''}</span>`;
|
|
88
101
|
const tr = document.createElement('tr');
|
|
89
|
-
|
|
102
|
+
// 搜索只匹配名称列
|
|
103
|
+
tr.dataset.searchText = String(s.name).toLowerCase();
|
|
90
104
|
tr.innerHTML = `
|
|
91
|
-
<td><
|
|
105
|
+
<td><b>${esc(s.name)}</b></td>
|
|
92
106
|
<td>${conn}</td>
|
|
93
|
-
<td><span class="
|
|
94
|
-
<td class="mcp-status" data-status-for="${esc(s.name)}"><span class="
|
|
107
|
+
<td><span class="tag ${sourceTagClass(s)}">${sourceTag(s)}</span></td>
|
|
108
|
+
<td class="mcp-status" data-status-for="${esc(s.name)}"><span class="mut">未检测</span></td>
|
|
95
109
|
<td class="ops">
|
|
96
110
|
<button class="btn sm" data-view="${esc(s.name)}">查看</button>
|
|
97
|
-
${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删除</button>` : '<span class="
|
|
111
|
+
${isDeletable(s) ? `<button class="btn sm danger" data-del="${esc(s.name)}">删除</button>` : '<span class="dim" style="font-size:11.5px">只读</span>'}
|
|
98
112
|
</td>`;
|
|
99
113
|
tr.querySelector('[data-view]').onclick = () => viewMcp(s);
|
|
100
114
|
const delBtn = tr.querySelector('[data-del]');
|
|
@@ -111,7 +125,7 @@ async function loadMcp() {
|
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
async function checkMcp(s, cell) {
|
|
114
|
-
cell.innerHTML = '<span class="
|
|
128
|
+
cell.innerHTML = '<span class="mut">检测中…</span>';
|
|
115
129
|
try {
|
|
116
130
|
const r = await api('POST', '/api/mcp/check', { name: s.name, source: s.source, workspaceName: s.workspaceName });
|
|
117
131
|
const map = {
|
|
@@ -119,7 +133,7 @@ async function checkMcp(s, cell) {
|
|
|
119
133
|
failed: `<span style="color:var(--err-tx)">✘ 异常</span>`,
|
|
120
134
|
unreachable: `<span style="color:var(--warn-tx)">⚠ 不可达</span>`,
|
|
121
135
|
};
|
|
122
|
-
cell.innerHTML = `${map[r.status] || esc(r.status)}<div class="
|
|
136
|
+
cell.innerHTML = `${map[r.status] || esc(r.status)}<div class="mini-note" style="white-space:normal">${esc(r.detail || '')}</div>`;
|
|
123
137
|
} catch (e) {
|
|
124
138
|
cell.innerHTML = `<span style="color:var(--err-tx)">检测失败</span>`;
|
|
125
139
|
toast(e.message, true);
|
|
@@ -147,30 +161,30 @@ function viewMcp(s) {
|
|
|
147
161
|
const missing = (s.missingEnv || []).includes(k) || ((s.resolvedEnv || {})[k] || '').includes('${');
|
|
148
162
|
const raw = String((s.config.env || {})[k] ?? '');
|
|
149
163
|
const cur = (s.resolvedEnv || {})[k] ?? raw;
|
|
150
|
-
return `<tr><td><code>${esc(k)}</code></td><td><code
|
|
164
|
+
return `<tr><td><code>${esc(k)}</code></td><td><code>${esc(raw)}</code></td>
|
|
151
165
|
<td>${missing ? `<span style="color:var(--err-tx)">未设置(${esc(cur)})</span>` : `<code>${esc(cur)}</code>`}</td></tr>`;
|
|
152
166
|
}).join('')
|
|
153
|
-
: `<tr><td colspan="3" class="
|
|
167
|
+
: `<tr><td colspan="3" class="mut">(无 env 配置)</td></tr>`;
|
|
154
168
|
const onlyRead = !isDeletable(s);
|
|
155
169
|
const editable = s.source === 'bridge'; // 后端 op:update 只写 bridge servers.json;machine-user 只能删
|
|
156
170
|
openDrawer({
|
|
157
171
|
title: `MCP · ${s.name}`,
|
|
158
172
|
bodyHtml: `
|
|
159
|
-
<div class="
|
|
160
|
-
${onlyRead ? `<div class="
|
|
161
|
-
${!editable && !onlyRead ? `<div class="
|
|
173
|
+
<div class="mini-note" style="margin:0 0 8px">来源:${sourceTag(s)} · 配置文件:<code>${esc(s.path)}</code></div>
|
|
174
|
+
${onlyRead ? `<div class="banner warn" style="margin:0 0 8px"><span>只读来源——此 MCP 由 ${s.source === 'plugin' ? '插件' : '项目'}自带,编辑请前往 ${s.source === 'plugin' ? '插件目录' : '对应工作区目录'}手动修改。</span></div>` : ''}
|
|
175
|
+
${!editable && !onlyRead ? `<div class="banner warn" style="margin:0 0 8px"><span>用户级·本机来源不支持页面编辑(可删除);如需修改请在 ~/.claude.json 手动改,或删除后到本页重新添加。</span></div>` : ''}
|
|
162
176
|
<div data-pane="view">
|
|
163
|
-
<label>配置(JSON 原文)</label>
|
|
177
|
+
<label class="fld">配置(JSON 原文)</label>
|
|
164
178
|
<textarea readonly rows="10" style="min-height:140px">${esc(JSON.stringify(s.config, null, 2))}</textarea>
|
|
165
179
|
</div>
|
|
166
180
|
<div data-pane="edit" style="display:none">
|
|
167
|
-
<label>配置(JSON,可编辑——env 也在其中改)</label>
|
|
181
|
+
<label class="fld">配置(JSON,可编辑——env 也在其中改)</label>
|
|
168
182
|
<textarea id="emJson" rows="12" style="min-height:180px">${esc(JSON.stringify(s.config, null, 2))}</textarea>
|
|
169
183
|
<div class="hint" style="margin-top:6px">保存后立即对新任务生效(bridge 自管 servers.json 每任务现读)。</div>
|
|
170
184
|
</div>
|
|
171
|
-
<label
|
|
185
|
+
<label class="fld" style="margin-top:12px">环境变量 <span class="tip" data-tip="当前值已合并:飞书应用 env › Claude 认证 env › settings.json env › 系统环境,支持 \${VAR:-默认值}">ⓘ</span></label>
|
|
172
186
|
<table><thead><tr><th style="width:130px">变量</th><th>配置值</th><th>当前值</th></tr></thead><tbody>${envRows}</tbody></table>`,
|
|
173
|
-
footHtml: `${editable ? '<button class="btn" data-act="edit">编辑</button><button class="btn primary" data-act="save" style="display:none">保存</button>' : ''}<button class="btn" data-act="close">关闭</button>`,
|
|
187
|
+
footHtml: `${editable ? '<button class="btn" data-act="edit">编辑</button><button class="btn primary" data-act="save" style="display:none">保存</button>' : ''}<span class="sp"></span><button class="btn" data-act="close">关闭</button>`,
|
|
174
188
|
onMount({ body, foot }) {
|
|
175
189
|
foot.querySelector('[data-act="close"]').onclick = () => closeDrawer();
|
|
176
190
|
const editBtn = foot.querySelector('[data-act="edit"]');
|
|
@@ -208,17 +222,17 @@ function addMcp() {
|
|
|
208
222
|
<button data-tab="json">JSON 配置</button>
|
|
209
223
|
</div>
|
|
210
224
|
<div data-panel="cmd">
|
|
211
|
-
<label>粘贴 claude mcp add 命令(自动解析 command / args / env / header / url)</label>
|
|
225
|
+
<label class="fld">粘贴 claude mcp add 命令(自动解析 command / args / env / header / url)</label>
|
|
212
226
|
<textarea id="amCmd" rows="4" placeholder='claude mcp add my-mcp -e API_KEY=xxx -- npx -y @scope/mcp-server claude mcp add my-mcp -t http -H "Authorization: Bearer xxx" https://example.com/mcp claude mcp add-json my-mcp {"type":"http","url":"https://…"}'></textarea>
|
|
213
|
-
<div class="
|
|
227
|
+
<div class="mini-note" style="margin-top:8px">名字取命令第 4 个参数;scope(-s)忽略——统一存 bridge 目录。</div>
|
|
214
228
|
</div>
|
|
215
229
|
<div data-panel="json" style="display:none">
|
|
216
|
-
<label>名字</label>
|
|
230
|
+
<label class="fld">名字</label>
|
|
217
231
|
<input type="text" id="amName" placeholder="my-mcp">
|
|
218
|
-
<label>配置 JSON</label>
|
|
232
|
+
<label class="fld">配置 JSON</label>
|
|
219
233
|
<textarea id="amJson" rows="6" placeholder='stdio:{"type":"stdio","command":"npx","args":["-y","mcp-server-foo"],"env":{"KEY":"val"}} http:{"type":"http","url":"https://example.com/mcp"}'></textarea>
|
|
220
234
|
</div>`,
|
|
221
|
-
footHtml: `<button class="btn" data-act="cancel">取消</button><button class="btn primary" data-act="save">添加</button>`,
|
|
235
|
+
footHtml: `<span class="sp"></span><button class="btn" data-act="cancel">取消</button><button class="btn primary" data-act="save">添加</button>`,
|
|
222
236
|
onMount({ body, foot }) {
|
|
223
237
|
body.querySelectorAll('.subtabs [data-tab]').forEach((b) => {
|
|
224
238
|
b.onclick = () => {
|
|
@@ -260,6 +274,8 @@ function addMcp() {
|
|
|
260
274
|
export const page = {
|
|
261
275
|
id: 'mcp',
|
|
262
276
|
title: 'MCP',
|
|
277
|
+
desc: '四个来源的 MCP server 聚合;bridge 来源页面可管理,任务级热生效',
|
|
278
|
+
legend: false,
|
|
263
279
|
icon: '<circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4M2 12h4m12 0h4M5 5l2.8 2.8m8.4 8.4L19 19M19 5l-2.8 2.8M7.8 16.2L5 19"/>',
|
|
264
280
|
render: renderMcp,
|
|
265
281
|
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// 配置读写走标准 doc 管道(saveDoc → PUT /api/config,secret 自动回填);测试连接走
|
|
10
10
|
// /api/memory/test-connection(表单现值,不落盘);安装/启停走 /api/memory/install|start|stop。
|
|
11
11
|
import { S, $, esc, api, toast, saveDoc, hooks, refresh } from '../core.js';
|
|
12
|
-
import { confirmDialog } from '../ui.js';
|
|
12
|
+
import { confirmDialog, openModal, closeModal } from '../ui.js';
|
|
13
13
|
|
|
14
14
|
/** 服务商预设(§6.8 填表助手:只预填已知值,全部可见可改)。预设只填主块(vlm);
|
|
15
15
|
* 向量块留空沿用主块——跨厂商组合时单独填向量端点/Key(P2 双端点拆分语义) */
|
|
@@ -532,7 +532,7 @@ function renderInstalled(el, { st, m, model, inst, state, meta, remoteConfigured
|
|
|
532
532
|
infoCell('server', '运行方式', m.mode === 'embedded'
|
|
533
533
|
? `LCB 受管${inst.managedRunning ? '' : ' <span class="hint">(未运行)</span>'}`
|
|
534
534
|
: `外部自管 <span class="hint">非 LCB 拉起</span>`),
|
|
535
|
-
|
|
535
|
+
// 运行时间不单独占格:上面「启动自举」已带(主人反馈 #2)
|
|
536
536
|
// 随 LCB 启动的自动拉起结果:成功只作留痕,失败把「手动开启指引」摆到状态区
|
|
537
537
|
// (自举失败不阻断 LCB,用户必须能从页面一眼看到该去哪手动起)
|
|
538
538
|
...(inst.autoStart ? [infoCell('clock', '启动自举', inst.autoStart.ready
|
|
@@ -732,49 +732,39 @@ function userRowHtml(u) {
|
|
|
732
732
|
function studioGuideCardHtml(rk, endpoint) {
|
|
733
733
|
const srcLabel = rk?.source === 'config' ? 'config.yaml 的 memory.api_key'
|
|
734
734
|
: rk?.source === 'ov-conf' ? '受管 ov.conf 的 server.root_api_key' : '';
|
|
735
|
-
const
|
|
736
|
-
? `<span
|
|
737
|
-
: '<span
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
<div style="font-size:13px;font-weight:700">${esc(title)} ${esc(badge)}</div>
|
|
741
|
-
<div class="hint" style="margin-top:6px;line-height:1.8">${body}</div>
|
|
742
|
-
<div style="margin-top:8px">${btn}</div>
|
|
743
|
-
</div>`;
|
|
744
|
-
return `<div class="card" style="margin-top:12px;border-left:3px solid var(--accent)">
|
|
735
|
+
const stateHtml = rk?.configured
|
|
736
|
+
? `<span class="tag ok">✓ 已配置</span> <span class="hint">来源:${esc(srcLabel)};查看行为会留痕</span>`
|
|
737
|
+
: '<span class="tag err">✗ 未配置</span> <span class="hint">请到「服务与配置」点「检查修复」重新生成</span>';
|
|
738
|
+
// 精简(主人反馈):hover 悬浮塞不下格式化长文 → ⓘ 改为「点击弹说明弹窗」,悬浮只留一句话
|
|
739
|
+
return `<div class="card" style="margin-top:12px">
|
|
745
740
|
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
|
746
741
|
<b style="font-size:13.5px">🌐 连接 OpenViking Studio(${esc((endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, ''))}/studio)</b>
|
|
742
|
+
<button class="btn sm ghost" id="memGuideInfo" title="Root 密钥与用户密钥的区别、如何进入某个机器人的记忆空间">ⓘ 连接说明</button>
|
|
747
743
|
<span style="flex:1"></span>
|
|
748
744
|
<button class="btn sm" id="memCopyRoot" title="复制 Root 密钥到剪贴板(查看行为留痕)" ${rk?.configured ? '' : 'disabled style="opacity:.5;cursor:not-allowed"'}>⧉ 复制 Root 密钥</button>
|
|
749
745
|
<button class="btn sm primary" id="memOpenStudio" title="复制 Root 密钥 → 打开 Studio 新标签 → 在「Root 或账号管理员 API 密钥」框 Ctrl+V 即可进入">打开 Studio 并复制</button>
|
|
750
746
|
</div>
|
|
751
|
-
<div style="
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
用 Root 进去只会看到管理员(default)视角,看不到各机器人的记忆。<br>
|
|
773
|
-
⚠️ <b>更不要填</b>「模型配置」里那个模型厂商 API Key(智谱等)——那是调用模型用的,与 OpenViking 鉴权无关。
|
|
774
|
-
</div>
|
|
775
|
-
<div class="hint" style="margin-top:8px;line-height:1.7">Root 密钥状态:${state}</div>
|
|
776
|
-
<div id="memRootStatus" class="hint" style="margin-top:4px;line-height:1.7"></div>
|
|
777
|
-
</div>`;
|
|
747
|
+
<div id="memRootStatus" class="hint" style="margin-top:6px;line-height:1.7"></div>
|
|
748
|
+
</div>
|
|
749
|
+
<template id="memGuideState">${stateHtml}</template>`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/** 「连接说明」弹窗(点击 ⓘ 打开;富文本排版,替代塞不下也看不清的 hover 长提示) */
|
|
753
|
+
function openStudioGuideModal() {
|
|
754
|
+
const stateHtml = document.getElementById('memGuideState')?.innerHTML || '';
|
|
755
|
+
openModal({
|
|
756
|
+
title: '连接 OpenViking Studio 说明',
|
|
757
|
+
width: 'min(560px,92vw)',
|
|
758
|
+
bodyHtml: `
|
|
759
|
+
<div class="sub-title">方式一 · 管理员(Root 密钥)</div>
|
|
760
|
+
<div class="desc" style="margin:0 0 4px">用于<b>管理</b>:列出用户、复制/重置他人密钥、账号设置。用它登录后,Studio 里的身份<b>固定显示为 <code>default</code> 且无法切换</b>——这是 Studio 的设计(管理员凭证不含具体用户身份),<b>不代表机器人记忆没隔离</b>。</div>
|
|
761
|
+
<div class="sub-title" style="margin-top:14px">方式二 · 查看某个机器人的记忆(该机器人的用户密钥)</div>
|
|
762
|
+
<div class="desc" style="margin:0 0 4px">在下方用户列表点那一行<b>「打开记忆空间」</b>:密钥自动进剪贴板并打开该身份的记忆页,粘贴一次即可进入。每个机器人一把独立密钥、一机一目录,数据物理隔离。</div>
|
|
763
|
+
<div class="banner warn" style="margin-top:12px"><span>⚠️ 两种登录态<b>不能互相切换</b>:看 A 机器人就用 A 的密钥进。<br>⚠️ <b>不要</b>把「模型配置」里的模型厂商 API Key(智谱等)填进 Studio——那是调模型用的,与鉴权无关,填了必 401。</span></div>
|
|
764
|
+
<div class="hint" style="margin-top:12px">Root 密钥状态:${stateHtml}</div>`,
|
|
765
|
+
footHtml: `<span class="sp"></span><button class="btn primary" id="mgOk">知道了</button>`,
|
|
766
|
+
});
|
|
767
|
+
$('#mgOk').onclick = closeModal;
|
|
778
768
|
}
|
|
779
769
|
|
|
780
770
|
/** Tab2 主渲染:一键初始化 + Studio 接入指引 + 用户列表(网页管理 / 机器人分组)+ 粒度向导 */
|
|
@@ -826,9 +816,9 @@ function renderUsersTab(el, { st, m }) {
|
|
|
826
816
|
${u.authInitNeeded ? '<button class="btn sm primary" id="memUsersInit">⚡ 一键初始化</button>' : '<button class="btn sm" id="memUsersInit">⚡ 一键初始化</button>'}
|
|
827
817
|
<button class="btn sm" id="memUsersRefresh">↻ 刷新</button>
|
|
828
818
|
</div>
|
|
829
|
-
<div class="desc" style="margin-top:6px"
|
|
830
|
-
|
|
831
|
-
${u.authInitNeeded ? `<div class="hint" style="margin-top:10px;padding:9px 14px;background:var(--warn-bg);color:var(--warn-tx);border-radius:10px">🔑
|
|
819
|
+
<div class="desc" style="margin-top:6px">网页与每个机器人各持一把独立密钥,页面脱敏显示、查看留痕。「打开记忆空间」= 密钥进剪贴板并自动打开该身份的记忆页。
|
|
820
|
+
<span class="tip" style="vertical-align:-1px" data-tip="密钥落盘已加密;1933 网页右上角身份显示的是技术 ID(OpenViking 暂不支持显示名),哪个 ID 对应哪台机器人以本页对照为准。">ⓘ</span></div>
|
|
821
|
+
${u.authInitNeeded ? `<div class="hint" style="margin-top:10px;padding:9px 14px;background:var(--warn-bg);color:var(--warn-tx);border-radius:10px">🔑 待初始化鉴权:网页端用户尚未创建,浏览器打开记忆空间会 401——点右上「⚡ 一键初始化」自动创建并签发密钥。</div>` : ''}
|
|
832
822
|
${u.users.length === 0 && !u.authInitNeeded ? '<div class="hint" style="margin-top:12px">暂无用户——点「⚡ 一键初始化」创建。</div>' : ''}
|
|
833
823
|
${studioGuideCardHtml(u.rootKey, m.endpoint)}
|
|
834
824
|
${group('网页管理', webUsers, '浏览器打开记忆空间用这一把(Studio 里的「用户 API 密钥」也用它)')}
|
|
@@ -853,7 +843,8 @@ const granularityCardHtml = (gran) => `
|
|
|
853
843
|
<label class="switch"><input type="checkbox" id="memGran" ${gran === 'bot-user' ? 'checked' : ''}><span class="track"></span></label>
|
|
854
844
|
</label>
|
|
855
845
|
</div>
|
|
856
|
-
<div class="hint" style="margin-top:8px;line-height:1.8">升级属<b>增量演进</b
|
|
846
|
+
<div class="hint" style="margin-top:8px;line-height:1.8">升级属<b>增量演进</b>:不影响既有密钥与记忆、会话不中断,仅为新聊天者追加独立签发。
|
|
847
|
+
<span class="tip" style="vertical-align:-1px" data-tip="建议触发条件(任一出现即升级):同一 bot 服务 2 人以上且内容敏感;出现「记忆是谁写的」追溯需求;部署走向团队共享。">ⓘ</span></div>
|
|
857
848
|
</div>`;
|
|
858
849
|
|
|
859
850
|
function bindUsersTabActions(el, { m }) {
|
|
@@ -925,6 +916,9 @@ function bindUsersTabActions(el, { m }) {
|
|
|
925
916
|
if (openStudioBtn) openStudioBtn.onclick = openStudio;
|
|
926
917
|
// 指引卡内的「复制 Root 密钥」(同一 handler,多入口)
|
|
927
918
|
el.querySelectorAll('[data-root-copy]').forEach((b) => { b.onclick = copyRoot; });
|
|
919
|
+
// ⓘ 连接说明 → 点击弹富文本弹窗(hover 塞不下长文案)
|
|
920
|
+
const guideBtn = $('#memGuideInfo');
|
|
921
|
+
if (guideBtn) guideBtn.onclick = openStudioGuideModal;
|
|
928
922
|
|
|
929
923
|
// 打开记忆空间(最终方案 §三 决策 5):密钥进剪贴板 → 自动新标签开 Web Studio 文件系统页
|
|
930
924
|
//(深链 /studio/filesystem 直落记忆目录树,避开默认的 VikingBot 标签——那是 OpenViking
|
|
@@ -1005,6 +999,8 @@ function bindUsersTabActions(el, { m }) {
|
|
|
1005
999
|
export const page = {
|
|
1006
1000
|
id: 'memory',
|
|
1007
1001
|
title: '记忆增强',
|
|
1002
|
+
desc: 'OpenViking 长期记忆:自动召回历史、归档提炼,按机器人命名空间隔离',
|
|
1003
|
+
legend: false,
|
|
1008
1004
|
icon: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/>',
|
|
1009
1005
|
render: renderMemory,
|
|
1010
1006
|
};
|