@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/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.jsxs)("div", {
1375
- className: "dsh-channels-cfg__accessSection",
1376
- children: [
1377
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1378
- className: "dsh-channels-cfg__labelRow",
1379
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1380
- className: "dsh-channels-cfg__label",
1381
- children: PANEL_COPY.accessUsersTitle
1382
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
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
- * 首次引导、权限切换、附件接收)。表单字段完全由 Host 描述驱动。
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
- const onAddAccessUser = (0, react.useCallback)(async (userId, name) => {
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 addAccessUser(userId, name);
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(PANEL_COPY.accessUserAdded);
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 onRemoveAccessUser = (0, react.useCallback)(async (userId) => {
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 removeAccessUser(userId);
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(PANEL_COPY.accessUserRemoved);
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, { form }) : null,
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: onAddAccessUser,
3204
+ onAddUser: (userId, name) => addUser(addAccessUser, userId, name, PANEL_COPY.accessUserAdded),
2939
3205
  onRemoveUser: (userId) => {
2940
- onRemoveAccessUser(userId);
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: () => {