@modesto_ma/dsh-channels 0.5.0-beta.3 → 0.5.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -12
- package/docs/feishu-onboarding.md +1 -1
- package/lib/client.js +369 -94
- package/lib/client.js.map +1 -1
- package/lib/index.js +7549 -6353
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/controller.ts +32 -1
- package/src/analyze/contract.ts +121 -0
- package/src/analyze/launcher.ts +353 -0
- package/src/analyze/server.ts +201 -0
- package/src/cards/analyze.ts +116 -0
- package/src/cards/index.ts +1 -0
- package/src/cards/streaming.ts +58 -0
- package/src/client/api.ts +15 -0
- package/src/client/panel/access-card.tsx +96 -70
- package/src/client/panel/analyze-token-card.tsx +70 -0
- package/src/client/panel/index.tsx +72 -10
- package/src/client/panel/sessions-tab.tsx +7 -4
- package/src/client/panel/shared.tsx +12 -0
- package/src/client/panel/wiki-tab.tsx +62 -0
- package/src/config.ts +30 -4
- package/src/domain.ts +55 -0
- package/src/feishu/scopes.ts +13 -7
- package/src/feishu/tools.ts +3 -1
- package/src/health.ts +12 -0
- package/src/host/agent.ts +5 -1
- package/src/index.ts +1 -1
- package/src/interactions/pending.ts +11 -2
- package/src/routing/inbound.ts +57 -14
- package/src/service.ts +161 -28
- package/src/settings/contract.ts +37 -6
- package/src/settings/routes.ts +66 -14
- package/src/settings/surface.ts +65 -16
- package/src/streamer.ts +156 -7
- package/src/workspaces.ts +31 -0
package/lib/client.js
CHANGED
|
@@ -32,6 +32,8 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
const ACCESS_GROUPS_ROUTE = "/dsh-channels/access/groups";
|
|
33
33
|
/** 用户白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */
|
|
34
34
|
const ACCESS_USERS_ROUTE = "/dsh-channels/access/users";
|
|
35
|
+
/** 知识库白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */
|
|
36
|
+
const ACCESS_WIKI_USERS_ROUTE = "/dsh-channels/access/wiki-users";
|
|
35
37
|
/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */
|
|
36
38
|
const ACCESS_REQUESTS_ROUTE = "/dsh-channels/access/requests";
|
|
37
39
|
/**
|
|
@@ -68,6 +70,10 @@ window.__ModuleLoader__.load({
|
|
|
68
70
|
id: "access",
|
|
69
71
|
label: "访问授权"
|
|
70
72
|
},
|
|
73
|
+
{
|
|
74
|
+
id: "wiki",
|
|
75
|
+
label: "知识库"
|
|
76
|
+
},
|
|
71
77
|
{
|
|
72
78
|
id: "advanced",
|
|
73
79
|
label: "高级"
|
|
@@ -84,6 +90,7 @@ window.__ModuleLoader__.load({
|
|
|
84
90
|
model: ["model"],
|
|
85
91
|
workspace: ["workspace"],
|
|
86
92
|
access: ["access"],
|
|
93
|
+
wiki: ["wiki"],
|
|
87
94
|
advanced: ["streaming"]
|
|
88
95
|
};
|
|
89
96
|
/** Section heading for one field group inside its tab. */
|
|
@@ -94,7 +101,8 @@ window.__ModuleLoader__.load({
|
|
|
94
101
|
tools: "模型工具",
|
|
95
102
|
model: "模型与推理",
|
|
96
103
|
streaming: "流式卡片与日志",
|
|
97
|
-
access: "准入与授权"
|
|
104
|
+
access: "准入与授权",
|
|
105
|
+
wiki: "知识库工具"
|
|
98
106
|
};
|
|
99
107
|
/** Display labels for the panel header/cards (kept on the wire so the client owns no copy). */
|
|
100
108
|
const PANEL_COPY = {
|
|
@@ -178,6 +186,7 @@ window.__ModuleLoader__.load({
|
|
|
178
186
|
modelTabHelp: "飞书会话使用的模型服务商、模型、推理档位与 Agent 预设;下拉选项来自宿主机实时目录,保存后对新会话生效。",
|
|
179
187
|
workspaceTabHelp: "每个用户一个工作区(模型/工具产生文件的落点),每个话题在其中拥有独立工作目录;只影响之后新建的话题。入站附件默认存于 DSH 原生附件库,不占用话题目录。插件不会自动删除工作区或话题目录;过期目录可手动删除,话题下次对话时会重建空目录。",
|
|
180
188
|
accessTabHelp: "准入采用 fail-closed:管理员不受白名单限制,非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。",
|
|
189
|
+
wikiTabHelp: "知识库工具以应用身份访问「知识库范围」内的文档、电子表格和多维表格。只有管理员或知识库白名单里的用户发起的话题能用;每次打开、新建、修改仍需话题发起人在授权卡上确认。",
|
|
181
190
|
advancedTabHelp: "流式刷新等低层参数、Web 覆盖文件与只读信息。",
|
|
182
191
|
save: "保存更改",
|
|
183
192
|
saving: "保存中…",
|
|
@@ -216,6 +225,13 @@ window.__ModuleLoader__.load({
|
|
|
216
225
|
enabled: "已启用",
|
|
217
226
|
disabled: "已关闭",
|
|
218
227
|
credentialRemoveConfirm: "确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。",
|
|
228
|
+
analyzeTokenCard: "日志分析接口令牌",
|
|
229
|
+
analyzeTokenCardHelp: "日志平台调用分析接口时 Authorization: Bearer 后面的值。经 DSH 凭据服务写入本机凭据库,面板永不回显;保存或移除后下一次请求即生效,无需重连。未配置时接口对所有请求返回 503。",
|
|
230
|
+
analyzeTokenLabel: "访问令牌",
|
|
231
|
+
analyzeTokenPlaceholder: "填写新令牌(建议 32 位以上随机串)",
|
|
232
|
+
saveAnalyzeToken: "保存令牌",
|
|
233
|
+
analyzeTokenSaved: "令牌已保存,下一次请求生效",
|
|
234
|
+
analyzeTokenRemoved: "令牌已移除,接口将拒绝所有请求",
|
|
219
235
|
accessNotLoaded: "飞书连接建立后加载授权列表",
|
|
220
236
|
loading: "加载中…",
|
|
221
237
|
loadFailed: "设置面板加载失败",
|
|
@@ -263,6 +279,11 @@ window.__ModuleLoader__.load({
|
|
|
263
279
|
accessRequestAllowed: "已授权",
|
|
264
280
|
accessRequestDenied: "已忽略",
|
|
265
281
|
accessHints: "三种授权入口:群内 /auth 命令、入群与用户授权卡、本面板。撤销立即生效;白名单只约束非管理员(管理员在任何私聊/群聊都直接放行),非管理员需要群和用户两层同时命中。",
|
|
282
|
+
wikiUsersCard: "知识库白名单",
|
|
283
|
+
wikiUsersHelp: "与「访问授权」里的用户白名单相互独立:这里的用户(以及管理员)发起的话题可以使用知识库工具。也可以在飞书里发送 /auth wiki add @用户 或 /auth wiki remove @用户;移除立即生效。",
|
|
284
|
+
wikiUsersEmpty: "知识库白名单为空:目前只有管理员发起的话题能使用知识库工具。",
|
|
285
|
+
wikiUserAdded: "已加入知识库白名单",
|
|
286
|
+
wikiUserRemoved: "已移出知识库白名单",
|
|
266
287
|
accessChatIdHint: "chat_id 可从入群群信息卡或群内 /groupid 获取;user_id 可私聊机器人发送 /userid 获取。"
|
|
267
288
|
};
|
|
268
289
|
//#endregion
|
|
@@ -469,6 +490,17 @@ window.__ModuleLoader__.load({
|
|
|
469
490
|
async function removeAccessUser(userId) {
|
|
470
491
|
return mutateAccess(ACCESS_USERS_ROUTE, "DELETE", { userId });
|
|
471
492
|
}
|
|
493
|
+
/** Add one user `user_id` to the wiki whitelist. */
|
|
494
|
+
async function addWikiUser(userId, name) {
|
|
495
|
+
return mutateAccess(ACCESS_WIKI_USERS_ROUTE, "POST", name === void 0 || name === "" ? { userId } : {
|
|
496
|
+
userId,
|
|
497
|
+
name
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/** Remove one user from the wiki whitelist (takes effect on the next wiki tool call). */
|
|
501
|
+
async function removeWikiUser(userId) {
|
|
502
|
+
return mutateAccess(ACCESS_WIKI_USERS_ROUTE, "DELETE", { userId });
|
|
503
|
+
}
|
|
472
504
|
/** Settle one pending authorization request (same resolver as the Feishu card). */
|
|
473
505
|
async function resolveAccessRequest(token, decision) {
|
|
474
506
|
return mutateAccess(ACCESS_REQUESTS_ROUTE, "POST", {
|
|
@@ -1245,10 +1277,7 @@ window.__ModuleLoader__.load({
|
|
|
1245
1277
|
const { access } = panel;
|
|
1246
1278
|
const [groupChatId, setGroupChatId] = (0, react.useState)("");
|
|
1247
1279
|
const [groupName, setGroupName] = (0, react.useState)("");
|
|
1248
|
-
const [userUserId, setUserUserId] = (0, react.useState)("");
|
|
1249
|
-
const [userName, setUserName] = (0, react.useState)("");
|
|
1250
1280
|
const groups = access.groups;
|
|
1251
|
-
const users = access.users;
|
|
1252
1281
|
const requests = access.requests;
|
|
1253
1282
|
const working = busy === "access";
|
|
1254
1283
|
const disabled = busy !== null || locked || !access.loaded;
|
|
@@ -1259,13 +1288,6 @@ window.__ModuleLoader__.load({
|
|
|
1259
1288
|
setGroupName("");
|
|
1260
1289
|
}
|
|
1261
1290
|
};
|
|
1262
|
-
const submitUser = async () => {
|
|
1263
|
-
if (userUserId.trim() === "") return;
|
|
1264
|
-
if (await onAddUser(userUserId, userName)) {
|
|
1265
|
-
setUserUserId("");
|
|
1266
|
-
setUserName("");
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
1291
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1270
1292
|
className: "dsh-channels-cfg__card",
|
|
1271
1293
|
children: [
|
|
@@ -1371,77 +1393,15 @@ window.__ModuleLoader__.load({
|
|
|
1371
1393
|
})
|
|
1372
1394
|
]
|
|
1373
1395
|
}),
|
|
1374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
className: "dsh-channels-cfg__cardMeta",
|
|
1384
|
-
children: [users.length, " 个"]
|
|
1385
|
-
})]
|
|
1386
|
-
}),
|
|
1387
|
-
users.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1388
|
-
className: "dsh-channels-cfg__help",
|
|
1389
|
-
children: PANEL_COPY.accessUsersEmpty
|
|
1390
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1391
|
-
className: "dsh-channels-cfg__accessList",
|
|
1392
|
-
children: users.map((user) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1393
|
-
className: "dsh-channels-cfg__accessRow",
|
|
1394
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1395
|
-
className: "dsh-channels-cfg__accessMain",
|
|
1396
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: user.name ?? user.userId }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1397
|
-
className: "dsh-channels-cfg__accessMeta",
|
|
1398
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1399
|
-
className: "dsh-channels-cfg__mono",
|
|
1400
|
-
children: user.userId
|
|
1401
|
-
}), ` · ${accessSourceLabel(user.source)} · ${formatAccessDate(user.addedAt)}`]
|
|
1402
|
-
})]
|
|
1403
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1404
|
-
type: "button",
|
|
1405
|
-
className: "dsh-channels-cfg__btn",
|
|
1406
|
-
disabled,
|
|
1407
|
-
onClick: () => onRemoveUser(user.userId),
|
|
1408
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1409
|
-
})]
|
|
1410
|
-
}, user.userId))
|
|
1411
|
-
}),
|
|
1412
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1413
|
-
className: "dsh-channels-cfg__accessAdd",
|
|
1414
|
-
children: [
|
|
1415
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1416
|
-
type: "text",
|
|
1417
|
-
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1418
|
-
value: userUserId,
|
|
1419
|
-
placeholder: PANEL_COPY.accessUserIdPlaceholder,
|
|
1420
|
-
"aria-label": PANEL_COPY.accessUserIdPlaceholder,
|
|
1421
|
-
disabled,
|
|
1422
|
-
onChange: (event) => setUserUserId(event.target.value)
|
|
1423
|
-
}),
|
|
1424
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1425
|
-
type: "text",
|
|
1426
|
-
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1427
|
-
value: userName,
|
|
1428
|
-
placeholder: PANEL_COPY.accessUserNamePlaceholder,
|
|
1429
|
-
"aria-label": PANEL_COPY.accessUserNamePlaceholder,
|
|
1430
|
-
disabled,
|
|
1431
|
-
onChange: (event) => setUserName(event.target.value)
|
|
1432
|
-
}),
|
|
1433
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1434
|
-
type: "button",
|
|
1435
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1436
|
-
disabled: disabled || userUserId.trim() === "",
|
|
1437
|
-
onClick: () => {
|
|
1438
|
-
submitUser();
|
|
1439
|
-
},
|
|
1440
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAddUser
|
|
1441
|
-
})
|
|
1442
|
-
]
|
|
1443
|
-
})
|
|
1444
|
-
]
|
|
1396
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserWhitelist, {
|
|
1397
|
+
title: PANEL_COPY.accessUsersTitle,
|
|
1398
|
+
empty: PANEL_COPY.accessUsersEmpty,
|
|
1399
|
+
addLabel: PANEL_COPY.accessAddUser,
|
|
1400
|
+
users: access.users,
|
|
1401
|
+
disabled,
|
|
1402
|
+
working,
|
|
1403
|
+
onAdd: onAddUser,
|
|
1404
|
+
onRemove: onRemoveUser
|
|
1445
1405
|
}),
|
|
1446
1406
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1447
1407
|
className: "dsh-channels-cfg__accessSection",
|
|
@@ -1521,6 +1481,91 @@ window.__ModuleLoader__.load({
|
|
|
1521
1481
|
]
|
|
1522
1482
|
});
|
|
1523
1483
|
}
|
|
1484
|
+
/** One user whitelist (user `user_id` list with add/remove), shared by the access and wiki tabs. */
|
|
1485
|
+
function UserWhitelist(props) {
|
|
1486
|
+
const { title, empty, addLabel, users, disabled, working, onAdd, onRemove } = props;
|
|
1487
|
+
const [userUserId, setUserUserId] = (0, react.useState)("");
|
|
1488
|
+
const [userName, setUserName] = (0, react.useState)("");
|
|
1489
|
+
const submitUser = async () => {
|
|
1490
|
+
if (userUserId.trim() === "") return;
|
|
1491
|
+
if (await onAdd(userUserId, userName)) {
|
|
1492
|
+
setUserUserId("");
|
|
1493
|
+
setUserName("");
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1497
|
+
className: "dsh-channels-cfg__accessSection",
|
|
1498
|
+
children: [
|
|
1499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1500
|
+
className: "dsh-channels-cfg__labelRow",
|
|
1501
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1502
|
+
className: "dsh-channels-cfg__label",
|
|
1503
|
+
children: title
|
|
1504
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1505
|
+
className: "dsh-channels-cfg__cardMeta",
|
|
1506
|
+
children: [users.length, " 个"]
|
|
1507
|
+
})]
|
|
1508
|
+
}),
|
|
1509
|
+
users.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1510
|
+
className: "dsh-channels-cfg__help",
|
|
1511
|
+
children: empty
|
|
1512
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1513
|
+
className: "dsh-channels-cfg__accessList",
|
|
1514
|
+
children: users.map((user) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1515
|
+
className: "dsh-channels-cfg__accessRow",
|
|
1516
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1517
|
+
className: "dsh-channels-cfg__accessMain",
|
|
1518
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: user.name ?? user.userId }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1519
|
+
className: "dsh-channels-cfg__accessMeta",
|
|
1520
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1521
|
+
className: "dsh-channels-cfg__mono",
|
|
1522
|
+
children: user.userId
|
|
1523
|
+
}), ` · ${accessSourceLabel(user.source)} · ${formatAccessDate(user.addedAt)}`]
|
|
1524
|
+
})]
|
|
1525
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1526
|
+
type: "button",
|
|
1527
|
+
className: "dsh-channels-cfg__btn",
|
|
1528
|
+
disabled,
|
|
1529
|
+
onClick: () => onRemove(user.userId),
|
|
1530
|
+
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1531
|
+
})]
|
|
1532
|
+
}, user.userId))
|
|
1533
|
+
}),
|
|
1534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1535
|
+
className: "dsh-channels-cfg__accessAdd",
|
|
1536
|
+
children: [
|
|
1537
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1538
|
+
type: "text",
|
|
1539
|
+
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1540
|
+
value: userUserId,
|
|
1541
|
+
placeholder: PANEL_COPY.accessUserIdPlaceholder,
|
|
1542
|
+
"aria-label": PANEL_COPY.accessUserIdPlaceholder,
|
|
1543
|
+
disabled,
|
|
1544
|
+
onChange: (event) => setUserUserId(event.target.value)
|
|
1545
|
+
}),
|
|
1546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1547
|
+
type: "text",
|
|
1548
|
+
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1549
|
+
value: userName,
|
|
1550
|
+
placeholder: PANEL_COPY.accessUserNamePlaceholder,
|
|
1551
|
+
"aria-label": PANEL_COPY.accessUserNamePlaceholder,
|
|
1552
|
+
disabled,
|
|
1553
|
+
onChange: (event) => setUserName(event.target.value)
|
|
1554
|
+
}),
|
|
1555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1556
|
+
type: "button",
|
|
1557
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1558
|
+
disabled: disabled || userUserId.trim() === "",
|
|
1559
|
+
onClick: () => {
|
|
1560
|
+
submitUser();
|
|
1561
|
+
},
|
|
1562
|
+
children: working ? PANEL_COPY.accessWorking : addLabel
|
|
1563
|
+
})
|
|
1564
|
+
]
|
|
1565
|
+
})
|
|
1566
|
+
]
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1524
1569
|
//#endregion
|
|
1525
1570
|
//#region src/client/panel/access-tab.tsx
|
|
1526
1571
|
/**
|
|
@@ -2178,15 +2223,108 @@ window.__ModuleLoader__.load({
|
|
|
2178
2223
|
});
|
|
2179
2224
|
}
|
|
2180
2225
|
//#endregion
|
|
2226
|
+
//#region src/client/panel/analyze-token-card.tsx
|
|
2227
|
+
/**
|
|
2228
|
+
* 日志分析接口令牌编辑器:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示
|
|
2229
|
+
* 「已配置 / 未配置」,不回显已存值;保存或移除后下一次接口请求即生效,不触发重连。
|
|
2230
|
+
*
|
|
2231
|
+
* @module dsh-channels/client/panel/analyze-token-card
|
|
2232
|
+
*/
|
|
2233
|
+
function AnalyzeTokenCard(props) {
|
|
2234
|
+
const { panel, token } = props;
|
|
2235
|
+
const state = panel.config.credentials.analyzeToken;
|
|
2236
|
+
const disabled = token.busy !== null || token.locked || !state.writable;
|
|
2237
|
+
const issue = token.issues.find((item) => item.key === "analyzeToken");
|
|
2238
|
+
const inputClass = `dsh-channels-cfg__input${issue === void 0 ? "" : " dsh-channels-cfg__input--invalid"}`;
|
|
2239
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2240
|
+
className: "dsh-channels-cfg__card",
|
|
2241
|
+
children: [
|
|
2242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2243
|
+
className: "dsh-channels-cfg__cardHead",
|
|
2244
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2245
|
+
className: "dsh-channels-cfg__cardTitle",
|
|
2246
|
+
children: PANEL_COPY.analyzeTokenCard
|
|
2247
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2248
|
+
className: `dsh-channels-cfg__badge dsh-channels-cfg__badge--${state.configured ? "ok" : "muted"}`,
|
|
2249
|
+
children: state.configured ? PANEL_COPY.credentialConfigured : PANEL_COPY.credentialMissing
|
|
2250
|
+
})]
|
|
2251
|
+
}),
|
|
2252
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2253
|
+
className: "dsh-channels-cfg__help",
|
|
2254
|
+
children: PANEL_COPY.analyzeTokenCardHelp
|
|
2255
|
+
}),
|
|
2256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2257
|
+
className: "dsh-channels-cfg__field",
|
|
2258
|
+
children: [
|
|
2259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2260
|
+
className: "dsh-channels-cfg__label",
|
|
2261
|
+
children: PANEL_COPY.analyzeTokenLabel
|
|
2262
|
+
}),
|
|
2263
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2264
|
+
className: "dsh-channels-cfg__cred",
|
|
2265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2266
|
+
type: "password",
|
|
2267
|
+
className: inputClass,
|
|
2268
|
+
value: token.value,
|
|
2269
|
+
placeholder: PANEL_COPY.analyzeTokenPlaceholder,
|
|
2270
|
+
autoComplete: "new-password",
|
|
2271
|
+
"aria-label": PANEL_COPY.analyzeTokenLabel,
|
|
2272
|
+
disabled,
|
|
2273
|
+
onChange: (event) => token.onChange(event.target.value)
|
|
2274
|
+
}), state.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2275
|
+
type: "button",
|
|
2276
|
+
className: "dsh-channels-cfg__btn",
|
|
2277
|
+
disabled,
|
|
2278
|
+
onClick: token.onClear,
|
|
2279
|
+
children: token.busy === "credentials-clear" ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential
|
|
2280
|
+
}) : null]
|
|
2281
|
+
}),
|
|
2282
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2283
|
+
className: "dsh-channels-cfg__help",
|
|
2284
|
+
children: [
|
|
2285
|
+
PANEL_COPY.credentialsRef,
|
|
2286
|
+
":",
|
|
2287
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2288
|
+
className: "dsh-channels-cfg__mono",
|
|
2289
|
+
children: state.ref
|
|
2290
|
+
}),
|
|
2291
|
+
state.source === void 0 ? "" : ` · ${state.source}`
|
|
2292
|
+
]
|
|
2293
|
+
}),
|
|
2294
|
+
issue === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2295
|
+
className: "dsh-channels-cfg__fieldError",
|
|
2296
|
+
children: issue.message
|
|
2297
|
+
})
|
|
2298
|
+
]
|
|
2299
|
+
}),
|
|
2300
|
+
state.writable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2301
|
+
className: "dsh-channels-cfg__fieldError",
|
|
2302
|
+
children: PANEL_COPY.credentialReadOnly
|
|
2303
|
+
}),
|
|
2304
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2305
|
+
className: "dsh-channels-cfg__footer",
|
|
2306
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2307
|
+
type: "button",
|
|
2308
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
2309
|
+
onClick: token.onSave,
|
|
2310
|
+
disabled: disabled || token.value.trim() === "",
|
|
2311
|
+
children: token.busy === "credentials" ? PANEL_COPY.savingCredentials : PANEL_COPY.saveAnalyzeToken
|
|
2312
|
+
})
|
|
2313
|
+
})
|
|
2314
|
+
]
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
//#endregion
|
|
2181
2318
|
//#region src/client/panel/sessions-tab.tsx
|
|
2182
2319
|
/**
|
|
2183
2320
|
* 「会话与行为」tab:行为 + 内容与附件两组字段(私聊/群聊触发方式、
|
|
2184
|
-
*
|
|
2321
|
+
* 首次引导、权限切换、附件接收、日志分析接口)。表单字段完全由 Host 描述驱动;
|
|
2322
|
+
* 日志分析接口令牌是凭据,单独一张卡片。
|
|
2185
2323
|
*
|
|
2186
2324
|
* @module dsh-channels/client/panel/sessions-tab
|
|
2187
2325
|
*/
|
|
2188
2326
|
function SessionsTab(props) {
|
|
2189
|
-
const { form } = props;
|
|
2327
|
+
const { form, analyzeToken } = props;
|
|
2190
2328
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2191
2329
|
className: "dsh-channels-cfg__tabBody",
|
|
2192
2330
|
children: [
|
|
@@ -2198,6 +2336,74 @@ window.__ModuleLoader__.load({
|
|
|
2198
2336
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
|
|
2199
2337
|
form,
|
|
2200
2338
|
tab: "sessions"
|
|
2339
|
+
}),
|
|
2340
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnalyzeTokenCard, {
|
|
2341
|
+
panel: form.panel,
|
|
2342
|
+
token: analyzeToken
|
|
2343
|
+
})
|
|
2344
|
+
]
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/client/panel/wiki-tab.tsx
|
|
2349
|
+
/**
|
|
2350
|
+
* 「知识库」tab:知识库工具字段表单(读取/写入开关、知识库范围)+ 知识库
|
|
2351
|
+
* 白名单卡。表单保存走全局 draft,白名单增删是独立即时 mutation。
|
|
2352
|
+
*
|
|
2353
|
+
* @module dsh-channels/client/panel/wiki-tab
|
|
2354
|
+
*/
|
|
2355
|
+
function WikiTab(props) {
|
|
2356
|
+
const { form, accessNotice, accessError, onAddUser, onRemoveUser } = props;
|
|
2357
|
+
const { access } = form.panel;
|
|
2358
|
+
const disabled = form.busy !== null || form.locked || !access.loaded;
|
|
2359
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2360
|
+
className: "dsh-channels-cfg__tabBody",
|
|
2361
|
+
children: [
|
|
2362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabHelp, { text: PANEL_COPY.wikiTabHelp }),
|
|
2363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsCard, {
|
|
2364
|
+
form,
|
|
2365
|
+
groups: TAB_FIELD_GROUPS.wiki
|
|
2366
|
+
}),
|
|
2367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
|
|
2368
|
+
form,
|
|
2369
|
+
tab: "wiki"
|
|
2370
|
+
}),
|
|
2371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2372
|
+
className: "dsh-channels-cfg__card",
|
|
2373
|
+
children: [
|
|
2374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2375
|
+
className: "dsh-channels-cfg__cardHead",
|
|
2376
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2377
|
+
className: "dsh-channels-cfg__cardTitle",
|
|
2378
|
+
children: PANEL_COPY.wikiUsersCard
|
|
2379
|
+
})
|
|
2380
|
+
}),
|
|
2381
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2382
|
+
className: "dsh-channels-cfg__help",
|
|
2383
|
+
children: PANEL_COPY.wikiUsersHelp
|
|
2384
|
+
}),
|
|
2385
|
+
!access.loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, { text: PANEL_COPY.accessNotLoaded }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserWhitelist, {
|
|
2386
|
+
title: PANEL_COPY.wikiUsersCard,
|
|
2387
|
+
empty: PANEL_COPY.wikiUsersEmpty,
|
|
2388
|
+
addLabel: PANEL_COPY.accessAddUser,
|
|
2389
|
+
users: access.wikiUsers,
|
|
2390
|
+
disabled,
|
|
2391
|
+
working: form.busy === "access",
|
|
2392
|
+
onAdd: onAddUser,
|
|
2393
|
+
onRemove: onRemoveUser
|
|
2394
|
+
}),
|
|
2395
|
+
accessNotice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2396
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
2397
|
+
role: "status",
|
|
2398
|
+
"aria-live": "polite",
|
|
2399
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: accessNotice })
|
|
2400
|
+
}),
|
|
2401
|
+
accessError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2402
|
+
className: "dsh-channels-cfg__fieldError",
|
|
2403
|
+
role: "alert",
|
|
2404
|
+
children: accessError
|
|
2405
|
+
})
|
|
2406
|
+
]
|
|
2201
2407
|
})
|
|
2202
2408
|
]
|
|
2203
2409
|
});
|
|
@@ -2250,6 +2456,7 @@ window.__ModuleLoader__.load({
|
|
|
2250
2456
|
const [pollError, setPollError] = (0, react.useState)(null);
|
|
2251
2457
|
const [appIdInput, setAppIdInput] = (0, react.useState)("");
|
|
2252
2458
|
const [appSecretInput, setAppSecretInput] = (0, react.useState)("");
|
|
2459
|
+
const [analyzeTokenInput, setAnalyzeTokenInput] = (0, react.useState)("");
|
|
2253
2460
|
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
2254
2461
|
const [catalogBusy, setCatalogBusy] = (0, react.useState)(false);
|
|
2255
2462
|
const [accessNotice, setAccessNotice] = (0, react.useState)(null);
|
|
@@ -2278,6 +2485,7 @@ window.__ModuleLoader__.load({
|
|
|
2278
2485
|
setDraft({});
|
|
2279
2486
|
setAppIdInput("");
|
|
2280
2487
|
setAppSecretInput("");
|
|
2488
|
+
setAnalyzeTokenInput("");
|
|
2281
2489
|
} catch (error) {
|
|
2282
2490
|
setLoad({
|
|
2283
2491
|
phase: "error",
|
|
@@ -2664,18 +2872,19 @@ window.__ModuleLoader__.load({
|
|
|
2664
2872
|
setBusy(null);
|
|
2665
2873
|
}
|
|
2666
2874
|
}, [applyAccess]);
|
|
2667
|
-
|
|
2875
|
+
/** Add one user to a whitelist; resolves true when the input should clear. */
|
|
2876
|
+
const addUser = (0, react.useCallback)(async (add, userId, name, notice) => {
|
|
2668
2877
|
setBusy("access");
|
|
2669
2878
|
setAccessError(null);
|
|
2670
2879
|
setAccessNotice(null);
|
|
2671
2880
|
try {
|
|
2672
|
-
const result = await
|
|
2881
|
+
const result = await add(userId, name);
|
|
2673
2882
|
if (!result.ok) {
|
|
2674
2883
|
setAccessError(result.error);
|
|
2675
2884
|
return false;
|
|
2676
2885
|
}
|
|
2677
2886
|
applyAccess(result.access);
|
|
2678
|
-
setAccessNotice(
|
|
2887
|
+
setAccessNotice(notice);
|
|
2679
2888
|
return true;
|
|
2680
2889
|
} catch (error) {
|
|
2681
2890
|
setAccessError(messageOf(error));
|
|
@@ -2684,18 +2893,18 @@ window.__ModuleLoader__.load({
|
|
|
2684
2893
|
setBusy(null);
|
|
2685
2894
|
}
|
|
2686
2895
|
}, [applyAccess]);
|
|
2687
|
-
const
|
|
2896
|
+
const removeUser = (0, react.useCallback)(async (remove, userId, notice) => {
|
|
2688
2897
|
setBusy("access");
|
|
2689
2898
|
setAccessError(null);
|
|
2690
2899
|
setAccessNotice(null);
|
|
2691
2900
|
try {
|
|
2692
|
-
const result = await
|
|
2901
|
+
const result = await remove(userId);
|
|
2693
2902
|
if (!result.ok) {
|
|
2694
2903
|
setAccessError(result.error);
|
|
2695
2904
|
return;
|
|
2696
2905
|
}
|
|
2697
2906
|
applyAccess(result.access);
|
|
2698
|
-
setAccessNotice(
|
|
2907
|
+
setAccessNotice(notice);
|
|
2699
2908
|
} catch (error) {
|
|
2700
2909
|
setAccessError(messageOf(error));
|
|
2701
2910
|
} finally {
|
|
@@ -2759,6 +2968,40 @@ window.__ModuleLoader__.load({
|
|
|
2759
2968
|
setBusy(null);
|
|
2760
2969
|
}
|
|
2761
2970
|
}, [appIdInput, appSecretInput]);
|
|
2971
|
+
/** Save the analyze API token; it applies to the next request, no reconnect. */
|
|
2972
|
+
const onSaveAnalyzeToken = (0, react.useCallback)(async () => {
|
|
2973
|
+
if (analyzeTokenInput.trim() === "") return;
|
|
2974
|
+
setBusy("credentials");
|
|
2975
|
+
setNotice(null);
|
|
2976
|
+
try {
|
|
2977
|
+
const result = await saveCredentials({ analyzeToken: analyzeTokenInput });
|
|
2978
|
+
if (!result.ok) {
|
|
2979
|
+
setNotice({
|
|
2980
|
+
kind: "error",
|
|
2981
|
+
text: result.error,
|
|
2982
|
+
issues: result.issues
|
|
2983
|
+
});
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
setLoad({
|
|
2987
|
+
phase: "ready",
|
|
2988
|
+
panel: result.panel
|
|
2989
|
+
});
|
|
2990
|
+
setStatus(result.panel.status);
|
|
2991
|
+
setAnalyzeTokenInput("");
|
|
2992
|
+
setNotice({
|
|
2993
|
+
kind: "ok",
|
|
2994
|
+
text: PANEL_COPY.analyzeTokenSaved
|
|
2995
|
+
});
|
|
2996
|
+
} catch (error) {
|
|
2997
|
+
setNotice({
|
|
2998
|
+
kind: "error",
|
|
2999
|
+
text: messageOf(error)
|
|
3000
|
+
});
|
|
3001
|
+
} finally {
|
|
3002
|
+
setBusy(null);
|
|
3003
|
+
}
|
|
3004
|
+
}, [analyzeTokenInput]);
|
|
2762
3005
|
const onClearCredential = (0, react.useCallback)(async (field) => {
|
|
2763
3006
|
if (!(typeof window.confirm === "function" ? window.confirm(PANEL_COPY.credentialRemoveConfirm) : true)) return;
|
|
2764
3007
|
setBusy("credentials-clear");
|
|
@@ -2780,7 +3023,7 @@ window.__ModuleLoader__.load({
|
|
|
2780
3023
|
setStatus(result.panel.status);
|
|
2781
3024
|
setNotice({
|
|
2782
3025
|
kind: "ok",
|
|
2783
|
-
text: result.reconnect === "pending" ? PANEL_COPY.credentialRemovedPending : PANEL_COPY.credentialRemoved
|
|
3026
|
+
text: field === "analyzeToken" ? PANEL_COPY.analyzeTokenRemoved : result.reconnect === "pending" ? PANEL_COPY.credentialRemovedPending : PANEL_COPY.credentialRemoved
|
|
2784
3027
|
});
|
|
2785
3028
|
} catch (error) {
|
|
2786
3029
|
setNotice({
|
|
@@ -2862,6 +3105,26 @@ window.__ModuleLoader__.load({
|
|
|
2862
3105
|
onClearCredential,
|
|
2863
3106
|
onSaveCredentials
|
|
2864
3107
|
]);
|
|
3108
|
+
const analyzeToken = (0, react.useMemo)(() => ({
|
|
3109
|
+
value: analyzeTokenInput,
|
|
3110
|
+
issues,
|
|
3111
|
+
busy,
|
|
3112
|
+
locked: hostBusy,
|
|
3113
|
+
onChange: setAnalyzeTokenInput,
|
|
3114
|
+
onSave: () => {
|
|
3115
|
+
onSaveAnalyzeToken();
|
|
3116
|
+
},
|
|
3117
|
+
onClear: () => {
|
|
3118
|
+
onClearCredential("analyzeToken");
|
|
3119
|
+
}
|
|
3120
|
+
}), [
|
|
3121
|
+
analyzeTokenInput,
|
|
3122
|
+
busy,
|
|
3123
|
+
hostBusy,
|
|
3124
|
+
issues,
|
|
3125
|
+
onClearCredential,
|
|
3126
|
+
onSaveAnalyzeToken
|
|
3127
|
+
]);
|
|
2865
3128
|
if (load.phase === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2866
3129
|
className: "dsh-channels-cfg",
|
|
2867
3130
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -2921,7 +3184,10 @@ window.__ModuleLoader__.load({
|
|
|
2921
3184
|
onReconnect();
|
|
2922
3185
|
}
|
|
2923
3186
|
}) : null,
|
|
2924
|
-
activeTab === "sessions" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionsTab, {
|
|
3187
|
+
activeTab === "sessions" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionsTab, {
|
|
3188
|
+
form,
|
|
3189
|
+
analyzeToken
|
|
3190
|
+
}) : null,
|
|
2925
3191
|
activeTab === "model" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelTab, {
|
|
2926
3192
|
form,
|
|
2927
3193
|
catalogBusy
|
|
@@ -2935,14 +3201,23 @@ window.__ModuleLoader__.load({
|
|
|
2935
3201
|
onRemoveGroup: (chatId) => {
|
|
2936
3202
|
onRemoveAccessGroup(chatId);
|
|
2937
3203
|
},
|
|
2938
|
-
onAddUser:
|
|
3204
|
+
onAddUser: (userId, name) => addUser(addAccessUser, userId, name, PANEL_COPY.accessUserAdded),
|
|
2939
3205
|
onRemoveUser: (userId) => {
|
|
2940
|
-
|
|
3206
|
+
removeUser(removeAccessUser, userId, PANEL_COPY.accessUserRemoved);
|
|
2941
3207
|
},
|
|
2942
3208
|
onResolveRequest: (token, decision) => {
|
|
2943
3209
|
onResolveAccessRequest(token, decision);
|
|
2944
3210
|
}
|
|
2945
3211
|
}) : null,
|
|
3212
|
+
activeTab === "wiki" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WikiTab, {
|
|
3213
|
+
form,
|
|
3214
|
+
accessNotice,
|
|
3215
|
+
accessError,
|
|
3216
|
+
onAddUser: (userId, name) => addUser(addWikiUser, userId, name, PANEL_COPY.wikiUserAdded),
|
|
3217
|
+
onRemoveUser: (userId) => {
|
|
3218
|
+
removeUser(removeWikiUser, userId, PANEL_COPY.wikiUserRemoved);
|
|
3219
|
+
}
|
|
3220
|
+
}) : null,
|
|
2946
3221
|
activeTab === "advanced" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedTab, {
|
|
2947
3222
|
form,
|
|
2948
3223
|
onClear: () => {
|