@roaming-ai/dsh-group-chat 0.2.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.
Files changed (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/client.js +3308 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +2583 -0
  7. package/lib/types/client/Bubble.d.ts +10 -0
  8. package/lib/types/client/Glyph.d.ts +12 -0
  9. package/lib/types/client/GroupChatPanel.d.ts +10 -0
  10. package/lib/types/client/GroupChatPanel.old.d.ts +10 -0
  11. package/lib/types/client/GroupChatSettingsSection.d.ts +15 -0
  12. package/lib/types/client/PermissionSelect.d.ts +13 -0
  13. package/lib/types/client/RoleDrawer.d.ts +17 -0
  14. package/lib/types/client/ThinkRow.d.ts +9 -0
  15. package/lib/types/client/ToolRow.d.ts +9 -0
  16. package/lib/types/client/api.d.ts +8 -0
  17. package/lib/types/client/components/AsidePanel.d.ts +31 -0
  18. package/lib/types/client/components/Bubble.d.ts +19 -0
  19. package/lib/types/client/components/ChatPanel.d.ts +57 -0
  20. package/lib/types/client/components/Composer.d.ts +48 -0
  21. package/lib/types/client/components/Glyph.d.ts +12 -0
  22. package/lib/types/client/components/GroupChatSettingsSection.d.ts +15 -0
  23. package/lib/types/client/components/MessageFlow.d.ts +15 -0
  24. package/lib/types/client/components/NavPanel.d.ts +40 -0
  25. package/lib/types/client/components/PermissionSelect.d.ts +13 -0
  26. package/lib/types/client/components/RoleDrawer.d.ts +17 -0
  27. package/lib/types/client/components/ThinkRow.d.ts +9 -0
  28. package/lib/types/client/components/ToolRow.d.ts +9 -0
  29. package/lib/types/client/components/index.d.ts +9 -0
  30. package/lib/types/client/components.d.ts +22 -0
  31. package/lib/types/client/drawer.d.ts +17 -0
  32. package/lib/types/client/glyph.d.ts +12 -0
  33. package/lib/types/client/hooks/index.d.ts +6 -0
  34. package/lib/types/client/hooks/useComposer.d.ts +27 -0
  35. package/lib/types/client/hooks/useFileSearch.d.ts +19 -0
  36. package/lib/types/client/hooks/useGroupChatState.d.ts +95 -0
  37. package/lib/types/client/hooks/useMentionState.d.ts +27 -0
  38. package/lib/types/client/index.d.ts +52 -0
  39. package/lib/types/client/lib/api.d.ts +8 -0
  40. package/lib/types/client/lib/model.d.ts +80 -0
  41. package/lib/types/client/lib/styles.d.ts +10 -0
  42. package/lib/types/client/lib/ui.d.ts +17 -0
  43. package/lib/types/client/model.d.ts +78 -0
  44. package/lib/types/client/panel.d.ts +10 -0
  45. package/lib/types/client/settings.d.ts +15 -0
  46. package/lib/types/client/styles.d.ts +10 -0
  47. package/lib/types/client/ui.d.ts +17 -0
  48. package/lib/types/client/utils/utils.d.ts +29 -0
  49. package/lib/types/client/utils.d.ts +22 -0
  50. package/lib/types/core/json.d.ts +9 -0
  51. package/lib/types/core/status.d.ts +18 -0
  52. package/lib/types/core/tools.d.ts +59 -0
  53. package/lib/types/core/types.d.ts +256 -0
  54. package/lib/types/host/actions.d.ts +34 -0
  55. package/lib/types/host/api/actions.d.ts +36 -0
  56. package/lib/types/host/api/http.d.ts +16 -0
  57. package/lib/types/host/api/index.d.ts +7 -0
  58. package/lib/types/host/api/routes.d.ts +11 -0
  59. package/lib/types/host/broadcast.d.ts +20 -0
  60. package/lib/types/host/conversation.d.ts +25 -0
  61. package/lib/types/host/engine/conversation.d.ts +25 -0
  62. package/lib/types/host/engine/index.d.ts +7 -0
  63. package/lib/types/host/engine/retitle.d.ts +20 -0
  64. package/lib/types/host/http.d.ts +16 -0
  65. package/lib/types/host/index.d.ts +8 -0
  66. package/lib/types/host/materials/index.d.ts +6 -0
  67. package/lib/types/host/materials/materials.d.ts +40 -0
  68. package/lib/types/host/materials.d.ts +32 -0
  69. package/lib/types/host/persistence/index.d.ts +6 -0
  70. package/lib/types/host/persistence/persistence.d.ts +31 -0
  71. package/lib/types/host/persistence/store.d.ts +65 -0
  72. package/lib/types/host/persistence.d.ts +31 -0
  73. package/lib/types/host/routes.d.ts +11 -0
  74. package/lib/types/host/service.d.ts +28 -0
  75. package/lib/types/host/state.d.ts +45 -0
  76. package/lib/types/host/store.d.ts +65 -0
  77. package/lib/types/host/tools/index.d.ts +6 -0
  78. package/lib/types/host/tools/tools.d.ts +28 -0
  79. package/lib/types/host/tools.d.ts +28 -0
  80. package/lib/types/index.d.ts +41 -0
  81. package/lib/types/mount-once.d.ts +9 -0
  82. package/lib/types/shared/file-mention-grammar.d.ts +37 -0
  83. package/lib/types/shared/file-mention-grammar.test.d.ts +5 -0
  84. package/package.json +98 -0
  85. package/src/client/GroupChatPanel.tsx +385 -0
  86. package/src/client/components/AsidePanel.tsx +188 -0
  87. package/src/client/components/Bubble.tsx +76 -0
  88. package/src/client/components/ChatPanel.tsx +214 -0
  89. package/src/client/components/Composer.tsx +238 -0
  90. package/src/client/components/Glyph.tsx +27 -0
  91. package/src/client/components/GroupChatSettingsSection.tsx +74 -0
  92. package/src/client/components/MessageFlow.tsx +97 -0
  93. package/src/client/components/NavPanel.tsx +249 -0
  94. package/src/client/components/PermissionSelect.tsx +159 -0
  95. package/src/client/components/RoleDrawer.tsx +186 -0
  96. package/src/client/components/ThinkRow.tsx +32 -0
  97. package/src/client/components/ToolRow.tsx +51 -0
  98. package/src/client/components/index.ts +10 -0
  99. package/src/client/hooks/index.ts +7 -0
  100. package/src/client/hooks/useComposer.ts +272 -0
  101. package/src/client/hooks/useFileSearch.ts +115 -0
  102. package/src/client/hooks/useGroupChatState.ts +214 -0
  103. package/src/client/hooks/useMentionState.ts +56 -0
  104. package/src/client/index.ts +110 -0
  105. package/src/client/lib/api.ts +23 -0
  106. package/src/client/lib/model.ts +119 -0
  107. package/src/client/lib/styles.ts +317 -0
  108. package/src/client/lib/ui.ts +27 -0
  109. package/src/client/react-dom-shim.d.ts +13 -0
  110. package/src/client/utils/utils.ts +104 -0
  111. package/src/core/json.ts +28 -0
  112. package/src/core/status.ts +33 -0
  113. package/src/core/tools.ts +41 -0
  114. package/src/core/types.ts +222 -0
  115. package/src/host/api/actions.ts +335 -0
  116. package/src/host/api/http.ts +80 -0
  117. package/src/host/api/index.ts +8 -0
  118. package/src/host/api/routes.ts +95 -0
  119. package/src/host/broadcast.ts +65 -0
  120. package/src/host/engine/conversation.ts +331 -0
  121. package/src/host/engine/index.ts +8 -0
  122. package/src/host/engine/retitle.ts +135 -0
  123. package/src/host/index.ts +9 -0
  124. package/src/host/materials/index.ts +7 -0
  125. package/src/host/materials/materials.ts +193 -0
  126. package/src/host/persistence/index.ts +7 -0
  127. package/src/host/persistence/persistence.ts +311 -0
  128. package/src/host/persistence/store.ts +375 -0
  129. package/src/host/service.ts +78 -0
  130. package/src/host/state.ts +85 -0
  131. package/src/host/tools/index.ts +7 -0
  132. package/src/host/tools/tools.ts +194 -0
  133. package/src/index.ts +99 -0
  134. package/src/mount-once.ts +49 -0
  135. package/src/shared/file-mention-grammar.test.ts +77 -0
  136. package/src/shared/file-mention-grammar.ts +82 -0
package/lib/client.js ADDED
@@ -0,0 +1,3308 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-group-chat",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
+ key = keys[i];
17
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
+ get: ((k) => from[k]).bind(null, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+ //#endregion
29
+ let react = require("react");
30
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
31
+ _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
32
+ let react_jsx_runtime = require("react/jsx-runtime");
33
+ let react_dom = require("react-dom");
34
+ let react_dom_client = require("react-dom/client");
35
+ //#region src/client/lib/styles.ts
36
+ /**
37
+ * 浏览器半样式:CSS 以内联字符串承载(刻意不迁移 CSS Modules 管线——
38
+ * 类名需保持原样,侧栏入口的 “newSession” 类名是任务看板等 DOM 注入式
39
+ * 面板识别“侧边栏导航点击”的跨插件标记,哈希化会破坏该约定)。
40
+ * 工厂执行时幂等注入 <style>,loader 卸载时按 data-plugin 摘除标签。
41
+ * @module dsh-group-chat/client/styles
42
+ */
43
+ /** 注入标签的唯一 id。 */
44
+ const TAG_ID = "dsh-group-chat/styles.css";
45
+ const CSS = [
46
+ ".dsgc-root{display:flex;height:100%;min-height:0;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary,inherit);font-size:var(--dsh-content-font-size,14px);container-type:inline-size;position:relative}",
47
+ ".dsgc-loading{padding:24px;color:var(--dsw-alias-label-tertiary,inherit);display:flex;gap:8px;align-items:center;opacity:0;animation:dsgc-loading-in .18s ease-out .15s forwards}",
48
+ "@keyframes dsgc-loading-in{to{opacity:1}}",
49
+ ".dsgc-loading svg{animation:dsgc-spin 1s linear infinite}",
50
+ "@keyframes dsgc-spin{to{transform:rotate(360deg)}}",
51
+ ".dsgc-nav{width:232px;flex:none;display:flex;flex-direction:column;gap:8px;border-right:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2));padding:12px 10px;min-height:0;min-width:0;transition:width .28s cubic-bezier(.16,1,.3,1),opacity .18s ease,padding .28s cubic-bezier(.16,1,.3,1),border-right-width .28s cubic-bezier(.16,1,.3,1)}",
52
+ ".dsgc-nav>*{flex:none}",
53
+ ".dsgc-nav.closed{width:0;opacity:0;padding-left:0;padding-right:0;border-right-width:0;visibility:hidden;transition:width .24s ease-in,opacity .16s ease-in,padding .24s ease-in,border-right-width .24s ease-in,visibility 0s .22s}",
54
+ ".dsgc-search{flex:none}",
55
+ ".dsgc-search input{font-size:13px}",
56
+ ".dsgc-search:focus-within{border-color:var(--dsw-alias-state-business-primary,#4f6ef7)}",
57
+ ".dsgc-tree{display:flex;flex-direction:column;gap:6px;flex:1;overflow-y:auto;min-height:0;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
58
+ ".dsgc-grow-row{display:flex;align-items:center;gap:2px;min-width:0;border:none;background:none;border-radius:6px;padding:5px 6px;color:var(--dsw-alias-label-secondary,inherit);transition:background-color .12s,color .12s;cursor:pointer;font:inherit;text-align:left}",
59
+ ".dsgc-grow-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit)}",
60
+ ".dsgc-grow-row.on{color:var(--dsw-alias-label-primary,inherit);font-weight:600}",
61
+ ".dsgc-twist{flex:none;width:16px;height:16px;border:none;background:none;color:inherit;cursor:pointer;padding:0;display:inline-flex;align-items:center;justify-content:center}",
62
+ ".dsgc-twist svg{transition:transform .16s ease}",
63
+ ".dsgc-twist.closed svg{transform:rotate(-90deg)}",
64
+ ".dsgc-gname{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
65
+ ".dsgc-nodeops{display:none;gap:2px;flex:none}",
66
+ ".dsgc-grow-row:hover .dsgc-nodeops,.dsgc-sess-row:hover .dsgc-nodeops{display:inline-flex}",
67
+ ".dsgc-opbtn{border:none;background:none;color:var(--dsw-alias-label-tertiary,inherit);cursor:pointer;padding:2px;border-radius:4px;line-height:0;transition:background-color .12s,color .12s}",
68
+ ".dsgc-opbtn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
69
+ ".dsgc-opbtn.danger{color:var(--dsw-alias-state-error-primary,#e5484d)}",
70
+ ".dsgc-sess-list{display:flex;flex-direction:column;gap:2px;margin:4px 0 2px 4px;padding-left:12px;border-left:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.15))}",
71
+ ".dsgc-sess-row{display:flex;align-items:center;gap:6px;min-width:0;border:none;background:none;border-radius:6px;padding:6px 8px;font-size:12.5px;line-height:1.5;color:var(--dsw-alias-label-secondary,inherit);transition:background-color .12s,color .12s;cursor:pointer;text-align:left;font-family:inherit}",
72
+ ".dsgc-sess-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit)}",
73
+ ".dsgc-sess-row.on{background:var(--dsw-alias-interactive-bg-active,rgba(128,128,128,.2));color:var(--dsw-alias-label-primary,inherit);font-weight:600}",
74
+ ".dsgc-sess-status{flex:none;width:8px;height:8px;display:inline-flex;justify-content:center;align-items:center}",
75
+ ".dsgc-sess-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
76
+ ".dsgc-addsess{border:none;background:none;color:var(--dsw-alias-label-tertiary,inherit);cursor:pointer;text-align:left;font-size:12px;line-height:1.5;padding:5px 8px;border-radius:6px;transition:background-color .12s,color .12s;font-family:inherit;display:flex;align-items:center;gap:5px;white-space:nowrap;overflow:hidden}",
77
+ ".dsgc-addsess:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit)}",
78
+ ".dsgc-rename{font:inherit;font-size:12.5px;min-width:0;flex:1;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-3,transparent));color:inherit;border-radius:6px;padding:1px 6px;outline:none;transition:border-color .16s}",
79
+ ".dsgc-rename:focus{border-color:var(--dsw-alias-state-business-primary,#4f6ef7)}",
80
+ ".dsgc-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);line-height:1.5;text-wrap:balance}",
81
+ ".dsgc-chat{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;position:relative}",
82
+ ".dsgc-seambtn{position:absolute;top:50%;transform:translateY(-50%);z-index:14;width:24px;height:56px;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,inherit);cursor:pointer;transition:background-color .12s,color .12s,border-color .12s,right .3s cubic-bezier(.16,1,.3,1)}",
83
+ ".dsgc-seambtn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l3,rgba(128,128,128,.4))}",
84
+ ".dsgc-seambtn.left{left:0;border-left:none;border-radius:0 8px 8px 0;box-shadow:3px 0 10px rgba(0,0,0,.07)}",
85
+ ".dsgc-seambtn.right{right:0;border-right:none;border-radius:8px 0 0 8px;box-shadow:-3px 0 10px rgba(0,0,0,.07)}",
86
+ ".dsgc-chathead{display:flex;gap:10px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2))}",
87
+ ".dsgc-sess-title{flex:none;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,inherit);max-width:30%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
88
+ ".dsgc-topic{flex:1;background:transparent;border:none;border-bottom:1px solid transparent;padding:2px;min-height:20px;color:var(--dsw-alias-label-secondary,inherit);font:inherit;outline:none;min-width:0;transition:color .12s,border-color .12s}",
89
+ ".dsgc-topic:hover{border-bottom-color:var(--dsw-alias-border-l2,rgba(128,128,128,.3))}",
90
+ ".dsgc-topic:focus{color:var(--dsw-alias-label-primary,inherit);border-bottom-color:var(--dsw-alias-state-business-primary,#4f6ef7)}",
91
+ ".dsgc-topic::placeholder{color:var(--dsw-alias-label-tertiary,inherit)}",
92
+ ".dsgc-chathead .dsgc-clearbtn{transition:background-color .12s,color .12s}",
93
+ ".dsgc-chathead .dsgc-clearbtn:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 10%,transparent);color:var(--dsw-alias-state-error-primary,#e5484d)}",
94
+ ".dsgc-chathead .dsgc-clearbtn:active:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 16%,transparent)}",
95
+ ".dsgc-msgs{flex:1;overflow-y:auto;padding:20px 24px 16px;display:flex;flex-direction:column;gap:16px;min-height:0;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
96
+ ".dsgc-msg{display:flex;gap:10px;align-items:flex-start;max-width:100%;animation:dsgc-msg-in .22s cubic-bezier(.16,1,.3,1)}",
97
+ ".dsgc-msg.mine{flex-direction:row-reverse}",
98
+ ".dsgc-avatar{width:28px;height:28px;box-sizing:border-box;border:2px solid var(--dsw-alias-border-l3,rgba(128,128,128,.4));border-radius:50%;background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;flex:none;margin-top:1px}",
99
+ ".dsgc-avatar.mine{border-color:transparent;background:var(--dsw-alias-button-info-fill,#4f6ef7);color:var(--dsw-alias-label-primary-foreground,#fff)}",
100
+ ".dsgc-msgbody{max-width:76%;min-width:0;display:flex;flex-direction:column;gap:5px}",
101
+ ".dsgc-msg.mine .dsgc-msgbody{align-items:flex-end}",
102
+ ".dsgc-msghead{display:flex;gap:6px;align-items:center;min-width:0;font-size:12px;color:var(--dsw-alias-label-secondary,inherit)}",
103
+ ".dsgc-msgname{font-weight:600;color:var(--dsw-alias-label-primary,inherit);white-space:nowrap}",
104
+ ".dsgc-msgmodel{background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.12));border-radius:999px;padding:0 7px;font-size:10.5px;font-weight:500;line-height:16px;color:var(--dsw-alias-label-secondary,inherit);white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}",
105
+ ".dsgc-msgtime{font-size:10.5px;color:var(--dsw-alias-label-tertiary,inherit);white-space:nowrap}",
106
+ ".dsgc-typing{background:linear-gradient(90deg,var(--dsw-static-deepseek-500,#4176e6) 0%,var(--dsw-static-deepseek-500,#4176e6) 40%,var(--dsw-static-deepseek-200,#d3e2ff) 50%,var(--dsw-static-deepseek-500,#4176e6) 60%,var(--dsw-static-deepseek-500,#4176e6) 100%);color:#0000;-webkit-text-fill-color:transparent;background-position:100% 0;background-size:250% 100%;-webkit-background-clip:text;background-clip:text;font-weight:600;animation:dsgc-typing-shimmer 1.2s linear infinite}",
107
+ "@keyframes dsgc-typing-shimmer{to{background-position:0 0}}",
108
+ ".dsgc-msgtext{background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.06));border:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.15));padding:10px 14px;border-radius:12px;line-height:1.6;text-align:left;color:var(--dsw-alias-label-primary,inherit);min-width:0;overflow-wrap:anywhere}",
109
+ ".dsgc-msg.mine .dsgc-msgtext{background:var(--dsw-alias-button-info-fill,#4f6ef7);border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff);white-space:pre-wrap;word-break:break-word}",
110
+ ".dsgc-msgtext.live{opacity:.92}",
111
+ ".dsgc-msg.live .dsgc-avatar{--presence-ring:color-mix(in srgb,var(--role-color,#4f6ef7) 22%,transparent);animation:dsgc-presence 1.8s ease-in-out infinite}",
112
+ ".dsgc-think{margin:2px 0}",
113
+ ".dsgc-think .dsgc-thinkrow{font-size:12px;color:var(--dsw-alias-label-secondary,inherit);border-radius:8px;padding:3px 8px;transition:background-color .12s}",
114
+ ".dsgc-think .dsgc-thinkrow:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.1))}",
115
+ ".dsgc-think .dsgc-thinksummary{color:var(--dsw-alias-label-tertiary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:44ch;font-size:11.5px;margin-left:6px}",
116
+ ".dsgc-think .dsgc-thinkbody{font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary,inherit);white-space:pre-wrap;word-break:break-word;border-left:2px solid var(--dsw-alias-border-l2,rgba(128,128,128,.25));margin:2px 0 4px;padding:2px 0 2px 10px;max-height:320px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent;animation:dsgc-think-in .2s ease-out}",
117
+ ".dsgc-pending{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,inherit)}",
118
+ ".dsgc-pending svg{flex:none}",
119
+ ".dsgc-pendingdots{display:inline-flex;gap:2px;align-items:center}",
120
+ ".dsgc-pendingdots i{width:3px;height:3px;border-radius:50%;background:currentColor;opacity:.3;animation:dsgc-dot-breathe 1.2s ease-in-out infinite}",
121
+ ".dsgc-pendingdots i:nth-child(2){animation-delay:.2s}",
122
+ ".dsgc-pendingdots i:nth-child(3){animation-delay:.4s}",
123
+ "@keyframes dsgc-dot-breathe{0%,100%{opacity:.25}50%{opacity:1}}",
124
+ ".dsgc-sysmsg{align-self:center;font-size:11.5px;color:var(--dsw-alias-label-secondary,inherit);background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.1));border-radius:999px;padding:2px 10px;text-align:center;max-width:90%;animation:dsgc-sys-in .24s ease-out}",
125
+ ".dsgc-tool{margin:2px 0}",
126
+ ".dsgc-tool .dsgc-toolrow{font-size:12px;color:var(--dsw-alias-label-secondary,inherit);border-radius:8px;padding:3px 8px;transition:background-color .12s}",
127
+ ".dsgc-tool .dsgc-toolrow:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.1))}",
128
+ ".dsgc-tool .dsgc-toolsummary{color:var(--dsw-alias-label-tertiary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:40ch;font-size:11.5px;margin-left:6px}",
129
+ ".dsgc-tool .dsgc-toolbody{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,inherit);white-space:pre-wrap;word-break:break-word;border-left:2px solid var(--dsw-alias-border-l2,rgba(128,128,128,.25));margin:2px 0 4px;padding:2px 0 2px 10px;max-height:260px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
130
+ ".dsgc-confirm{border:1px solid var(--dsw-alias-state-business-primary,#4f6ef7);border-radius:12px;background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.06));padding:10px 12px;display:flex;flex-direction:column;gap:8px;max-width:76%;animation:dsgc-fade-in .18s ease-out}",
131
+ ".dsgc-confirmtitle{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);display:flex;align-items:center;gap:6px}",
132
+ ".dsgc-confirmcmd{font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:8px;padding:8px 10px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
133
+ ".dsgc-confirmops{display:flex;gap:8px;justify-content:flex-end}",
134
+ ".dsgc-sysmsg.err{color:var(--dsw-alias-state-error-primary,#e5484d)}",
135
+ ".dsgc-clearnotes{margin:0;padding-left:18px;font-size:13px;line-height:1.8;color:var(--dsw-alias-label-secondary,inherit);display:flex;flex-direction:column;gap:2px}",
136
+ ".dsgc-err{font-size:12px;color:var(--dsw-alias-state-error-primary,#e5484d);animation:dsgc-fade-in .18s ease-out}",
137
+ ".dsgc-empty{margin:auto;display:flex;flex-direction:column;align-items:center;gap:10px;color:var(--dsw-alias-label-tertiary,inherit);text-align:center;max-width:40ch;padding:24px;animation:dsgc-fade-in .3s ease-out}",
138
+ ".dsgc-empty svg{opacity:.5}",
139
+ ".dsgc-empty .dsgc-emptytitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,inherit)}",
140
+ ".dsgc-empty .dsgc-hint{text-wrap:balance}",
141
+ ".dsgc-tobottom{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);z-index:12;animation:dsgc-rise-in .18s cubic-bezier(.16,1,.3,1)}",
142
+ ".dsgc-tobtn{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,inherit);border-radius:999px;padding:5px 12px;font:inherit;font-size:12px;line-height:1.4;cursor:pointer;box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.18));transition:color .12s,border-color .12s}",
143
+ ".dsgc-tobtn:hover{color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l3,rgba(128,128,128,.4))}",
144
+ ".dsgc-composer{padding:12px 16px 14px;display:flex;flex-direction:column;gap:10px;position:relative}",
145
+ ".dsgc-card{background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-3,transparent));--dsw-elevation-stroke-color:var(--dsw-alias-border-l2,rgba(128,128,128,.3));box-shadow:var(--dsw-elevation-soft,0 1px 3px rgba(0,0,0,.08));border-radius:22px;padding:8px 0 0;display:flex;flex-direction:column;gap:8px;position:relative}",
146
+ ".dsgc-edit{display:block;box-sizing:border-box;width:100%;min-height:36px;max-height:180px;overflow-y:auto;outline:none;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;font:inherit;font-size:13px;line-height:24px;color:var(--dsw-alias-label-primary,inherit);caret-color:var(--dsw-alias-state-business-primary,#4f6ef7);scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent;position:relative;z-index:1}",
147
+ ".dsgc-card .dsgc-edit{padding:6px 8px 0 14px}",
148
+ ".dsgc-ph{position:absolute;left:14px;top:6px;max-width:calc(100% - 22px);color:var(--dsw-alias-label-tertiary,inherit);pointer-events:none;user-select:none;font-size:13px;line-height:24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
149
+ ".dsgc-chipin{display:inline-flex;align-items:center;gap:4px;background:color-mix(in srgb,var(--role-color,#888) 15%,transparent);border-radius:999px;padding:1px 7px 1px 5px;margin:0 1px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary,inherit);white-space:nowrap;user-select:all}",
150
+ ".dsgc-card .dsgc-sendrow{padding:0 12px 10px}",
151
+ ".dsgc-stopbtn{background:var(--dsw-alias-state-error-primary,#e5484d);border-color:transparent;color:#fff;font-weight:600}",
152
+ ".dsgc-stopbtn:hover:not(:disabled){filter:brightness(1.08);color:#fff}",
153
+ ".dsgc-parts{display:flex;gap:6px;flex-wrap:wrap;align-items:center;row-gap:4px;height:24px}",
154
+ ".dsgc-partslabel{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);flex:none}",
155
+ ".dsgc-partchip{display:inline-flex;gap:6px;align-items:center;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:transparent;color:var(--dsw-alias-label-secondary,inherit);border-radius:999px;padding:2px 10px;font-size:12px;line-height:1.5;cursor:pointer;transition:background-color .12s,color .12s,border-color .12s;font-family:inherit}",
156
+ ".dsgc-partchip:hover{color:var(--dsw-alias-label-primary,inherit)}",
157
+ ".dsgc-partchip.on{background:var(--dsw-alias-interactive-bg-active,rgba(128,128,128,.18));color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l2,rgba(128,128,128,.3))}",
158
+ ".dsgc-partchip:disabled{opacity:.5;cursor:default}",
159
+ ".dsgc-chipdot{width:8px;height:8px;border-radius:50%;flex:none;display:inline-block}",
160
+ ".dsgc-sendrow{display:flex;gap:8px;align-items:center;justify-content:flex-end}",
161
+ ".dsgc-sendrow button{white-space:nowrap}",
162
+ ".dsgc-permtrigger{min-width:0;max-width:220px;height:28px;color:var(--dsw-alias-label-secondary,inherit);cursor:pointer;background:transparent;border:none;border-radius:999px;outline:none;align-items:center;gap:4px;padding:0 4px 0 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex;font-family:inherit;transition:background-color .12s,color .12s}",
163
+ ".dsgc-permtrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
164
+ ".dsgc-permtrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3,rgba(128,128,128,.4))}",
165
+ ".dsgc-permtrigger:disabled{color:var(--dsw-alias-label-dimmed,inherit);cursor:default}",
166
+ ".dsgc-permicon{flex:none;display:inline-flex}",
167
+ ".dsgc-permicon svg{width:14px;height:14px}",
168
+ ".dsgc-permlabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}",
169
+ ".dsgc-permchevron{color:var(--dsw-alias-label-caption,inherit);flex:none;transition:transform .12s;display:inline-flex}",
170
+ ".dsgc-permchevron.open{transform:rotate(180deg)}",
171
+ ".dsgc-rounds{display:flex;gap:2px;align-items:center;font-size:12px;color:var(--dsw-alias-label-secondary,inherit);flex:none;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:8px;padding:2px}",
172
+ ".dsgc-rounds .dsgc-roundbtn{border:none;background:none;color:var(--dsw-alias-label-secondary,inherit);cursor:pointer;padding:3px;border-radius:5px;line-height:0;transition:background-color .12s,color .12s}",
173
+ ".dsgc-rounds .dsgc-roundbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
174
+ ".dsgc-rounds .dsgc-roundbtn:disabled{opacity:.4;cursor:default}",
175
+ ".dsgc-rounds .dsgc-roundnum{width:26px;text-align:center;font-variant-numeric:tabular-nums;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
176
+ ".dsgc-mentionwrap{position:relative}",
177
+ ".dsgc-mention{animation:dsgc-pop-in .16s cubic-bezier(.16,1,.3,1);position:absolute;left:0;right:0;bottom:calc(100% + 6px);background:var(--dsw-alias-bg-layer-3,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:10px;box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.18));max-height:220px;overflow-y:auto;padding:4px;z-index:30;display:flex;flex-direction:column;gap:2px;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
178
+ ".dsgc-mentionitem{display:flex;align-items:center;gap:8px;border:none;background:none;color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:12.5px;text-align:left;cursor:pointer;padding:6px 8px;border-radius:6px;transition:background-color .12s,color .12s}",
179
+ ".dsgc-mentionitem.on,.dsgc-mentionitem:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
180
+ ".dsgc-mentionname{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}",
181
+ ".dsgc-mentionmodel{font-size:11px;color:var(--dsw-alias-label-tertiary,inherit)}",
182
+ ".dsgc-mentionhint{font-size:11px;color:var(--dsw-alias-label-tertiary,inherit);padding:2px 8px 1px}",
183
+ ".dsgc-mention-file .dsgc-mentionitem.dsgc-hint{cursor:default;color:var(--dsw-alias-label-tertiary,inherit);justify-content:center}",
184
+ ".dsgc-mention-file .dsgc-mentionitem.dsgc-hint:hover{background:none}",
185
+ ".dsgc-fileglyph{flex:none;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border-radius:5px}",
186
+ ".dsgc-fileglyph.dir{background:color-mix(in srgb,var(--dsw-static-amber-400,#e8912d) 16%,transparent)}",
187
+ ".dsgc-fileglyph.file{background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.12))}",
188
+ ".dsgc-filedrill{flex:none;display:inline-flex;color:var(--dsw-alias-label-tertiary,inherit)}",
189
+ ".dsgc-mentionitem.on .dsgc-filedrill,.dsgc-mentionitem:hover .dsgc-filedrill{color:var(--dsw-alias-label-secondary,inherit)}",
190
+ ".dsgc-chipin-file{background:color-mix(in srgb,var(--dsw-static-neutral-bluish-300,rgba(128,128,128,.55)) 18%,transparent)}",
191
+ ".dsgc-chipin-file[data-dir=\"1\"]{background:color-mix(in srgb,var(--dsw-static-amber-400,#e8912d) 18%,transparent)}",
192
+ ".dsgc-chipglyph{flex:none;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;overflow:hidden}",
193
+ ".dsgc-chipglyph svg{display:block}",
194
+ ".dsgc-aside{width:304px;flex:none;display:flex;flex-direction:column;gap:10px;border-left:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2));padding:12px;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent;transition:width .28s cubic-bezier(.16,1,.3,1),opacity .18s ease,padding .28s cubic-bezier(.16,1,.3,1),border-left-width .28s cubic-bezier(.16,1,.3,1)}",
195
+ ".dsgc-aside>*{flex:none}",
196
+ ".dsgc-aside.closed{width:0;opacity:0;padding-left:0;padding-right:0;border-left-width:0;visibility:hidden;transition:width .24s ease-in,opacity .16s ease-in,padding .24s ease-in,border-left-width .24s ease-in,visibility 0s .22s}",
197
+ ".dsgc-sec{display:flex;flex-direction:column;gap:8px;flex:none}",
198
+ ".dsgc-sechead{display:flex;align-items:center;gap:6px;font-weight:600;font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);padding-top:2px}",
199
+ ".dsgc-sechead .dsgc-secspacer{flex:1}",
200
+ ".dsgc-seccount{font-weight:500;color:var(--dsw-alias-label-tertiary,inherit)}",
201
+ ".dsgc-roles{display:flex;flex-direction:column;gap:8px}",
202
+ ".dsgc-role{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-3,transparent);border-radius:12px;padding:10px 12px;display:flex;flex-direction:column;gap:7px;transition:border-color .16s,background-color .16s;list-style:none;text-align:left;font:inherit;cursor:pointer;color:inherit}",
203
+ ".dsgc-role:hover{border-color:var(--dsw-alias-label-dimmed,rgba(128,128,128,.5))}",
204
+ ".dsgc-rolehead{display:flex;gap:8px;align-items:center}",
205
+ ".dsgc-roledot{width:10px;height:10px;border-radius:50%;flex:none}",
206
+ ".dsgc-rolename{font-weight:600;font-size:13px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary,inherit)}",
207
+ ".dsgc-role.off .dsgc-rolename{color:var(--dsw-alias-label-tertiary,inherit)}",
208
+ ".dsgc-rolepersona{font-size:11.5px;color:var(--dsw-alias-label-tertiary,inherit);line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:0}",
209
+ ".dsgc-rolemodel{width:fit-content;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.12));border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;color:var(--dsw-alias-label-secondary,inherit)}",
210
+ ".dsgc-roleops{display:flex;gap:2px;flex:none;opacity:0;transition:opacity .12s}",
211
+ ".dsgc-role:hover .dsgc-roleops,.dsgc-role:focus-within .dsgc-roleops{opacity:1}",
212
+ ".dsgc-rolemenu{display:flex;align-items:center;justify-content:space-between;gap:8px}",
213
+ ".dsgc-field{display:flex;flex-direction:column;gap:5px}",
214
+ ".dsgc-field>label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary,inherit)}",
215
+ ".dsgc-input,.dsgc-select,.dsgc-textarea{background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-3,transparent));border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:13px;border-radius:8px;padding:6px 10px;width:100%;box-sizing:border-box;outline:none;transition:border-color .16s,background-color .16s}",
216
+ ".dsgc-input:focus,.dsgc-select:focus,.dsgc-textarea:focus{border-color:var(--dsw-alias-state-business-primary,#4f6ef7)}",
217
+ ".dsgc-input::placeholder,.dsgc-textarea::placeholder{color:var(--dsw-alias-label-tertiary,inherit)}",
218
+ ".dsgc-textarea{resize:vertical;min-height:76px}",
219
+ ".dsgc-palette{display:flex;gap:6px;flex-wrap:wrap}",
220
+ ".dsgc-dot{width:18px;height:18px;border-radius:50%;border:2px solid transparent;cursor:pointer;padding:0;transition:transform .12s}",
221
+ ".dsgc-dot:hover{transform:scale(1.12)}",
222
+ ".dsgc-dot.on{border-color:var(--dsw-alias-label-primary,#fff)}",
223
+ ".dsgc-form{display:flex;flex-direction:column;gap:12px}",
224
+ ".dsgc-formerr{font-size:12px;color:var(--dsw-alias-state-error-primary,#e5484d);line-height:1.5}",
225
+ ".dsgc-selhint{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);line-height:1.5;display:flex;gap:6px;align-items:center;flex-wrap:wrap}",
226
+ ".dsgc-drawer{position:absolute;top:0;right:0;bottom:0;width:380px;max-width:calc(100% - 40px);z-index:20;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));border-left:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));box-shadow:-12px 0 32px rgba(0,0,0,.14);display:flex;flex-direction:column;animation:dsgc-drawer-in .22s cubic-bezier(.16,1,.3,1)}",
227
+ ".dsgc-drawer.closing{animation:dsgc-drawer-out .14s ease-in forwards;pointer-events:none}",
228
+ "@keyframes dsgc-drawer-in{from{transform:translateX(32px);opacity:0}to{transform:none;opacity:1}}",
229
+ "@keyframes dsgc-drawer-out{to{transform:translateX(32px);opacity:0}}",
230
+ "@keyframes dsgc-msg-in{from{opacity:0;transform:translateY(6px)}}",
231
+ "@keyframes dsgc-sys-in{from{opacity:0;transform:scale(.96)}}",
232
+ "@keyframes dsgc-think-in{from{opacity:0;transform:translateY(-3px)}}",
233
+ "@keyframes dsgc-pop-in{from{opacity:0;transform:translateY(4px)}}",
234
+ "@keyframes dsgc-rise-in{from{opacity:0;transform:translate(-50%,6px)}}",
235
+ "@keyframes dsgc-fade-in{from{opacity:0}}",
236
+ "@keyframes dsgc-presence{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--role-color,#4f6ef7) 0%,transparent)}50%{box-shadow:0 0 0 4px var(--presence-ring)}}",
237
+ ".dsgc-drawerhead{display:flex;align-items:center;gap:8px;padding:14px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2));flex:none}",
238
+ ".dsgc-drawertitle{flex:1;font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
239
+ ".dsgc-drawerbody{flex:1;overflow-y:auto;padding:16px;min-height:0;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
240
+ ".dsgc-drawerfoot{display:flex;gap:8px;justify-content:flex-end;padding:12px 16px;border-top:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2));flex:none}",
241
+ ".dsgc-fb{border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-2,transparent);border-radius:8px;display:flex;flex-direction:column;gap:6px;padding:8px;animation:dsgc-fade-in .2s ease-out}",
242
+ ".dsgc-fbhead{display:flex;align-items:center;justify-content:space-between;gap:8px;min-width:0}",
243
+ ".dsgc-fbhead button{flex:none;white-space:nowrap}",
244
+ ".dsgc-wsrow{display:flex;gap:6px;align-items:center;min-width:0}",
245
+ ".dsgc-wsrow .dsgc-input{width:auto;flex:1;min-width:0}",
246
+ ".dsgc-wsrow>button{flex:none;white-space:nowrap}",
247
+ ".dsgc-nav>button{white-space:nowrap;overflow:hidden}",
248
+ ".dsgc-fbpath{flex:1;min-width:0;font-size:11px;color:var(--dsw-alias-label-secondary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
249
+ ".dsgc-fblist{display:flex;flex-direction:column;gap:1px;max-height:220px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
250
+ ".dsgc-fbrow{display:flex;align-items:center;gap:7px;width:100%;border:none;background:none;color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12.5px;text-align:left;cursor:pointer;padding:3px 8px;border-radius:6px;transition:background-color .12s}",
251
+ ".dsgc-fbrow:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15))}",
252
+ ".dsgc-fbrow.dim{opacity:.55}",
253
+ ".dsgc-fbrow.file{cursor:default}",
254
+ ".dsgc-fbrow.file:hover{background:none}",
255
+ ".dsgc-fbname{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
256
+ ".dsgc-fbsize{flex:none;font-size:10.5px;color:var(--dsw-alias-label-tertiary,inherit)}",
257
+ ".dsgc-partchip:focus-visible,.dsgc-mentionitem:focus-visible,.dsgc-dot:focus-visible,.dsgc-opbtn:focus-visible,.dsgc-twist:focus-visible,.dsgc-addsess:focus-visible,.dsgc-grow-row:focus-visible,.dsgc-sess-row:focus-visible,.dsgc-rename:focus-visible,.dsgc-fbrow:focus-visible,.dsgc-role:focus-visible,.dsgc-roundbtn:focus-visible,.dsgc-tobtn:focus-visible,.dsgc-seambtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary,#4f6ef7));outline-offset:1px}",
258
+ "@container (max-width: 880px){.dsgc-aside{position:absolute;top:0;right:0;bottom:0;z-index:15;width:min(304px,88%);box-shadow:-12px 0 32px rgba(0,0,0,.16);border-left:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));transition:transform .3s cubic-bezier(.16,1,.3,1),opacity .2s ease}.dsgc-aside.closed{width:min(304px,88%);padding-left:12px;padding-right:12px;border-left-width:1px;transform:translateX(calc(100% + 14px));opacity:0;transition:transform .26s ease-in,opacity .18s ease-in,visibility 0s .24s}.dsgc-seambtn.right:not(.closed){right:min(304px,88%)}}",
259
+ "@container (max-width: 640px){.dsgc-nav{width:200px}.dsgc-msgbody{max-width:88%}}",
260
+ "[class*=\"panelList\"]:has(.dsgc-entryOverlay){margin-top:4px}",
261
+ "[class*=\"panelRow\"]:has(.dsgc-entryOverlay){position:relative;box-sizing:border-box;height:36px;min-height:36px;padding:0 10px;font-size:13px;transition:background-color .12s,color .12s}",
262
+ "[class*=\"panelRow\"][class*=\"panelActive\"]:has(.dsgc-entryOverlay){font-weight:600}",
263
+ "[class*=\"panelRow\"]:has(.dsgc-entryOverlay):hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit)}",
264
+ "html[data-dsh-taskboard-active] [class*=\"panelRow\"][class*=\"panelActive\"]:has(.dsgc-entryOverlay),html[data-dsh-ssh-active] [class*=\"panelRow\"][class*=\"panelActive\"]:has(.dsgc-entryOverlay){background:transparent;color:var(--dsw-alias-label-secondary,inherit);font-weight:400}",
265
+ "html[data-dsh-taskboard-active] [class*=\"panelRow\"]:has(.dsgc-entryOverlay):hover,html[data-dsh-ssh-active] [class*=\"panelRow\"]:has(.dsgc-entryOverlay):hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.12));color:var(--dsw-alias-label-primary,inherit)}",
266
+ "html[data-dsh-taskboard-active] [data-sidebar-collapsed] [class*=\"panelRow\"]:has(.dsgc-entryOverlay),html[data-dsh-ssh-active] [data-sidebar-collapsed] [class*=\"panelRow\"]:has(.dsgc-entryOverlay),html[data-dsh-taskboard-active] [class*=\"collapsed\"] [class*=\"panelRow\"]:has(.dsgc-entryOverlay),html[data-dsh-ssh-active] [class*=\"collapsed\"] [class*=\"panelRow\"]:has(.dsgc-entryOverlay){color:var(--dsw-alias-label-primary,inherit)}",
267
+ "[class*=\"panelRow\"]:has(.dsgc-entryOverlay) > [class*=\"panelTitle\"]{padding-left:24px}",
268
+ ".dsgc-entryOverlay{position:absolute;inset:0;display:flex;align-items:center;padding:0 10px;box-sizing:border-box}",
269
+ ".dsgc-entryIcon{flex:none;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center}",
270
+ ".dsgc-entryIcon svg{width:18px;height:18px;display:block}",
271
+ "[data-sidebar-collapsed] .dsgc-entryOverlay,[class*=\"collapsed\"] .dsgc-entryOverlay{justify-content:center;padding:0}",
272
+ "[data-sidebar-collapsed] [class*=\"panelRow\"]:has(.dsgc-entryOverlay),[class*=\"collapsed\"] [class*=\"panelRow\"]:has(.dsgc-entryOverlay){margin:0 auto}",
273
+ "@media (prefers-reduced-motion:reduce){.dsgc-dot,.dsgc-partchip,.dsgc-mentionitem,.dsgc-grow-row,.dsgc-sess-row,.dsgc-opbtn,.dsgc-addsess,.dsgc-topic,.dsgc-role,.dsgc-rename,.dsgc-input,.dsgc-select,.dsgc-textarea,.dsgc-fbrow,.dsgc-twist svg,.dsgc-roleops,.dsgc-roundbtn,.dsgc-permtrigger,.dsgc-permchevron,.dsgc-tobtn,.dsgc-seambtn,.dsgc-aside,.dsgc-aside.closed,.dsgc-nav,.dsgc-nav.closed,.dsgc-clearbtn,[class*=\"panelRow\"]:has(.dsgc-entryOverlay){transition:none}.dsgc-dot:hover{transform:none}.dsgc-drawer,.dsgc-msg,.dsgc-sysmsg,.dsgc-mention,.dsgc-tobottom,.dsgc-err,.dsgc-empty,.dsgc-fb,.dsgc-think .dsgc-thinkbody,.dsgc-msg.live .dsgc-avatar,.dsgc-loading svg,.dsgc-pendingdots i,.dsgc-typing{animation:none}.dsgc-typing{background-position:0 0;background-size:100% 100%}}",
274
+ ".dgcs-page{display:flex;flex-direction:column;gap:14px;padding:4px 0}",
275
+ ".dgcs-head{display:flex;flex-direction:column;gap:6px}",
276
+ ".dgcs-title{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}",
277
+ ".dgcs-desc{margin:0;font-size:13px;line-height:1.6;color:var(--dsw-alias-label-secondary,inherit)}",
278
+ ".dgcs-card{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-3,transparent);border-radius:12px;padding:14px 16px;transition:border-color .16s}",
279
+ ".dgcs-cardtext{display:flex;flex-direction:column;gap:3px;min-width:0}",
280
+ ".dgcs-cardtitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
281
+ ".dgcs-cardhint{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit)}"
282
+ ].join("\n");
283
+ /** 工厂执行时幂等注入样式标签(loader 卸载时按 data-plugin 摘除)。 */
284
+ function injectStyles() {
285
+ if (typeof document === "undefined") return;
286
+ if (document.querySelector("style[data-plugin-css=" + JSON.stringify(TAG_ID) + "]") === null) {
287
+ const tag = document.createElement("style");
288
+ tag.dataset.plugin = "dsh-group-chat";
289
+ tag.dataset.pluginCss = TAG_ID;
290
+ tag.textContent = CSS;
291
+ document.head.appendChild(tag);
292
+ }
293
+ }
294
+ //#endregion
295
+ //#region src/client/lib/ui.ts
296
+ /**
297
+ * 浏览器半 UI 基座:DSW 原语(shell 静态种子模块,loader 模块表 external)
298
+ * 与动态图标 / 命令式 createElement 辅助。组件主体已 JSX 化(.tsx);此处
299
+ * 仅保留 slot 注册回调与动态图标表需要的少量命令式辅助。
300
+ * @module dsh-group-chat/client/ui
301
+ */
302
+ /** 命令式 createElement(仅 slot 注册回调用)。 */
303
+ const h$1 = react.createElement;
304
+ /** 按名取 DSW 原语(图标表驱动)。 */
305
+ function pickPrimitive(name) {
306
+ return _deepseek_ai_dsh_client_ui_primitives[name];
307
+ }
308
+ /** 以统一 size 包装图标组件调用(放在 JSX 子元素位)。 */
309
+ function Icon(comp, size, extra) {
310
+ return h$1(comp, Object.assign({ size }, extra || {}));
311
+ }
312
+ //#endregion
313
+ //#region src/core/status.ts
314
+ /** 状态点悬停/读屏文案(idle 不渲染点,无文案)。 */
315
+ const SESS_STATUS_LABEL = {
316
+ idle: "",
317
+ ongoing: "进行中",
318
+ warning: "等待确认",
319
+ done: "已完成",
320
+ error: "已出错"
321
+ };
322
+ /** 派生单个会话的列表状态(run 为快照的 run 视图)。 */
323
+ function sessStatus(run, sessionId) {
324
+ if (run.running && run.sessionId === sessionId) return run.pendingConfirm ? "warning" : "ongoing";
325
+ const f = run.finished;
326
+ if (f && f.sessionId === sessionId) return f.reason === "error" ? "error" : "done";
327
+ return "idle";
328
+ }
329
+ //#endregion
330
+ //#region src/client/lib/model.ts
331
+ /** 角色标识色调色板(与宿主半一致)。 */
332
+ const PALETTE = [
333
+ "#5b8def",
334
+ "#22a06b",
335
+ "#e8912d",
336
+ "#c678dd",
337
+ "#e05661",
338
+ "#56b6c2",
339
+ "#98c379",
340
+ "#d19a66"
341
+ ];
342
+ /** Host HTTP API 前缀。 */
343
+ const API_PREFIX = "/api/group-chat";
344
+ /** MarkdownText 渲染文案。 */
345
+ const MD_LABELS = Object.freeze({
346
+ code: {
347
+ copyLabel: "复制",
348
+ copiedLabel: "已复制"
349
+ },
350
+ footnotes: "脚注"
351
+ });
352
+ function roleById(s, id) {
353
+ for (const r of s.roles) if (r.id === id) return r;
354
+ return null;
355
+ }
356
+ function sessById(s, id) {
357
+ for (const x of s.sessions) if (x.id === id) return x;
358
+ return null;
359
+ }
360
+ function groupById(s, id) {
361
+ for (const g of s.groups) if (g.id === id) return g;
362
+ return null;
363
+ }
364
+ function escapeRegExp(v) {
365
+ return String(v).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
366
+ }
367
+ function firstLine(text) {
368
+ const i = text.indexOf("\n");
369
+ return i === -1 ? text : text.slice(0, i);
370
+ }
371
+ function latestLine(text) {
372
+ const visible = text.trimEnd();
373
+ const i = visible.lastIndexOf("\n");
374
+ return i === -1 ? visible : visible.slice(i + 1);
375
+ }
376
+ function fmtTime(ts) {
377
+ const delta = Math.max(0, Date.now() - ts);
378
+ if (delta < 6e4) return "刚刚";
379
+ if (delta < 36e5) return Math.floor(delta / 6e4) + " 分钟前";
380
+ if (delta < 864e5) return Math.floor(delta / 36e5) + " 小时前";
381
+ const d = new Date(ts);
382
+ return d.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? d.getMonth() + 1 + "月" + d.getDate() + "日" : d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
383
+ }
384
+ function draftFromRole(role) {
385
+ return {
386
+ id: role.id,
387
+ name: role.name,
388
+ color: role.color ?? null,
389
+ persona: role.persona,
390
+ provider: role.provider,
391
+ model: role.model,
392
+ temperature: role.temperature,
393
+ reasoningEffort: role.reasoningEffort || "default",
394
+ enabled: role.enabled,
395
+ thinking: role.thinking === true
396
+ };
397
+ }
398
+ function blankDraft() {
399
+ return {
400
+ name: "",
401
+ color: null,
402
+ persona: "",
403
+ provider: "",
404
+ model: "",
405
+ temperature: void 0,
406
+ reasoningEffort: "default",
407
+ enabled: true,
408
+ thinking: false
409
+ };
410
+ }
411
+ //#endregion
412
+ //#region src/client/lib/api.ts
413
+ /**
414
+ * 浏览器半 transport:/api/group-chat/* 同源 JSON 端点。
415
+ * @module dsh-group-chat/client/api
416
+ */
417
+ async function readJson(fetchPromise) {
418
+ const response = await fetchPromise;
419
+ const body = await response.json();
420
+ if (!response.ok) throw new Error(body && body.error ? body.error : "请求失败(HTTP " + response.status + "),请稍后重试");
421
+ return body;
422
+ }
423
+ const api = {
424
+ state: () => readJson(fetch(API_PREFIX + "/state", { cache: "no-store" })),
425
+ action: (payload, signal) => readJson(fetch(API_PREFIX + "/action", {
426
+ method: "POST",
427
+ headers: { "content-type": "application/json" },
428
+ body: JSON.stringify(payload),
429
+ signal
430
+ }))
431
+ };
432
+ //#endregion
433
+ //#region src/client/components/RoleDrawer.tsx
434
+ /**
435
+ * 角色编辑抽屉(右侧滑出,不遮挡会话流阅读)。
436
+ * @module dsh-group-chat/client/drawer
437
+ */
438
+ function RoleDrawer(props) {
439
+ const { draft, set } = props;
440
+ const models = props.models;
441
+ const providers = models && models.providers || [];
442
+ const modelsOf = models && models.modelsByProvider && models.modelsByProvider[draft.provider] || [];
443
+ const [closing, setClosing] = (0, react.useState)(false);
444
+ const [effortsInfo, setEffortsInfo] = (0, react.useState)(null);
445
+ const close = () => {
446
+ if (closing) return;
447
+ setClosing(true);
448
+ window.setTimeout(() => props.onCancel(), 140);
449
+ };
450
+ const onProvider = (e) => {
451
+ const p = e.target.value;
452
+ const list = models && models.modelsByProvider && models.modelsByProvider[p] || [];
453
+ set(Object.assign({}, draft, {
454
+ provider: p,
455
+ model: list.length ? list[0].id : "",
456
+ reasoningEffort: "default"
457
+ }));
458
+ };
459
+ (0, react.useEffect)(() => {
460
+ const onKey = (e) => {
461
+ if (e.key === "Escape") close();
462
+ };
463
+ document.addEventListener("keydown", onKey);
464
+ return () => document.removeEventListener("keydown", onKey);
465
+ }, []);
466
+ (0, react.useEffect)(() => {
467
+ let live = true;
468
+ setEffortsInfo(null);
469
+ if (!draft.provider || !draft.model) return;
470
+ api.action({
471
+ kind: "efforts",
472
+ provider: draft.provider,
473
+ model: draft.model
474
+ }).then((res) => {
475
+ if (!live) return;
476
+ const r = res;
477
+ if (r && r.ok) setEffortsInfo({
478
+ ok: true,
479
+ efforts: r.efforts || [],
480
+ defaultEffort: r.defaultEffort
481
+ });
482
+ else setEffortsInfo({
483
+ ok: false,
484
+ efforts: [],
485
+ defaultEffort: void 0,
486
+ error: r && r.error || "查询失败"
487
+ });
488
+ }).catch((e) => {
489
+ if (live) setEffortsInfo({
490
+ ok: false,
491
+ efforts: [],
492
+ defaultEffort: void 0,
493
+ error: String(e && e.message || e)
494
+ });
495
+ });
496
+ return () => {
497
+ live = false;
498
+ };
499
+ }, [draft.provider, draft.model]);
500
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
501
+ className: "dsgc-drawer" + (closing ? " closing" : ""),
502
+ role: "dialog",
503
+ "aria-modal": "false",
504
+ "aria-label": draft.id ? "编辑角色" : "添加角色",
505
+ children: [
506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
507
+ className: "dsgc-drawerhead",
508
+ children: [
509
+ Icon(_deepseek_ai_dsh_client_ui_primitives.IconUserOutline16, 16),
510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
511
+ className: "dsgc-drawertitle",
512
+ children: draft.id ? "编辑角色" : "添加角色"
513
+ }),
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
515
+ variant: "ghost",
516
+ size: "sm",
517
+ onClick: close,
518
+ "aria-label": "关闭",
519
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, 16)
520
+ })
521
+ ]
522
+ }),
523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
524
+ className: "dsgc-drawerbody",
525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
526
+ className: "dsgc-form",
527
+ children: [
528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
529
+ className: "dsgc-field",
530
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "名称" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
531
+ className: "dsgc-input",
532
+ value: draft.name,
533
+ autoFocus: !draft.id,
534
+ onChange: (e) => {
535
+ set(Object.assign({}, draft, { name: e.target.value }));
536
+ },
537
+ placeholder: "例如:产品经理"
538
+ })]
539
+ }),
540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
541
+ className: "dsgc-field",
542
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "标识色" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
543
+ className: "dsgc-palette",
544
+ children: PALETTE.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
545
+ type: "button",
546
+ className: "dsgc-dot" + (draft.color === c ? " on" : ""),
547
+ style: { background: c },
548
+ onClick: () => {
549
+ set(Object.assign({}, draft, { color: c }));
550
+ },
551
+ "aria-label": "选择标识色 " + c
552
+ }, c))
553
+ })]
554
+ }),
555
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
556
+ className: "dsgc-field",
557
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "人设 / 角色设定" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
558
+ className: "dsgc-textarea",
559
+ rows: 4,
560
+ value: draft.persona,
561
+ onChange: (e) => {
562
+ set(Object.assign({}, draft, { persona: e.target.value }));
563
+ },
564
+ placeholder: "性格、立场、说话风格、专业背景……"
565
+ })]
566
+ }),
567
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
568
+ className: "dsgc-field",
569
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "模型提供方" }), providers.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
570
+ className: "dsgc-select",
571
+ value: draft.provider,
572
+ onChange: onProvider,
573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
574
+ value: "",
575
+ children: "选择提供方…"
576
+ }), providers.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
577
+ value: p.id,
578
+ children: p.name || p.id
579
+ }, p.id))]
580
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
581
+ className: "dsgc-selhint",
582
+ children: [(props.modelsError ? "模型目录加载失败:" + props.modelsError : "暂无可用 provider") + " ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
583
+ variant: "outline",
584
+ size: "sm",
585
+ onClick: props.onRetryModels,
586
+ children: "重试"
587
+ })]
588
+ })]
589
+ }),
590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
591
+ className: "dsgc-field",
592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "模型" }), modelsOf.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
593
+ className: "dsgc-select",
594
+ value: draft.model,
595
+ onChange: (e) => {
596
+ set(Object.assign({}, draft, {
597
+ model: e.target.value,
598
+ reasoningEffort: "default"
599
+ }));
600
+ },
601
+ children: modelsOf.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
602
+ value: m.id,
603
+ children: m.name || m.id
604
+ }, m.id))
605
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
606
+ className: "dsgc-selhint",
607
+ children: "请先选择有可用模型的提供方"
608
+ })]
609
+ }),
610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
611
+ className: "dsgc-field",
612
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "温度(可选)" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
613
+ className: "dsgc-input",
614
+ type: "number",
615
+ step: "0.1",
616
+ min: "0",
617
+ max: "2",
618
+ value: draft.temperature == null ? "" : String(draft.temperature),
619
+ onChange: (e) => {
620
+ set(Object.assign({}, draft, { temperature: e.target.value === "" ? void 0 : Number(e.target.value) }));
621
+ }
622
+ })]
623
+ }),
624
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
625
+ className: "dsgc-field",
626
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "深度思考" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
627
+ style: {
628
+ display: "flex",
629
+ gap: 10,
630
+ alignItems: "center"
631
+ },
632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
633
+ checked: draft.thinking === true,
634
+ onChange: (v) => {
635
+ set(Object.assign({}, draft, { thinking: v }));
636
+ },
637
+ label: "深度思考"
638
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
639
+ className: "dsgc-hint",
640
+ children: "开启后角色发言前先思考(支持思考的模型)"
641
+ })]
642
+ })]
643
+ }),
644
+ draft.thinking === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
645
+ className: "dsgc-field",
646
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: "推理级别" }), effortsInfo === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
647
+ className: "dsgc-selhint",
648
+ children: "查询推理级别…"
649
+ }) : !effortsInfo.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
650
+ className: "dsgc-selhint",
651
+ children: [
652
+ "推理级别查询失败:",
653
+ effortsInfo.error,
654
+ "(若刚更新插件,请重启 dsh web 使 host 半生效)"
655
+ ]
656
+ }) : (effortsInfo.efforts || []).length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
657
+ className: "dsgc-select",
658
+ value: draft.reasoningEffort && draft.reasoningEffort !== "default" ? draft.reasoningEffort : "default",
659
+ onChange: (e) => {
660
+ set(Object.assign({}, draft, { reasoningEffort: e.target.value }));
661
+ },
662
+ title: "取自当前模型设置中的推理选项;默认 = 使用该模型的默认推理级别",
663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
664
+ value: "default",
665
+ children: ["默认", effortsInfo.defaultEffort ? "(当前默认:" + ((effortsInfo.efforts || []).find((x) => x.id === effortsInfo.defaultEffort) || { name: "" }).name + ")" : ""]
666
+ }), (effortsInfo.efforts || []).map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
667
+ value: e.id,
668
+ title: e.description || "",
669
+ children: e.name || e.id
670
+ }, e.id))]
671
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
672
+ className: "dsgc-selhint",
673
+ children: "当前模型未提供推理等级,将使用默认"
674
+ })]
675
+ }) : null,
676
+ props.formError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
677
+ className: "dsgc-formerr",
678
+ children: props.formError
679
+ }) : null
680
+ ]
681
+ })
682
+ }),
683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
684
+ className: "dsgc-drawerfoot",
685
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
686
+ variant: "outline",
687
+ onClick: close,
688
+ children: "取消"
689
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
690
+ variant: "primary",
691
+ onClick: props.onSave,
692
+ children: draft.id ? "保存" : "添加"
693
+ })]
694
+ })
695
+ ]
696
+ });
697
+ }
698
+ //#endregion
699
+ //#region src/client/components/NavPanel.tsx
700
+ function NavPanel(props) {
701
+ const { snap, search, setSearch, collapsedGroups, setCollapsedGroups, gid, sid, setGid, setSid, setPartsSel, renameDraft, setRenameDraft, confirmDel, setConfirmDel, mutate, navOpen } = props;
702
+ const commitRename = async () => {
703
+ if (!renameDraft) return;
704
+ const d = renameDraft;
705
+ setRenameDraft(null);
706
+ const value = String(d.value || "").trim();
707
+ if (!value) return;
708
+ if (d.kind === "group") await mutate({
709
+ op: "renameGroup",
710
+ groupId: d.id,
711
+ name: value
712
+ });
713
+ else await mutate({
714
+ op: "renameSession",
715
+ sessionId: d.id,
716
+ name: value
717
+ });
718
+ };
719
+ const renameKeyDown = (e) => {
720
+ if (e.key === "Enter") {
721
+ e.preventDefault();
722
+ commitRename();
723
+ } else if (e.key === "Escape") {
724
+ e.preventDefault();
725
+ setRenameDraft(null);
726
+ }
727
+ };
728
+ const doDelete = async () => {
729
+ if (!confirmDel) return;
730
+ const d = confirmDel;
731
+ setConfirmDel(null);
732
+ if (d.kind === "group") await mutate({
733
+ op: "deleteGroup",
734
+ groupId: d.id
735
+ });
736
+ else await mutate({
737
+ op: "deleteSession",
738
+ sessionId: d.id
739
+ });
740
+ };
741
+ const q = search.trim().toLowerCase();
742
+ const groupMatches = (g) => {
743
+ if (!q) return {
744
+ show: true,
745
+ filterSessions: false
746
+ };
747
+ if (g.name.toLowerCase().includes(q)) return {
748
+ show: true,
749
+ filterSessions: false
750
+ };
751
+ return {
752
+ show: g.sessionIds.map((id) => sessById(snap, id)).filter((s) => s && s.name.toLowerCase().includes(q)).length > 0,
753
+ filterSessions: true
754
+ };
755
+ };
756
+ const group = gid ? groupById(snap, gid) : null;
757
+ const treeNodes = [];
758
+ for (const g of snap.groups) {
759
+ const match = groupMatches(g);
760
+ if (!match.show) continue;
761
+ const expanded = q ? true : !collapsedGroups.has(g.id);
762
+ const isRenameGroup = renameDraft && renameDraft.kind === "group" && renameDraft.id === g.id;
763
+ const isConfirmGroup = confirmDel && confirmDel.kind === "group" && confirmDel.id === g.id;
764
+ const groupChildren = [];
765
+ if (expanded) {
766
+ for (const sessionId of g.sessionIds) {
767
+ const s = sessById(snap, sessionId);
768
+ if (!s) continue;
769
+ if (match.filterSessions && !s.name.toLowerCase().includes(q)) continue;
770
+ const sess = sid ? sessById(snap, sid) : null;
771
+ const isActive = sess && s.id === sess.id && g.id === group?.id;
772
+ const isRenameSess = renameDraft && renameDraft.kind === "session" && renameDraft.id === s.id;
773
+ const isConfirm = confirmDel && confirmDel.kind === "session" && confirmDel.id === s.id;
774
+ const st = sessStatus(snap.run, s.id);
775
+ groupChildren.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
776
+ className: "dsgc-sess-row" + (isActive ? " on" : ""),
777
+ role: "button",
778
+ tabIndex: 0,
779
+ onClick: () => {
780
+ setGid(g.id);
781
+ setSid(s.id);
782
+ setPartsSel(null);
783
+ setConfirmDel(null);
784
+ setRenameDraft(null);
785
+ },
786
+ onKeyDown: (e) => {
787
+ if (e.key === "Enter" || e.key === " ") {
788
+ e.preventDefault();
789
+ setGid(g.id);
790
+ setSid(s.id);
791
+ setPartsSel(null);
792
+ setConfirmDel(null);
793
+ setRenameDraft(null);
794
+ }
795
+ },
796
+ children: [
797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
798
+ className: "dsgc-sess-status",
799
+ children: st !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
800
+ role: "img",
801
+ title: SESS_STATUS_LABEL[st],
802
+ "aria-label": SESS_STATUS_LABEL[st],
803
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
804
+ state: st,
805
+ size: 8
806
+ })
807
+ }) : null
808
+ }),
809
+ isRenameSess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
810
+ className: "dsgc-rename",
811
+ value: renameDraft.value,
812
+ autoFocus: true,
813
+ onChange: (e) => {
814
+ setRenameDraft({
815
+ kind: "session",
816
+ id: s.id,
817
+ value: e.target.value
818
+ });
819
+ },
820
+ onBlur: () => {
821
+ commitRename();
822
+ },
823
+ onKeyDown: renameKeyDown,
824
+ onClick: (e) => {
825
+ e.stopPropagation();
826
+ }
827
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
828
+ className: "dsgc-sess-name",
829
+ children: s.name
830
+ }),
831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
832
+ className: "dsgc-nodeops",
833
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
834
+ className: "dsgc-opbtn",
835
+ title: "重命名会话",
836
+ "aria-label": "重命名会话",
837
+ onClick: (e) => {
838
+ e.stopPropagation();
839
+ setRenameDraft({
840
+ kind: "session",
841
+ id: s.id,
842
+ value: s.name
843
+ });
844
+ },
845
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
846
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
847
+ className: "dsgc-opbtn" + (isConfirm ? " danger" : ""),
848
+ title: isConfirm ? "再次点击确认删除" : "删除会话",
849
+ "aria-label": "删除会话",
850
+ onClick: (e) => {
851
+ e.stopPropagation();
852
+ if (isConfirm) doDelete();
853
+ else setConfirmDel({
854
+ kind: "session",
855
+ id: s.id
856
+ });
857
+ },
858
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
859
+ })]
860
+ })
861
+ ]
862
+ }, s.id));
863
+ }
864
+ groupChildren.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
865
+ className: "dsgc-addsess",
866
+ onClick: () => {
867
+ mutate({
868
+ op: "createSession",
869
+ groupId: g.id
870
+ });
871
+ },
872
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, 14), "新会话"]
873
+ }, "__add"));
874
+ }
875
+ treeNodes.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
876
+ className: "dsgc-gnode",
877
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
878
+ className: "dsgc-grow-row" + (g.id === group?.id ? " on" : ""),
879
+ role: "button",
880
+ tabIndex: 0,
881
+ onClick: () => {
882
+ setGid(g.id);
883
+ setPartsSel(null);
884
+ setConfirmDel(null);
885
+ setRenameDraft(null);
886
+ },
887
+ onKeyDown: (e) => {
888
+ if (e.key === "Enter" || e.key === " ") {
889
+ e.preventDefault();
890
+ setGid(g.id);
891
+ setPartsSel(null);
892
+ setConfirmDel(null);
893
+ setRenameDraft(null);
894
+ }
895
+ },
896
+ children: [
897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
898
+ className: "dsgc-twist" + (expanded ? "" : " closed"),
899
+ title: expanded ? "收起" : "展开",
900
+ "aria-label": expanded ? "收起" : "展开",
901
+ onClick: (e) => {
902
+ e.stopPropagation();
903
+ setCollapsedGroups((prev) => {
904
+ const next = new Set(prev);
905
+ if (next.has(g.id)) next.delete(g.id);
906
+ else next.add(g.id);
907
+ return next;
908
+ });
909
+ },
910
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, 14)
911
+ }),
912
+ isRenameGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
913
+ className: "dsgc-rename",
914
+ value: renameDraft.value,
915
+ autoFocus: true,
916
+ onChange: (e) => {
917
+ setRenameDraft({
918
+ kind: "group",
919
+ id: g.id,
920
+ value: e.target.value
921
+ });
922
+ },
923
+ onBlur: () => {
924
+ commitRename();
925
+ },
926
+ onKeyDown: renameKeyDown,
927
+ onClick: (e) => {
928
+ e.stopPropagation();
929
+ }
930
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
931
+ className: "dsgc-gname",
932
+ children: g.name
933
+ }),
934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
935
+ className: "dsgc-nodeops",
936
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
937
+ className: "dsgc-opbtn",
938
+ title: "重命名群组",
939
+ "aria-label": "重命名群组",
940
+ onClick: (e) => {
941
+ e.stopPropagation();
942
+ setRenameDraft({
943
+ kind: "group",
944
+ id: g.id,
945
+ value: g.name
946
+ });
947
+ },
948
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
949
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
950
+ className: "dsgc-opbtn" + (isConfirmGroup ? " danger" : ""),
951
+ title: isConfirmGroup ? "再次点击确认删除" : "删除群组",
952
+ "aria-label": "删除群组",
953
+ onClick: (e) => {
954
+ e.stopPropagation();
955
+ if (isConfirmGroup) doDelete();
956
+ else setConfirmDel({
957
+ kind: "group",
958
+ id: g.id
959
+ });
960
+ },
961
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
962
+ })]
963
+ })
964
+ ]
965
+ }), expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
966
+ className: "dsgc-sess-list",
967
+ children: groupChildren
968
+ }) : null]
969
+ }, g.id));
970
+ }
971
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
972
+ className: "dsgc-nav" + (navOpen ? "" : " closed"),
973
+ "aria-hidden": navOpen ? void 0 : "true",
974
+ children: [
975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
976
+ icon: Icon(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, 16),
977
+ className: "dsgc-search",
978
+ placeholder: "搜索群组与会话…",
979
+ value: search,
980
+ onChange: (e) => setSearch(e.target.value)
981
+ }),
982
+ treeNodes.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
983
+ className: "dsgc-tree",
984
+ children: treeNodes
985
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
986
+ className: "dsgc-hint",
987
+ children: q ? "没有匹配「" + search.trim() + "」的群组或会话" : "暂无群组"
988
+ }),
989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
990
+ variant: "outline",
991
+ onClick: () => {
992
+ mutate({ op: "createGroup" });
993
+ },
994
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, 16), "新建群组"]
995
+ })
996
+ ]
997
+ });
998
+ }
999
+ //#endregion
1000
+ //#region src/client/components/AsidePanel.tsx
1001
+ function AsidePanel(props) {
1002
+ const { snap, group, asideOpen, wsDraft, setWsDraft, fileBrowser, setFileBrowser, mutate, openRoleEditor, openBrowser, selectCurrentDir } = props;
1003
+ const commitWsDir = () => {
1004
+ if (wsDraft !== null && group && wsDraft !== (group.workspaceDir || "")) mutate({
1005
+ op: "setWorkspaceDir",
1006
+ groupId: group.id,
1007
+ path: wsDraft
1008
+ });
1009
+ setWsDraft(null);
1010
+ };
1011
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1012
+ className: "dsgc-aside" + (asideOpen ? "" : " closed"),
1013
+ "aria-hidden": asideOpen ? void 0 : "true",
1014
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1015
+ className: "dsgc-sec",
1016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1017
+ className: "dsgc-sechead",
1018
+ children: [
1019
+ "群成员",
1020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsgc-secspacer" }),
1021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1022
+ className: "dsgc-seccount",
1023
+ children: group.roleIds.length ? group.roleIds.length + " 个" : ""
1024
+ }),
1025
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1026
+ variant: "ghost",
1027
+ size: "sm",
1028
+ onClick: () => {
1029
+ openRoleEditor(null);
1030
+ },
1031
+ "aria-label": "添加角色",
1032
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, 14), "添加"]
1033
+ })
1034
+ ]
1035
+ }), group.roleIds.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1036
+ className: "dsgc-roles",
1037
+ children: group.roleIds.map((rid) => {
1038
+ const r = roleById(snap, rid);
1039
+ if (!r) return null;
1040
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1041
+ className: "dsgc-role" + (r.enabled ? "" : " off"),
1042
+ role: "button",
1043
+ tabIndex: 0,
1044
+ onClick: () => {
1045
+ openRoleEditor(r);
1046
+ },
1047
+ onKeyDown: (e) => {
1048
+ if (e.key === "Enter") {
1049
+ e.preventDefault();
1050
+ openRoleEditor(r);
1051
+ }
1052
+ },
1053
+ title: "点击编辑角色",
1054
+ children: [
1055
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1056
+ className: "dsgc-rolehead",
1057
+ children: [
1058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1059
+ className: "dsgc-roledot",
1060
+ style: { background: r.color || "#888" }
1061
+ }),
1062
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1063
+ className: "dsgc-rolename",
1064
+ children: r.name
1065
+ }),
1066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1067
+ label: r.enabled ? "停用该角色" : "启用该角色",
1068
+ side: "top",
1069
+ delayMs: 300,
1070
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1071
+ onClick: (e) => {
1072
+ e.stopPropagation();
1073
+ },
1074
+ onKeyDown: (e) => {
1075
+ e.stopPropagation();
1076
+ },
1077
+ style: {
1078
+ display: "inline-flex",
1079
+ flex: "none"
1080
+ },
1081
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
1082
+ checked: r.enabled,
1083
+ onChange: () => {
1084
+ mutate({
1085
+ op: "setRoleEnabled",
1086
+ roleId: r.id,
1087
+ enabled: !r.enabled
1088
+ });
1089
+ },
1090
+ label: r.enabled ? "停用该角色" : "启用该角色",
1091
+ "aria-label": (r.enabled ? "停用" : "启用") + "角色 " + r.name
1092
+ })
1093
+ })
1094
+ })
1095
+ ]
1096
+ }),
1097
+ r.persona ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1098
+ className: "dsgc-rolepersona",
1099
+ title: r.persona,
1100
+ children: r.persona
1101
+ }) : null,
1102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1103
+ className: "dsgc-rolemenu",
1104
+ children: [
1105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1106
+ className: "dsgc-rolemodel",
1107
+ title: r.provider + " / " + r.model + (r.thinking ? " · 深度思考" + (r.reasoningEffort && r.reasoningEffort !== "default" ? "(" + r.reasoningEffort + ")" : "") : ""),
1108
+ children: [
1109
+ r.provider,
1110
+ " / ",
1111
+ r.model
1112
+ ]
1113
+ }),
1114
+ r.thinking ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1115
+ title: "深度思考",
1116
+ style: {
1117
+ display: "inline-flex",
1118
+ alignItems: "center",
1119
+ color: "var(--dsw-alias-label-tertiary,inherit)"
1120
+ },
1121
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, 14)
1122
+ }) : null,
1123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1124
+ className: "dsgc-roleops",
1125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1126
+ className: "dsgc-opbtn",
1127
+ title: "编辑角色",
1128
+ "aria-label": "编辑角色",
1129
+ onClick: (e) => {
1130
+ e.stopPropagation();
1131
+ openRoleEditor(r);
1132
+ },
1133
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
1134
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1135
+ className: "dsgc-opbtn danger",
1136
+ title: "移除角色",
1137
+ "aria-label": "移除角色",
1138
+ onClick: (e) => {
1139
+ e.stopPropagation();
1140
+ mutate({
1141
+ op: "deleteRole",
1142
+ roleId: r.id
1143
+ });
1144
+ },
1145
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
1146
+ })]
1147
+ })
1148
+ ]
1149
+ })
1150
+ ]
1151
+ }, r.id);
1152
+ })
1153
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1154
+ className: "dsgc-hint",
1155
+ children: "还没有角色。每个角色可绑定不同的 provider/model,在群内以独立身份发言。"
1156
+ })]
1157
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1158
+ className: "dsgc-sec",
1159
+ children: [
1160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1161
+ className: "dsgc-sechead",
1162
+ children: "工作区目录"
1163
+ }),
1164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1165
+ className: "dsgc-field",
1166
+ children: [
1167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1168
+ className: "dsgc-wsrow",
1169
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1170
+ className: "dsgc-input",
1171
+ value: wsDraft === null ? group.workspaceDir || "" : wsDraft,
1172
+ placeholder: "~/docs 或 /abs/dir",
1173
+ onChange: (e) => {
1174
+ setWsDraft(e.target.value);
1175
+ },
1176
+ onBlur: commitWsDir,
1177
+ onKeyDown: (e) => {
1178
+ if (e.key === "Enter") e.target.blur();
1179
+ }
1180
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1181
+ variant: "outline",
1182
+ size: "sm",
1183
+ onClick: () => {
1184
+ openBrowser(wsDraft === null ? group.workspaceDir : wsDraft);
1185
+ },
1186
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, 14), "浏览"]
1187
+ })]
1188
+ }),
1189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1190
+ className: "dsgc-hint",
1191
+ children: "发送时读取目录内文本文件,注入本群全体角色上下文;角色也可用工具主动查看"
1192
+ }),
1193
+ group.workspaceDir === "~" || group.workspaceDir === "/" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1194
+ className: "dsgc-err",
1195
+ children: "工作区指向整个主目录/根目录:角色的只读工具将可读取该范围下的所有文件,请谨慎"
1196
+ }) : null
1197
+ ]
1198
+ }),
1199
+ fileBrowser && fileBrowser.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1200
+ className: "dsgc-fb",
1201
+ children: [
1202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1203
+ className: "dsgc-fbhead",
1204
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1205
+ className: "dsgc-fbpath",
1206
+ title: fileBrowser.list ? fileBrowser.list.path : "",
1207
+ children: fileBrowser.loading ? "读取中…" : fileBrowser.list ? fileBrowser.list.path : ""
1208
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1209
+ style: {
1210
+ display: "flex",
1211
+ gap: 4,
1212
+ alignItems: "center"
1213
+ },
1214
+ children: [
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1216
+ variant: "primary",
1217
+ size: "sm",
1218
+ disabled: !fileBrowser.list,
1219
+ onClick: selectCurrentDir,
1220
+ children: "选定此目录"
1221
+ }),
1222
+ fileBrowser.list ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1223
+ variant: "outline",
1224
+ size: "sm",
1225
+ title: "上一级",
1226
+ "aria-label": "上一级",
1227
+ onClick: () => {
1228
+ openBrowser(fileBrowser.list.parent);
1229
+ },
1230
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, 14)
1231
+ }) : null,
1232
+ fileBrowser.list ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1233
+ variant: "outline",
1234
+ size: "sm",
1235
+ title: "主目录",
1236
+ "aria-label": "主目录",
1237
+ onClick: () => {
1238
+ openBrowser(fileBrowser.list.home);
1239
+ },
1240
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, 14)
1241
+ }) : null,
1242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1243
+ variant: "ghost",
1244
+ size: "sm",
1245
+ onClick: () => {
1246
+ setFileBrowser(null);
1247
+ },
1248
+ "aria-label": "关闭浏览器",
1249
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, 14)
1250
+ })
1251
+ ]
1252
+ })]
1253
+ }),
1254
+ fileBrowser.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ className: "dsgc-err",
1256
+ children: fileBrowser.error
1257
+ }) : null,
1258
+ fileBrowser.list ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1259
+ className: "dsgc-fblist",
1260
+ children: (fileBrowser.list.entries || []).length ? (fileBrowser.list.entries || []).map((e) => e.type === "directory" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1261
+ type: "button",
1262
+ className: "dsgc-fbrow" + (e.hidden ? " dim" : "") + " dir",
1263
+ onClick: () => {
1264
+ openBrowser(e.path);
1265
+ },
1266
+ children: [Icon(e.hidden ? _deepseek_ai_dsh_client_ui_primitives.IconFolderClose16 : _deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, 14), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1267
+ className: "dsgc-fbname",
1268
+ children: [e.name, "/"]
1269
+ })]
1270
+ }, e.path) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1271
+ className: "dsgc-fbrow dim file",
1272
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1273
+ className: "dsgc-fbname",
1274
+ children: e.name
1275
+ }), e.size !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1276
+ className: "dsgc-fbsize",
1277
+ children: _deepseek_ai_dsh_client_ui_primitives.fileSizeText(e.size)
1278
+ }) : null]
1279
+ }, e.path)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1280
+ className: "dsgc-hint",
1281
+ children: "空目录"
1282
+ })
1283
+ }) : null
1284
+ ]
1285
+ }) : null
1286
+ ]
1287
+ })]
1288
+ });
1289
+ }
1290
+ //#endregion
1291
+ //#region src/client/components/ThinkRow.tsx
1292
+ /**
1293
+ * 思考折叠行(对标宿主 ReasoningRow:折叠摘要 / 展开全文)。
1294
+ * @module dsh-group-chat/client/ThinkRow
1295
+ */
1296
+ function ThinkRow(props) {
1297
+ const text = props.text || "";
1298
+ const running = !!props.running;
1299
+ const [expanded, setExpanded] = (0, react.useState)(false);
1300
+ const plain = (line) => line.replace(/^(\s*#{1,6}\s+|\s*[-*+]\s+|\s*>\s*)+/, "").replace(/[*`_~]/g, "").replace(/\s+/g, " ").trim();
1301
+ const summary = plain(running ? latestLine(text) : firstLine(text));
1302
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1303
+ className: "dsgc-think",
1304
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
1305
+ rowClassName: "dsgc-thinkrow",
1306
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, { size: 14 }),
1307
+ title: "思考",
1308
+ open: expanded,
1309
+ expandable: true,
1310
+ expandOnRowClick: true,
1311
+ onToggle: () => {
1312
+ setExpanded((v) => !v);
1313
+ },
1314
+ collapsedContent: text ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1315
+ className: "dsgc-thinksummary",
1316
+ children: summary
1317
+ }) : null,
1318
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1319
+ className: "dsgc-thinkbody",
1320
+ children: text
1321
+ })
1322
+ })
1323
+ });
1324
+ }
1325
+ //#endregion
1326
+ //#region src/client/components/ToolRow.tsx
1327
+ /**
1328
+ * 工具调用折叠行(对标思考折叠:摘要行 / 展开输出)。
1329
+ * @module dsh-group-chat/client/ToolRow
1330
+ */
1331
+ const TOOL_ICONS = {
1332
+ read_file: "IconBrowseOutline16",
1333
+ list_dir: "IconFolderOpenOutline16",
1334
+ run_command: "IconCodeOutline16"
1335
+ };
1336
+ function ToolRow(props) {
1337
+ const c = props.c;
1338
+ const [expanded, setExpanded] = (0, react.useState)(false);
1339
+ let brief = "";
1340
+ try {
1341
+ brief = JSON.stringify(c.args) || "";
1342
+ } catch {
1343
+ brief = "";
1344
+ }
1345
+ if (brief.length > 40) brief = brief.slice(0, 40) + "…";
1346
+ const IconOf = pickPrimitive(TOOL_ICONS[c.tool] || "IconCodeOutline16");
1347
+ const statusText = c.status === "ok" ? "成功" : c.status === "denied" ? "用户拒绝" : "失败";
1348
+ const StatusIcon = c.status === "ok" ? _deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14 : c.status === "denied" ? _deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16 : _deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16;
1349
+ const dur = c.durationMs ? c.durationMs >= 1e3 ? (c.durationMs / 1e3).toFixed(1) + "s" : c.durationMs + "ms" : "";
1350
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1351
+ className: "dsgc-tool",
1352
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
1353
+ rowClassName: "dsgc-toolrow",
1354
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconOf, { size: 14 }),
1355
+ title: c.tool + (brief ? " " + brief : ""),
1356
+ open: expanded,
1357
+ expandable: true,
1358
+ expandOnRowClick: true,
1359
+ onToggle: () => {
1360
+ setExpanded((v) => !v);
1361
+ },
1362
+ collapsedContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1363
+ className: "dsgc-toolsummary",
1364
+ children: [Icon(StatusIcon, 12, {
1365
+ verticalAlign: "-2px",
1366
+ marginRight: 4,
1367
+ color: c.status === "ok" ? "var(--dsw-alias-label-tertiary,inherit)" : "var(--dsw-alias-state-error-primary,#e5484d)"
1368
+ }), statusText + (dur ? " · " + dur : "")]
1369
+ }),
1370
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1371
+ className: "dsgc-toolbody",
1372
+ children: c.output || "(无输出)"
1373
+ })
1374
+ })
1375
+ });
1376
+ }
1377
+ //#endregion
1378
+ //#region src/client/components/Bubble.tsx
1379
+ /**
1380
+ * 聊天消息气泡(user / system / 角色发言)。
1381
+ *
1382
+ * 帧稳定 memo:SSE 全量快照(~65KB × 7fps)每帧重建全部消息对象 identity,
1383
+ * 但已完成消息在 host 侧 append 后不可变——按渲染相关字段做值比较,流式
1384
+ * 期间跳过已完成消息的重渲(每帧仅 live 行与派生列表变化),避免全量
1385
+ * MarkdownText 重新解析打满主线程导致「卡死后一次性蹦出」。
1386
+ * @module dsh-group-chat/client/Bubble
1387
+ */
1388
+ function BubbleInner({ m, role }) {
1389
+ const isUser = m.speaker === "user";
1390
+ const isSys = m.speaker === "system";
1391
+ const name = isUser ? "我" : isSys ? "系统" : role ? role.name : "成员";
1392
+ if (isSys) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1393
+ className: "dsgc-sysmsg" + (m.error ? " err" : ""),
1394
+ children: m.text
1395
+ });
1396
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1397
+ className: "dsgc-msg" + (isUser ? " mine" : ""),
1398
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1399
+ className: "dsgc-avatar" + (isUser ? " mine" : ""),
1400
+ style: isUser || !role ? void 0 : { border: "2px solid " + (role.color || "#888") },
1401
+ children: name.slice(0, 1)
1402
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1403
+ className: "dsgc-msgbody",
1404
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1405
+ className: "dsgc-msghead",
1406
+ children: [
1407
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1408
+ className: "dsgc-msgname",
1409
+ children: name
1410
+ }),
1411
+ m.model ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1412
+ className: "dsgc-msgmodel",
1413
+ title: m.model,
1414
+ children: m.model
1415
+ }) : null,
1416
+ m.ts ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1417
+ className: "dsgc-msgtime",
1418
+ children: fmtTime(m.ts)
1419
+ }) : null
1420
+ ]
1421
+ }), isUser ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1422
+ className: "dsgc-msgtext",
1423
+ children: m.text
1424
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1425
+ className: "dsgc-msgtext",
1426
+ children: [
1427
+ m.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkRow, { text: m.reasoning }) : null,
1428
+ (Array.isArray(m.toolCalls) ? m.toolCalls : []).map((c, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolRow, { c }, "tc" + i)),
1429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1430
+ text: m.text || "(无内容)",
1431
+ labels: MD_LABELS
1432
+ })
1433
+ ]
1434
+ })]
1435
+ })]
1436
+ });
1437
+ }
1438
+ /** 渲染相关字段的值比较(消息不可变;角色仅名/色参与渲染)。 */
1439
+ function bubblePropsEqual(a, b) {
1440
+ const x = a.m;
1441
+ const y = b.m;
1442
+ if (x !== y) {
1443
+ if (x.id !== y.id || x.speaker !== y.speaker || x.text !== y.text || x.reasoning !== y.reasoning || x.model !== y.model || x.error !== y.error || x.ts !== y.ts) return false;
1444
+ const ta = Array.isArray(x.toolCalls) ? x.toolCalls : [];
1445
+ const tb = Array.isArray(y.toolCalls) ? y.toolCalls : [];
1446
+ if (ta.length !== tb.length) return false;
1447
+ for (let i = 0; i < ta.length; i++) {
1448
+ const ca = ta[i];
1449
+ const cb = tb[i];
1450
+ if (ca !== cb && (ca.tool !== cb.tool || ca.status !== cb.status || ca.output !== cb.output || ca.durationMs !== cb.durationMs || JSON.stringify(ca.args) !== JSON.stringify(cb.args))) return false;
1451
+ }
1452
+ }
1453
+ const ra = a.role;
1454
+ const rb = b.role;
1455
+ return (ra ? ra.name + "\0" + (ra.color || "") : "") === (rb ? rb.name + "\0" + (rb.color || "") : "");
1456
+ }
1457
+ const Bubble = (0, react.memo)(BubbleInner, bubblePropsEqual);
1458
+ //#endregion
1459
+ //#region src/client/components/MessageFlow.tsx
1460
+ function MessageFlow(props) {
1461
+ const { snap, sess, busyNow, msgById, action } = props;
1462
+ const bubbles = [];
1463
+ if (sess) for (const mid of sess.messageIds) {
1464
+ const m = msgById[mid];
1465
+ if (!m) continue;
1466
+ const role = m.speaker !== "user" && m.speaker !== "system" ? roleById(snap, m.speaker) : null;
1467
+ bubbles.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bubble, {
1468
+ m,
1469
+ role
1470
+ }, m.id));
1471
+ }
1472
+ if (busyNow && snap.run.currentRoleId) {
1473
+ const lr = roleById(snap, snap.run.currentRoleId);
1474
+ if (lr) bubbles.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1475
+ className: "dsgc-msg live",
1476
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1477
+ className: "dsgc-avatar",
1478
+ style: {
1479
+ border: "2px solid " + (lr.color || "#888"),
1480
+ "--role-color": lr.color || "#888"
1481
+ },
1482
+ children: lr.name.slice(0, 1)
1483
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1484
+ className: "dsgc-msgbody",
1485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1486
+ className: "dsgc-msghead",
1487
+ children: [
1488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1489
+ className: "dsgc-msgname",
1490
+ children: lr.name
1491
+ }),
1492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1493
+ className: "dsgc-msgmodel",
1494
+ children: [
1495
+ lr.provider,
1496
+ " / ",
1497
+ lr.model
1498
+ ]
1499
+ }),
1500
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1501
+ className: "dsgc-msgtime dsgc-typing",
1502
+ children: "深度求索..."
1503
+ })
1504
+ ]
1505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1506
+ className: "dsgc-msgtext live",
1507
+ children: [
1508
+ snap.run.partialReasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkRow, {
1509
+ text: snap.run.partialReasoning,
1510
+ running: true
1511
+ }) : null,
1512
+ snap.run.partial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1513
+ text: snap.run.partial,
1514
+ streaming: true,
1515
+ labels: MD_LABELS
1516
+ }) : null,
1517
+ !snap.run.partial && !snap.run.partialReasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1518
+ className: "dsgc-pending",
1519
+ children: [
1520
+ Icon(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, 14),
1521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "思考中" }),
1522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1523
+ className: "dsgc-pendingdots",
1524
+ "aria-hidden": "true",
1525
+ children: [
1526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
1527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
1528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
1529
+ ]
1530
+ })
1531
+ ]
1532
+ }) : null
1533
+ ]
1534
+ })]
1535
+ })]
1536
+ }, "__live"));
1537
+ }
1538
+ const pc = busyNow && snap.run.pendingConfirm && sess && snap.run.sessionId === sess.id ? snap.run.pendingConfirm : null;
1539
+ if (pc) {
1540
+ const lr = roleById(snap, snap.run.currentRoleId);
1541
+ bubbles.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1542
+ className: "dsgc-confirm",
1543
+ children: [
1544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1545
+ className: "dsgc-confirmtitle",
1546
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, 14), (lr ? lr.name : "角色") + " 请求执行命令(工作区内)"]
1547
+ }),
1548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1549
+ className: "dsgc-confirmcmd",
1550
+ children: String(pc.args && pc.args.command || "")
1551
+ }),
1552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1553
+ className: "dsgc-hint",
1554
+ children: "允许后将在工作区目录执行;拒绝后角色将继续纯文本讨论"
1555
+ }),
1556
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1557
+ className: "dsgc-confirmops",
1558
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1559
+ variant: "primary",
1560
+ size: "sm",
1561
+ onClick: () => {
1562
+ action({
1563
+ kind: "confirmCommand",
1564
+ toolCallId: pc.toolCallId,
1565
+ allow: true
1566
+ });
1567
+ },
1568
+ children: "允许"
1569
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1570
+ variant: "outline",
1571
+ size: "sm",
1572
+ onClick: () => {
1573
+ action({
1574
+ kind: "confirmCommand",
1575
+ toolCallId: pc.toolCallId,
1576
+ allow: false
1577
+ });
1578
+ },
1579
+ children: "拒绝"
1580
+ })]
1581
+ })
1582
+ ]
1583
+ }, "__confirm"));
1584
+ }
1585
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: bubbles });
1586
+ }
1587
+ //#endregion
1588
+ //#region src/core/types.ts
1589
+ /** 全部合法档位(展示顺序)。 */
1590
+ const PERMISSION_TIERS = [
1591
+ "view_only",
1592
+ "workspace_write",
1593
+ "full_access"
1594
+ ];
1595
+ /** 档位安全化:合法字符串原样,其余 undefined。 */
1596
+ function asPermissionTier(value) {
1597
+ return typeof value === "string" && PERMISSION_TIERS.includes(value) ? value : void 0;
1598
+ }
1599
+ //#endregion
1600
+ //#region src/client/components/PermissionSelect.tsx
1601
+ /**
1602
+ * 群组权限档位选择器(对齐主会话 composer 左下角的 /permission 选择器):
1603
+ * 触发芯片(盾形图标 + 档位名 + chevron)+ Menu 上弹三档 + 完全权限风险
1604
+ * 确认弹窗(与主会话同款 RiskConfirmation 原语与文案)。
1605
+ * @module dsh-group-chat/client/permission-select
1606
+ */
1607
+ /** 档位展示名(与主会话中文标签一致)。 */
1608
+ const TIER_LABELS = {
1609
+ view_only: "仅可查看",
1610
+ workspace_write: "工作区内修改",
1611
+ full_access: "完全权限"
1612
+ };
1613
+ /** 档位悬停说明(群聊语境下的真实约束)。 */
1614
+ const TIER_DESCRIPTIONS = {
1615
+ view_only: "角色只能读取工作区文件,不能执行命令",
1616
+ workspace_write: "角色可执行命令(cwd 固定在工作区),每条命令需你在会话中确认",
1617
+ full_access: "角色可免确认直接执行命令,存在风险"
1618
+ };
1619
+ /** 完全权限风险确认文案(与主会话逐字一致)。 */
1620
+ const CONFIRM_TEXT = {
1621
+ title: "确认启用完全权限?",
1622
+ description: "启用完全权限后,智能体将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。",
1623
+ acknowledge: "我已了解风险,并愿意继续",
1624
+ cancel: "取消",
1625
+ enable: "启用完全权限"
1626
+ };
1627
+ const SHIELD_OUTLINE = "M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z";
1628
+ function GlyphViewOnly() {
1629
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1630
+ width: "16",
1631
+ height: "16",
1632
+ viewBox: "0 0 16 16",
1633
+ fill: "none",
1634
+ "aria-hidden": true,
1635
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1636
+ d: SHIELD_OUTLINE,
1637
+ stroke: "currentColor",
1638
+ strokeWidth: "1.31831",
1639
+ strokeLinejoin: "round"
1640
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1641
+ d: "M12.1654 5.7552L8.9447 9.41475C8.73044 9.65816 8.53628 9.8804 8.35774 10.0423C8.1713 10.2114 7.94235 10.3717 7.64016 10.4254C7.48207 10.4535 7.32 10.4552 7.16151 10.4294C6.85843 10.3801 6.62728 10.2223 6.43836 10.0559C6.25752 9.89653 6.06037 9.67732 5.84264 9.43705L4.72925 8.20897L5.63557 7.38707L6.74897 8.61594C6.98603 8.87755 7.12974 9.03533 7.24673 9.13839C7.31033 9.19443 7.34485 9.21476 7.35823 9.22122C7.38068 9.22484 7.40352 9.22515 7.42593 9.22122C7.40522 9.22502 7.42893 9.23294 7.53583 9.136C7.65132 9.03126 7.79316 8.87139 8.02643 8.60638L11.2479 4.94763L12.1654 5.7552Z",
1642
+ fill: "currentColor"
1643
+ })]
1644
+ });
1645
+ }
1646
+ function GlyphWorkspaceWrite() {
1647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1648
+ width: "16",
1649
+ height: "16",
1650
+ viewBox: "0 0 16 16",
1651
+ fill: "none",
1652
+ "aria-hidden": true,
1653
+ children: [
1654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1655
+ d: "M8.08887 0.251709C8.20479 0.23085 8.32486 0.241168 8.43652 0.282959L15.0215 2.75171C15.2787 2.84819 15.4492 3.09414 15.4492 3.3689V7.0105C15.4492 7.10986 15.4441 7.2081 15.4414 7.30542C15.0285 7.07175 14.5905 6.87695 14.1309 6.73022V3.82495L8.20508 1.60327L2.2793 3.82495V7.0105C2.27936 9.7171 3.4745 11.5379 5.02734 12.7947C5.01025 12.9942 5 13.1962 5 13.4001C5.00001 13.7617 5.02722 14.1169 5.08008 14.4636C2.91555 13.0393 0.961014 10.752 0.960938 7.0105V3.3689C0.960938 3.09417 1.13146 2.84821 1.38867 2.75171L7.97461 0.282959L8.08887 0.251709Z",
1656
+ fill: "currentColor"
1657
+ }),
1658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1659
+ d: "M11.3525 5.64688V6.85688H5V5.64688H11.3525Z",
1660
+ fill: "currentColor"
1661
+ }),
1662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1663
+ d: "M9.5824 8.29376V9.50376H5V8.29376H9.5824Z",
1664
+ fill: "currentColor"
1665
+ }),
1666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1667
+ d: "M14.6647 15.6852H10.0338C10.3878 15.3751 10.7567 15.0517 11.0772 14.7706C11.2531 14.6164 11.4144 14.4746 11.5511 14.3547H14.6647V15.6852Z",
1668
+ fill: "currentColor"
1669
+ }),
1670
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1671
+ d: "M8.14852 14.1308L7.33925 15.4976C7.22458 15.6912 7.42245 15.9194 7.63037 15.8333L9.09785 15.2254L15.0399 10.0719L14.0905 8.97733L8.14852 14.1308Z",
1672
+ fill: "currentColor"
1673
+ })
1674
+ ]
1675
+ });
1676
+ }
1677
+ function GlyphFullAccess() {
1678
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1679
+ width: "16",
1680
+ height: "16",
1681
+ viewBox: "0 0 16 16",
1682
+ fill: "none",
1683
+ "aria-hidden": true,
1684
+ children: [
1685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1686
+ d: SHIELD_OUTLINE,
1687
+ stroke: "currentColor",
1688
+ strokeWidth: "1.31831",
1689
+ strokeLinejoin: "round"
1690
+ }),
1691
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1692
+ d: "M9.10094 4.5V8.75939H7.59888V4.5H9.10094Z",
1693
+ fill: "currentColor"
1694
+ }),
1695
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1696
+ d: "M9.10094 9.8114V11.5H7.59888V9.8114Z",
1697
+ fill: "currentColor"
1698
+ })
1699
+ ]
1700
+ });
1701
+ }
1702
+ const TIER_GLYPHS = {
1703
+ view_only: GlyphViewOnly,
1704
+ workspace_write: GlyphWorkspaceWrite,
1705
+ full_access: GlyphFullAccess
1706
+ };
1707
+ function PermissionSelect(props) {
1708
+ const [open, setOpen] = (0, react.useState)(false);
1709
+ const [confirming, setConfirming] = (0, react.useState)(false);
1710
+ const [acknowledged, setAcknowledged] = (0, react.useState)(false);
1711
+ const tier = asPermissionTier(props.tier) ?? "view_only";
1712
+ (0, react.useEffect)(() => {
1713
+ setOpen(false);
1714
+ setAcknowledged(false);
1715
+ setConfirming(false);
1716
+ }, [tier]);
1717
+ const choose = (id) => {
1718
+ setOpen(false);
1719
+ if (id === tier) return;
1720
+ if (id === "full_access") {
1721
+ setAcknowledged(false);
1722
+ setConfirming(true);
1723
+ return;
1724
+ }
1725
+ props.onSelect(id);
1726
+ };
1727
+ const closeConfirm = () => {
1728
+ setAcknowledged(false);
1729
+ setConfirming(false);
1730
+ };
1731
+ const confirmFullAccess = () => {
1732
+ if (!acknowledged) return;
1733
+ closeConfirm();
1734
+ props.onSelect("full_access");
1735
+ };
1736
+ const items = PERMISSION_TIERS.map((t) => ({
1737
+ id: t,
1738
+ label: TIER_LABELS[t],
1739
+ icon: TIER_GLYPHS[t]()
1740
+ }));
1741
+ const Glyph = TIER_GLYPHS[tier] ?? GlyphViewOnly;
1742
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1743
+ open,
1744
+ items,
1745
+ selectedId: tier,
1746
+ onSelect: choose,
1747
+ onClose: () => {
1748
+ setOpen(false);
1749
+ },
1750
+ side: "top",
1751
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1752
+ type: "button",
1753
+ className: "dsgc-permtrigger",
1754
+ "aria-label": "权限模式:" + TIER_LABELS[tier],
1755
+ title: TIER_DESCRIPTIONS[tier],
1756
+ disabled: confirming,
1757
+ onClick: () => {
1758
+ setOpen(!open);
1759
+ },
1760
+ children: [
1761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1762
+ className: "dsgc-permicon",
1763
+ "aria-hidden": true,
1764
+ children: Glyph()
1765
+ }),
1766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1767
+ className: "dsgc-permlabel",
1768
+ children: TIER_LABELS[tier]
1769
+ }),
1770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1771
+ className: "dsgc-permchevron" + (open ? " open" : ""),
1772
+ "aria-hidden": true,
1773
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, 14)
1774
+ })
1775
+ ]
1776
+ })
1777
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.RiskConfirmation, {
1778
+ open: confirming,
1779
+ title: CONFIRM_TEXT.title,
1780
+ description: CONFIRM_TEXT.description,
1781
+ acknowledgeLabel: CONFIRM_TEXT.acknowledge,
1782
+ cancelLabel: CONFIRM_TEXT.cancel,
1783
+ closeLabel: "关闭",
1784
+ confirmLabel: CONFIRM_TEXT.enable,
1785
+ acknowledged,
1786
+ onAcknowledgedChange: setAcknowledged,
1787
+ onCancel: closeConfirm,
1788
+ onConfirm: confirmFullAccess
1789
+ })] });
1790
+ }
1791
+ //#endregion
1792
+ //#region src/client/components/Composer.tsx
1793
+ function Composer(props) {
1794
+ const { snap, sess, group, enabledRoles, participants, mentionedRoles, busyNow, input, mention, mentionCandidates, mentionIdxC, fileCandidates, fileSearchError, fileSearchLoading, rounds, err, atBottom, bubblesLength, inputRef, scrollRef, setMentionIdx, setRounds, togglePart, onInputCE, onInputKeyDown, onPasteCE, onDropCE, onDragOverCE, insertChip, insertFileChip, sendMsg, stopRun, mutate, setMention } = props;
1795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1796
+ className: "dsgc-composer",
1797
+ children: [
1798
+ !atBottom && bubblesLength ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1799
+ className: "dsgc-tobottom",
1800
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1801
+ type: "button",
1802
+ className: "dsgc-tobtn",
1803
+ onClick: () => {
1804
+ const el = scrollRef.current;
1805
+ if (el) el.scrollTo({
1806
+ top: el.scrollHeight,
1807
+ behavior: "smooth"
1808
+ });
1809
+ },
1810
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, 14), "回到底部"]
1811
+ })
1812
+ }) : null,
1813
+ err ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1814
+ className: "dsgc-err",
1815
+ children: err
1816
+ }) : null,
1817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1818
+ className: "dsgc-parts",
1819
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1820
+ className: "dsgc-partslabel",
1821
+ children: "参与角色"
1822
+ }), mentionedRoles.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1823
+ className: "dsgc-partslabel",
1824
+ children: [
1825
+ "已 @ ",
1826
+ mentionedRoles.map((r) => r.name).join("、"),
1827
+ "(本轮仅被点名成员发言)"
1828
+ ]
1829
+ }) : enabledRoles.length ? enabledRoles.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1830
+ type: "button",
1831
+ className: "dsgc-partchip" + (participants.includes(r.id) ? " on" : ""),
1832
+ onClick: () => togglePart(r.id),
1833
+ disabled: busyNow,
1834
+ title: busyNow ? "对话进行中,暂停调整" : "点击切换本轮是否参与",
1835
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1836
+ className: "dsgc-chipdot",
1837
+ style: { background: r.color || "#888" }
1838
+ }), r.name]
1839
+ }, r.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1840
+ className: "dsgc-hint",
1841
+ children: "还没有启用的角色,请在右侧添加"
1842
+ })]
1843
+ }),
1844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1845
+ className: "dsgc-card",
1846
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1847
+ className: "dsgc-mentionwrap",
1848
+ children: [
1849
+ mention !== null && mention.query === "" && mentionCandidates.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1850
+ className: "dsgc-mention",
1851
+ role: "listbox",
1852
+ children: [mentionCandidates.map((r, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1853
+ type: "button",
1854
+ className: "dsgc-mentionitem" + (i === mentionIdxC ? " on" : ""),
1855
+ role: "option",
1856
+ "aria-selected": i === mentionIdxC ? "true" : "false",
1857
+ onMouseDown: (e) => {
1858
+ e.preventDefault();
1859
+ },
1860
+ onClick: () => {
1861
+ insertChip(r);
1862
+ },
1863
+ onMouseEnter: () => setMentionIdx(i),
1864
+ children: [
1865
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1866
+ className: "dsgc-chipdot",
1867
+ style: { background: r.color || "#888" }
1868
+ }),
1869
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1870
+ className: "dsgc-mentionname",
1871
+ children: r.name
1872
+ }),
1873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1874
+ className: "dsgc-mentionmodel",
1875
+ children: [
1876
+ r.provider,
1877
+ " / ",
1878
+ r.model
1879
+ ]
1880
+ })
1881
+ ]
1882
+ }, r.id)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1883
+ className: "dsgc-mentionhint",
1884
+ children: "↑↓ 选择 · Enter/Tab 插入"
1885
+ })]
1886
+ }) : mention !== null && mention.query !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1887
+ className: "dsgc-mention dsgc-mention-file",
1888
+ role: "listbox",
1889
+ children: [fileCandidates.length ? fileCandidates.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1890
+ type: "button",
1891
+ className: "dsgc-mentionitem" + (i === mentionIdxC ? " on" : ""),
1892
+ role: "option",
1893
+ "aria-selected": i === mentionIdxC ? "true" : "false",
1894
+ onMouseDown: (e) => {
1895
+ e.preventDefault();
1896
+ },
1897
+ onClick: () => {
1898
+ insertFileChip(item.path, item.isDir ? "directory" : "file");
1899
+ },
1900
+ onMouseEnter: () => setMentionIdx(i),
1901
+ children: [
1902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1903
+ className: "dsgc-fileglyph" + (item.isDir ? " dir" : " file"),
1904
+ "aria-hidden": "true",
1905
+ children: item.isDir ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
1906
+ kind: "folder",
1907
+ size: 16
1908
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
1909
+ path: item.path,
1910
+ size: 16
1911
+ })
1912
+ }),
1913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1914
+ className: "dsgc-mentionname",
1915
+ children: item.path
1916
+ }),
1917
+ item.isDir ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1918
+ className: "dsgc-filedrill",
1919
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, 12)
1920
+ }) : null
1921
+ ]
1922
+ }, item.path)) : fileSearchLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1923
+ className: "dsgc-mentionitem dsgc-hint",
1924
+ children: "检索中…"
1925
+ }) : fileSearchError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1926
+ className: "dsgc-mentionitem dsgc-hint",
1927
+ children: fileSearchError
1928
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1929
+ className: "dsgc-mentionitem dsgc-hint",
1930
+ children: "无匹配文件"
1931
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1932
+ className: "dsgc-mentionhint",
1933
+ children: fileSearchLoading && fileCandidates.length ? "检索中…" : fileCandidates.some((f) => f.isDir) ? "↑↓ 选择 · Enter 插入 · Tab 进入目录 · Esc 关闭" : "↑↓ 选择 · Enter 插入 · Esc 关闭"
1934
+ })]
1935
+ }) : null,
1936
+ !input.trim() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1937
+ className: "dsgc-ph",
1938
+ "aria-hidden": "true",
1939
+ children: "发消息给全群,@成员 点名让其回应(留空则让角色自由讨论)…"
1940
+ }) : null,
1941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1942
+ className: "dsgc-edit",
1943
+ ref: inputRef,
1944
+ contentEditable: true,
1945
+ suppressContentEditableWarning: true,
1946
+ role: "textbox",
1947
+ "aria-multiline": "true",
1948
+ "aria-label": "群聊消息输入框",
1949
+ onInput: onInputCE,
1950
+ onKeyDown: onInputKeyDown,
1951
+ onPaste: onPasteCE,
1952
+ onDrop: onDropCE,
1953
+ onDragOver: onDragOverCE,
1954
+ onBlur: () => {
1955
+ setMention(null);
1956
+ }
1957
+ })
1958
+ ]
1959
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1960
+ className: "dsgc-sendrow",
1961
+ children: [
1962
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionSelect, {
1963
+ tier: group.permissionTier,
1964
+ onSelect: (tier) => {
1965
+ mutate({
1966
+ op: "setPermissionTier",
1967
+ groupId: group.id,
1968
+ tier
1969
+ });
1970
+ }
1971
+ }),
1972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
1973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1974
+ className: "dsgc-rounds",
1975
+ title: "自由讨论的轮数(1–10):一轮 = 全体参与角色按顺序各发言一次",
1976
+ children: [
1977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1978
+ type: "button",
1979
+ className: "dsgc-roundbtn",
1980
+ "aria-label": "减少轮数",
1981
+ disabled: rounds <= 1,
1982
+ onClick: () => {
1983
+ setRounds(Math.max(1, rounds - 1));
1984
+ },
1985
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, 12)
1986
+ }),
1987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1988
+ className: "dsgc-roundnum",
1989
+ title: "轮数",
1990
+ children: rounds
1991
+ }),
1992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1993
+ type: "button",
1994
+ className: "dsgc-roundbtn",
1995
+ "aria-label": "增加轮数",
1996
+ disabled: rounds >= 10,
1997
+ onClick: () => {
1998
+ setRounds(Math.min(10, rounds + 1));
1999
+ },
2000
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, 12)
2001
+ }),
2002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2003
+ style: { padding: "0 6px 0 2px" },
2004
+ children: "轮"
2005
+ })
2006
+ ]
2007
+ }),
2008
+ busyNow ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2009
+ variant: "outline",
2010
+ className: "dsgc-stopbtn",
2011
+ onClick: () => {
2012
+ stopRun();
2013
+ },
2014
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconStopFill16, 16), "停止"]
2015
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2016
+ variant: "primary",
2017
+ onClick: () => {
2018
+ sendMsg();
2019
+ },
2020
+ disabled: !participants.length && !mentionedRoles.length || !sess,
2021
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline16, 16), "发送"]
2022
+ })
2023
+ ]
2024
+ })]
2025
+ })
2026
+ ]
2027
+ });
2028
+ }
2029
+ //#endregion
2030
+ //#region src/client/components/ChatPanel.tsx
2031
+ function ChatPanel(props) {
2032
+ const { snap, sess, group, enabledRoles, participants, mentionedRoles, busyNow, input, mention, mentionCandidates, mentionIdxC, fileCandidates, fileSearchError, fileSearchLoading, rounds, err, atBottom, topicDraft, msgById, navOpen, asideOpen, inputRef, scrollRef, setTopicDraft, setConfirmClear, setMentionIdx, setRounds, setNavOpen, setAsideOpen, togglePart, onMsgsScroll, onInputCE, onInputKeyDown, onPasteCE, onDropCE, onDragOverCE, insertChip, insertFileChip, sendMsg, stopRun, action, mutate, setMention } = props;
2033
+ const commitTopic = () => {
2034
+ if (topicDraft !== null && sess && topicDraft !== sess.topic) mutate({
2035
+ op: "setTopic",
2036
+ sessionId: sess.id,
2037
+ topic: topicDraft
2038
+ });
2039
+ setTopicDraft(null);
2040
+ };
2041
+ const bubbles = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFlow, {
2042
+ snap,
2043
+ sess,
2044
+ busyNow,
2045
+ msgById,
2046
+ action
2047
+ });
2048
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2049
+ className: "dsgc-chat",
2050
+ children: [
2051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2052
+ type: "button",
2053
+ className: "dsgc-seambtn left" + (navOpen ? "" : " closed"),
2054
+ "aria-label": navOpen ? "收起群组导航栏" : "展开群组导航栏",
2055
+ title: navOpen ? "收起群组导航栏" : "展开群组导航栏",
2056
+ "aria-expanded": navOpen,
2057
+ onClick: () => {
2058
+ setNavOpen(!navOpen);
2059
+ },
2060
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, 16)
2061
+ }),
2062
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2063
+ type: "button",
2064
+ className: "dsgc-seambtn right" + (asideOpen ? "" : " closed"),
2065
+ "aria-label": asideOpen ? "收起成员与工作区栏" : "展开成员与工作区栏",
2066
+ title: asideOpen ? "收起成员与工作区栏" : "展开成员与工作区栏",
2067
+ "aria-expanded": asideOpen,
2068
+ onClick: () => {
2069
+ setAsideOpen(!asideOpen);
2070
+ },
2071
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, 16)
2072
+ }),
2073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2074
+ className: "dsgc-chathead",
2075
+ children: [
2076
+ sess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2077
+ className: "dsgc-sess-title",
2078
+ title: "当前会话:" + sess.name,
2079
+ children: sess.name
2080
+ }) : null,
2081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2082
+ className: "dsgc-topic",
2083
+ value: topicDraft === null ? sess ? sess.topic : "" : topicDraft,
2084
+ placeholder: "设置本会话主题(可选)…",
2085
+ onChange: (e) => {
2086
+ setTopicDraft(e.target.value);
2087
+ },
2088
+ onBlur: commitTopic,
2089
+ onKeyDown: (e) => {
2090
+ if (e.key === "Enter") e.target.blur();
2091
+ }
2092
+ }),
2093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2094
+ variant: "ghost",
2095
+ size: "sm",
2096
+ className: "dsgc-clearbtn",
2097
+ title: "清空当前会话的消息记录",
2098
+ onClick: () => {
2099
+ if (sess) setConfirmClear(true);
2100
+ },
2101
+ children: "清空"
2102
+ })
2103
+ ]
2104
+ }),
2105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2106
+ className: "dsgc-msgs",
2107
+ ref: scrollRef,
2108
+ onScroll: onMsgsScroll,
2109
+ children: sess && sess.messageIds.length > 0 ? bubbles : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2110
+ className: "dsgc-empty",
2111
+ children: [
2112
+ Icon(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, 20),
2113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2114
+ className: "dsgc-emptytitle",
2115
+ children: sess && sess.topic ? "「" + sess.topic + "」" : "会话已就绪"
2116
+ }),
2117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2118
+ className: "dsgc-hint",
2119
+ children: enabledRoles.length ? "发送消息开始讨论;@成员 点名让其优先回应;留空直接发送可让角色自由讨论——每轮全体参与角色按顺序各发言一次,可用右下角轮数控制(1–10 轮)" : "先在右侧添加角色(每个角色可绑定不同模型),再回到这里发起讨论。"
2120
+ })
2121
+ ]
2122
+ })
2123
+ }),
2124
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Composer, {
2125
+ snap,
2126
+ sess,
2127
+ group,
2128
+ enabledRoles,
2129
+ participants,
2130
+ mentionedRoles,
2131
+ busyNow,
2132
+ input,
2133
+ mention,
2134
+ mentionCandidates,
2135
+ mentionIdxC,
2136
+ fileCandidates,
2137
+ fileSearchError,
2138
+ fileSearchLoading,
2139
+ rounds,
2140
+ err,
2141
+ atBottom,
2142
+ bubblesLength: sess?.messageIds.length || 0,
2143
+ inputRef,
2144
+ scrollRef,
2145
+ setMentionIdx,
2146
+ setRounds,
2147
+ togglePart,
2148
+ onInputCE,
2149
+ onInputKeyDown,
2150
+ onPasteCE,
2151
+ onDropCE,
2152
+ onDragOverCE,
2153
+ insertChip,
2154
+ insertFileChip,
2155
+ sendMsg,
2156
+ stopRun,
2157
+ mutate,
2158
+ setMention
2159
+ })
2160
+ ]
2161
+ });
2162
+ }
2163
+ //#endregion
2164
+ //#region src/client/hooks/useGroupChatState.ts
2165
+ /**
2166
+ * 群聊面板核心状态管理 hook
2167
+ * @module dsh-group-chat/client/hooks
2168
+ */
2169
+ /**
2170
+ * 面板切换缓存(模块级,跨挂载存活):main 为 keyed 槽,主会话⇄群聊切换会
2171
+ * 整体卸载重挂面板组件;缓存最近快照与选中项让重挂载瞬时恢复上次视图,
2172
+ * 挂载后的 state 拉取 / SSE 首帧再行校正(本机回路往返仅数毫秒)。
2173
+ */
2174
+ let cachedSnap = null;
2175
+ let cachedGid = null;
2176
+ let cachedSid = null;
2177
+ function useGroupChatState() {
2178
+ const [snap, setSnap] = (0, react.useState)(() => cachedSnap);
2179
+ const [gid, setGid] = (0, react.useState)(() => cachedGid);
2180
+ const [sid, setSid] = (0, react.useState)(() => cachedSid);
2181
+ /** 已应用过的 lastCreated 会话标记:只在标记变化(真正的新建)时跟随选中,
2182
+ 而不是每帧都把视图拽回「最近创建的会话」(流式期间每 120ms 一帧)。 */
2183
+ const appliedCreatedRef = (0, react.useRef)(cachedSnap?.lastCreated?.sessionId ?? null);
2184
+ /** 快照落地统一口:写状态 + 写切换缓存。 */
2185
+ const applySnap = (0, react.useCallback)((s) => {
2186
+ cachedSnap = s;
2187
+ setSnap(s);
2188
+ }, []);
2189
+ /** 选中项统一口:写状态 + 写切换缓存(重挂载恢复到用户离开时的位置)。 */
2190
+ const selectGroup = (0, react.useCallback)((v) => {
2191
+ cachedGid = v;
2192
+ setGid(v);
2193
+ }, []);
2194
+ const selectSession = (0, react.useCallback)((v) => {
2195
+ cachedSid = v;
2196
+ setSid(v);
2197
+ }, []);
2198
+ const [search, setSearch] = (0, react.useState)("");
2199
+ const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(() => /* @__PURE__ */ new Set());
2200
+ const [asideOpen, setAsideOpen] = (0, react.useState)(true);
2201
+ const [navOpen, setNavOpen] = (0, react.useState)(true);
2202
+ const [atBottom, setAtBottom] = (0, react.useState)(true);
2203
+ const [renameDraft, setRenameDraft] = (0, react.useState)(null);
2204
+ const [confirmDel, setConfirmDel] = (0, react.useState)(null);
2205
+ const [confirmClear, setConfirmClear] = (0, react.useState)(false);
2206
+ const [topicDraft, setTopicDraft] = (0, react.useState)(null);
2207
+ const [wsDraft, setWsDraft] = (0, react.useState)(null);
2208
+ const [roleDraft, setRoleDraft] = (0, react.useState)(null);
2209
+ const [roleFormError, setRoleFormError] = (0, react.useState)("");
2210
+ const [models, setModels] = (0, react.useState)(null);
2211
+ const [modelsError, setModelsError] = (0, react.useState)(null);
2212
+ const [fileBrowser, setFileBrowser] = (0, react.useState)(null);
2213
+ const [partsSel, setPartsSel] = (0, react.useState)(null);
2214
+ const [rounds, setRounds] = (0, react.useState)(1);
2215
+ const [input, setInput] = (0, react.useState)("");
2216
+ const [mention, setMention] = (0, react.useState)(null);
2217
+ const [mentionIdx, setMentionIdx] = (0, react.useState)(0);
2218
+ const [err, setErr] = (0, react.useState)("");
2219
+ const inputRef = (0, react.useRef)(null);
2220
+ const sendingRef = (0, react.useRef)(false);
2221
+ const scrollRef = (0, react.useRef)(null);
2222
+ (0, react.useEffect)(() => {
2223
+ let live = true;
2224
+ api.state().then((s) => {
2225
+ if (live && s && s.ok) applySnap(s);
2226
+ }).catch(() => {});
2227
+ const events = new EventSource("/api/group-chat/events");
2228
+ events.onmessage = (message) => {
2229
+ try {
2230
+ const s = JSON.parse(message.data);
2231
+ if (!live || !s || !s.ok) return;
2232
+ if (s.lastCreated && s.lastCreated.sessionId && s.lastCreated.sessionId !== appliedCreatedRef.current) {
2233
+ appliedCreatedRef.current = s.lastCreated.sessionId;
2234
+ selectGroup(s.lastCreated.groupId);
2235
+ selectSession(s.lastCreated.sessionId);
2236
+ setPartsSel(null);
2237
+ }
2238
+ applySnap(s);
2239
+ } catch {}
2240
+ };
2241
+ return () => {
2242
+ live = false;
2243
+ events.close();
2244
+ };
2245
+ }, [
2246
+ applySnap,
2247
+ selectGroup,
2248
+ selectSession
2249
+ ]);
2250
+ const action = (0, react.useCallback)(async (payload) => {
2251
+ try {
2252
+ return await api.action(payload);
2253
+ } catch (e) {
2254
+ setErr(String(e && e.message || e));
2255
+ return null;
2256
+ }
2257
+ }, []);
2258
+ const mutate = async (args) => {
2259
+ setErr("");
2260
+ const res = await action(Object.assign({ kind: "mutate" }, args));
2261
+ if (res && res.ok && res.snapshot) {
2262
+ if (res.lastCreated && res.lastCreated.sessionId && res.lastCreated.sessionId !== appliedCreatedRef.current) {
2263
+ appliedCreatedRef.current = res.lastCreated.sessionId;
2264
+ selectGroup(res.lastCreated.groupId);
2265
+ selectSession(res.lastCreated.sessionId);
2266
+ setPartsSel(null);
2267
+ }
2268
+ applySnap(res.snapshot);
2269
+ if (res.snapshot.error) setErr(res.snapshot.error);
2270
+ }
2271
+ return res;
2272
+ };
2273
+ return {
2274
+ snap,
2275
+ setSnap: applySnap,
2276
+ gid,
2277
+ setGid: selectGroup,
2278
+ sid,
2279
+ setSid: selectSession,
2280
+ search,
2281
+ setSearch,
2282
+ collapsedGroups,
2283
+ setCollapsedGroups,
2284
+ renameDraft,
2285
+ setRenameDraft,
2286
+ confirmDel,
2287
+ setConfirmDel,
2288
+ confirmClear,
2289
+ setConfirmClear,
2290
+ roleDraft,
2291
+ setRoleDraft,
2292
+ roleFormError,
2293
+ setRoleFormError,
2294
+ models,
2295
+ setModels,
2296
+ modelsError,
2297
+ setModelsError,
2298
+ fileBrowser,
2299
+ setFileBrowser,
2300
+ wsDraft,
2301
+ setWsDraft,
2302
+ partsSel,
2303
+ setPartsSel,
2304
+ rounds,
2305
+ setRounds,
2306
+ input,
2307
+ setInput,
2308
+ err,
2309
+ setErr,
2310
+ topicDraft,
2311
+ setTopicDraft,
2312
+ mention,
2313
+ setMention,
2314
+ mentionIdx,
2315
+ setMentionIdx,
2316
+ asideOpen,
2317
+ setAsideOpen,
2318
+ navOpen,
2319
+ setNavOpen,
2320
+ atBottom,
2321
+ setAtBottom,
2322
+ inputRef,
2323
+ sendingRef,
2324
+ scrollRef,
2325
+ action,
2326
+ mutate
2327
+ };
2328
+ }
2329
+ //#endregion
2330
+ //#region src/shared/file-mention-grammar.ts
2331
+ /**
2332
+ * Extract active @token at cursor position.
2333
+ * Matches @ at word boundary (line start or after whitespace).
2334
+ *
2335
+ * @param line - Current line text
2336
+ * @param cursor - Cursor offset in line
2337
+ * @returns Token or undefined if no active @token at cursor
2338
+ */
2339
+ function activeAtToken(line, cursor) {
2340
+ const before = line.slice(0, cursor);
2341
+ const quotedMatch = /(?:^|\s)@"([^"]*)$/.exec(before);
2342
+ if (quotedMatch) return {
2343
+ prefix: quotedMatch[0].trimStart(),
2344
+ query: quotedMatch[1],
2345
+ quoted: true
2346
+ };
2347
+ const plainMatch = /(?:^|\s)@([^\s@]*)$/.exec(before);
2348
+ if (plainMatch) return {
2349
+ prefix: plainMatch[0].trimStart(),
2350
+ query: plainMatch[1],
2351
+ quoted: false
2352
+ };
2353
+ }
2354
+ //#endregion
2355
+ //#region src/client/utils/utils.ts
2356
+ /**
2357
+ * 通用工具函数
2358
+ * @module dsh-group-chat/client/utils
2359
+ */
2360
+ /** HTML 转义(芯片以 execCommand('insertHTML') 注入,角色名需转义)。 */
2361
+ function escapeHtml(v) {
2362
+ return String(v).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2363
+ }
2364
+ let execCommandFn;
2365
+ function execCommand(commandId, value) {
2366
+ execCommandFn ??= document.execCommand;
2367
+ return execCommandFn.call(document, commandId, false, value);
2368
+ }
2369
+ /**
2370
+ * contenteditable 输入区 → 纯文本序列化(发送/参与判定的唯一事实源):
2371
+ * 文本节点原样;<br> → 换行;角色芯片(.dsgc-chipin[data-role-id])展开回「@名字␠」;
2372
+ * 文件芯片(.dsgc-chipin[data-kind=file])展开回「@path」或「@"path with spaces"」;
2373
+ * DIV/P 块前补换行(防粘贴残留的块级包裹)。
2374
+ */
2375
+ function serializeInput(root) {
2376
+ const walk = (node) => {
2377
+ if (node.nodeType === Node.TEXT_NODE) return node.nodeValue || "";
2378
+ if (node.nodeType !== Node.ELEMENT_NODE) return "";
2379
+ const el = node;
2380
+ if (el.tagName === "BR") return "\n";
2381
+ if (el.classList.contains("dsgc-chipin")) {
2382
+ if (el.dataset.roleId) return "@" + (el.dataset.name || "") + " ";
2383
+ if (el.dataset.kind === "file") {
2384
+ const path = el.dataset.path || "";
2385
+ return /\s/.test(path) ? `@"${path}"` : `@${path}`;
2386
+ }
2387
+ }
2388
+ return (/^(DIV|P)$/.test(el.tagName) ? "\n" : "") + Array.from(el.childNodes).map(walk).join("");
2389
+ };
2390
+ return Array.from(root.childNodes).map(walk).join("");
2391
+ }
2392
+ /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
2393
+ function chipHtml(role) {
2394
+ const c = escapeHtml(role.color || "#888");
2395
+ return "<span class=\"dsgc-chipin\" data-role-id=\"" + escapeHtml(role.id) + "\" data-name=\"" + escapeHtml(role.name) + "\" style=\"--role-color:" + c + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipdot\" style=\"background:" + c + "\"></span>" + escapeHtml(role.name) + "</span>";
2396
+ }
2397
+ /** 把宿主 FileTypeIcon 渲成静态 SVG,再塞进 insertHTML(与检索列表同一套字形)。 */
2398
+ function fileTypeIconMarkup(path, kind) {
2399
+ if (typeof document === "undefined") return "";
2400
+ const host = document.createElement("span");
2401
+ const root = (0, react_dom_client.createRoot)(host);
2402
+ try {
2403
+ (0, react_dom.flushSync)(() => {
2404
+ root.render(kind === "directory" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
2405
+ kind: "folder",
2406
+ size: 14
2407
+ }) : (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
2408
+ path,
2409
+ size: 14
2410
+ }));
2411
+ });
2412
+ return host.innerHTML;
2413
+ } finally {
2414
+ root.unmount();
2415
+ }
2416
+ }
2417
+ /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
2418
+ function fileChipHtml(path, kind) {
2419
+ const basename = path.split("/").pop() || path;
2420
+ return "<span class=\"dsgc-chipin dsgc-chipin-file\" data-kind=\"file\"" + (kind === "directory" ? " data-dir=\"1\"" : "") + " data-path=\"" + escapeHtml(path) + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipglyph\" aria-hidden=\"true\">" + fileTypeIconMarkup(path, kind) + "</span>" + escapeHtml(basename) + "</span>";
2421
+ }
2422
+ /**
2423
+ * 光标前的活跃 @token(角色或文件弹层触发判定)。
2424
+ *
2425
+ * @returns AtToken 对象,包含 prefix/query/quoted;无返回 null
2426
+ */
2427
+ function queryAtCaret() {
2428
+ const sel = window.getSelection();
2429
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return null;
2430
+ const node = sel.anchorNode;
2431
+ if (!node || node.nodeType !== Node.TEXT_NODE) return null;
2432
+ const before = (node.nodeValue || "").slice(0, sel.anchorOffset);
2433
+ return activeAtToken(before, before.length) || null;
2434
+ }
2435
+ //#endregion
2436
+ //#region src/client/hooks/useComposer.ts
2437
+ /**
2438
+ * 消息输入相关 hooks
2439
+ * @module dsh-group-chat/client/hooks
2440
+ */
2441
+ function useComposerEffects(inputRef, scrollRef, input, atBottom, snap) {
2442
+ const onMsgsScroll = (0, react.useCallback)(() => {
2443
+ const el = scrollRef.current;
2444
+ if (!el) return;
2445
+ return el.scrollHeight - el.scrollTop - el.clientHeight < 60;
2446
+ }, [scrollRef]);
2447
+ (0, react.useEffect)(() => {
2448
+ const el = scrollRef.current;
2449
+ if (el && atBottom) el.scrollTop = el.scrollHeight;
2450
+ }, [
2451
+ snap,
2452
+ atBottom,
2453
+ scrollRef
2454
+ ]);
2455
+ (0, react.useEffect)(() => {
2456
+ const el = inputRef.current;
2457
+ if (!el) return;
2458
+ el.style.height = "auto";
2459
+ el.style.height = Math.min(180, Math.max(36, el.scrollHeight)) + "px";
2460
+ }, [input, inputRef]);
2461
+ return { onMsgsScroll };
2462
+ }
2463
+ function useComposerInput(inputRef, setInput, setMention, setMentionIdx) {
2464
+ /** 从 DOM 同步 input 状态(序列化) */
2465
+ const syncFromDOM = (0, react.useCallback)(() => {
2466
+ const el = inputRef.current;
2467
+ if (!el) return;
2468
+ setInput(serializeInput(el));
2469
+ }, [inputRef, setInput]);
2470
+ return {
2471
+ syncFromDOM,
2472
+ onInputCE: (0, react.useCallback)(() => {
2473
+ syncFromDOM();
2474
+ setMention(queryAtCaret());
2475
+ setMentionIdx(0);
2476
+ }, [
2477
+ syncFromDOM,
2478
+ setMention,
2479
+ setMentionIdx
2480
+ ]),
2481
+ onPasteCE: (0, react.useCallback)((e) => {
2482
+ e.preventDefault();
2483
+ const text = e.clipboardData.getData("text/plain");
2484
+ if (text) execCommand("insertText", text);
2485
+ }, []),
2486
+ onDragOverCE: (0, react.useCallback)((e) => {
2487
+ e.preventDefault();
2488
+ }, []),
2489
+ onDropCE: (0, react.useCallback)((e) => {
2490
+ e.preventDefault();
2491
+ const text = e.dataTransfer.getData("text/plain");
2492
+ if (text) execCommand("insertText", text);
2493
+ }, [])
2494
+ };
2495
+ }
2496
+ function useMentionChip(inputRef, setMention, setMentionIdx, syncFromDOM) {
2497
+ return {
2498
+ insertChip: (0, react.useCallback)((role) => {
2499
+ const el = inputRef.current;
2500
+ const sel = window.getSelection();
2501
+ if (!el || !sel) return;
2502
+ if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
2503
+ el.focus({ preventScroll: true });
2504
+ const node = sel.anchorNode;
2505
+ if (node.nodeType === Node.TEXT_NODE) {
2506
+ const text = node.nodeValue || "";
2507
+ const off = sel.anchorOffset;
2508
+ const before = text.slice(0, off);
2509
+ const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
2510
+ if (m) {
2511
+ const prefixLen = m[1] ? 2 : 1;
2512
+ const queryLen = m[2].length;
2513
+ const start = off - prefixLen - queryLen;
2514
+ if (start >= 0) {
2515
+ const range = document.createRange();
2516
+ range.setStart(node, start);
2517
+ range.setEnd(node, off);
2518
+ sel.removeAllRanges();
2519
+ sel.addRange(range);
2520
+ execCommand("delete");
2521
+ }
2522
+ }
2523
+ }
2524
+ execCommand("insertHTML", chipHtml(role).replace("class=\"dsgc-chipin\"", "class=\"dsgc-chipin\" data-new=\"\""));
2525
+ const chip = el.querySelector(".dsgc-chipin[data-new]");
2526
+ if (!chip) return;
2527
+ const after = document.createRange();
2528
+ after.setStartAfter(chip);
2529
+ after.collapse(true);
2530
+ sel.removeAllRanges();
2531
+ sel.addRange(after);
2532
+ execCommand("insertText", " ");
2533
+ chip.removeAttribute("data-new");
2534
+ setMention(null);
2535
+ setMentionIdx(0);
2536
+ syncFromDOM();
2537
+ }, [
2538
+ inputRef,
2539
+ setMention,
2540
+ setMentionIdx,
2541
+ syncFromDOM
2542
+ ]),
2543
+ insertFileChip: (0, react.useCallback)((path, kind) => {
2544
+ const el = inputRef.current;
2545
+ const sel = window.getSelection();
2546
+ if (!el || !sel) return;
2547
+ if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
2548
+ el.focus({ preventScroll: true });
2549
+ const node = sel.anchorNode;
2550
+ if (node.nodeType === Node.TEXT_NODE) {
2551
+ const text = node.nodeValue || "";
2552
+ const off = sel.anchorOffset;
2553
+ const before = text.slice(0, off);
2554
+ const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
2555
+ if (m) {
2556
+ const prefixLen = m[1] ? 2 : 1;
2557
+ const queryLen = m[2].length;
2558
+ const start = off - prefixLen - queryLen;
2559
+ if (start >= 0) {
2560
+ const range = document.createRange();
2561
+ range.setStart(node, start);
2562
+ range.setEnd(node, off);
2563
+ sel.removeAllRanges();
2564
+ sel.addRange(range);
2565
+ execCommand("delete");
2566
+ }
2567
+ }
2568
+ }
2569
+ execCommand("insertHTML", fileChipHtml(path, kind).replace("class=\"dsgc-chipin dsgc-chipin-file\"", "class=\"dsgc-chipin dsgc-chipin-file\" data-new=\"\""));
2570
+ const chip = el.querySelector(".dsgc-chipin[data-new]");
2571
+ if (!chip) return;
2572
+ const after = document.createRange();
2573
+ after.setStartAfter(chip);
2574
+ after.collapse(true);
2575
+ sel.removeAllRanges();
2576
+ sel.addRange(after);
2577
+ execCommand("insertText", " ");
2578
+ chip.removeAttribute("data-new");
2579
+ setMention(null);
2580
+ setMentionIdx(0);
2581
+ syncFromDOM();
2582
+ }, [
2583
+ inputRef,
2584
+ setMention,
2585
+ setMentionIdx,
2586
+ syncFromDOM
2587
+ ])
2588
+ };
2589
+ }
2590
+ function useInputKeyboard(mention, mentionCandidates, mentionIdxC, setMentionIdx, insertChip, insertFileChip, fileCandidates, setMention, sendMsg) {
2591
+ return { onInputKeyDown: (0, react.useCallback)((e) => {
2592
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
2593
+ if (mention !== null && mention.query === "" && mentionCandidates.length) {
2594
+ if (e.key === "ArrowDown") {
2595
+ e.preventDefault();
2596
+ setMentionIdx((mentionIdxC + 1) % mentionCandidates.length);
2597
+ return;
2598
+ }
2599
+ if (e.key === "ArrowUp") {
2600
+ e.preventDefault();
2601
+ setMentionIdx((mentionIdxC - 1 + mentionCandidates.length) % mentionCandidates.length);
2602
+ return;
2603
+ }
2604
+ if (e.key === "Enter" && !e.shiftKey || e.key === "Tab") {
2605
+ e.preventDefault();
2606
+ insertChip(mentionCandidates[mentionIdxC]);
2607
+ return;
2608
+ }
2609
+ }
2610
+ if (mention !== null && mention.query !== "" && fileCandidates.length) {
2611
+ if (e.key === "ArrowDown") {
2612
+ e.preventDefault();
2613
+ setMentionIdx((mentionIdxC + 1) % fileCandidates.length);
2614
+ return;
2615
+ }
2616
+ if (e.key === "ArrowUp") {
2617
+ e.preventDefault();
2618
+ setMentionIdx((mentionIdxC - 1 + fileCandidates.length) % fileCandidates.length);
2619
+ return;
2620
+ }
2621
+ const selectedItem = fileCandidates[mentionIdxC];
2622
+ if (selectedItem) {
2623
+ if (e.key === "Enter" && !e.shiftKey) {
2624
+ e.preventDefault();
2625
+ insertFileChip(selectedItem.path, selectedItem.isDir ? "directory" : "file");
2626
+ return;
2627
+ }
2628
+ if (e.key === "Tab" && selectedItem.isDir) {
2629
+ e.preventDefault();
2630
+ insertFileChip(selectedItem.path, "directory");
2631
+ return;
2632
+ }
2633
+ }
2634
+ if (e.key === "Escape") {
2635
+ e.preventDefault();
2636
+ setMention(null);
2637
+ return;
2638
+ }
2639
+ }
2640
+ if (e.key === "Enter" && e.shiftKey) {
2641
+ e.preventDefault();
2642
+ execCommand("insertLineBreak");
2643
+ return;
2644
+ }
2645
+ if (e.key === "Enter" && !e.shiftKey) {
2646
+ e.preventDefault();
2647
+ sendMsg();
2648
+ }
2649
+ }, [
2650
+ mention,
2651
+ mentionCandidates,
2652
+ mentionIdxC,
2653
+ fileCandidates,
2654
+ setMentionIdx,
2655
+ insertChip,
2656
+ insertFileChip,
2657
+ setMention,
2658
+ sendMsg
2659
+ ]) };
2660
+ }
2661
+ //#endregion
2662
+ //#region src/client/hooks/useFileSearch.ts
2663
+ /**
2664
+ * 文件搜索 hook:防抖 + 取消过期请求 + 短缓存。
2665
+ * @module dsh-group-chat/client/hooks
2666
+ */
2667
+ const DEBOUNCE_MS = 180;
2668
+ const CACHE_LIMIT = 20;
2669
+ const cache = /* @__PURE__ */ new Map();
2670
+ function cacheGet(key) {
2671
+ const hit = cache.get(key);
2672
+ if (!hit) return void 0;
2673
+ cache.delete(key);
2674
+ cache.set(key, hit);
2675
+ return hit;
2676
+ }
2677
+ function cacheSet(key, value) {
2678
+ cache.delete(key);
2679
+ cache.set(key, value);
2680
+ if (cache.size > CACHE_LIMIT) {
2681
+ const oldest = cache.keys().next().value;
2682
+ if (oldest !== void 0) cache.delete(oldest);
2683
+ }
2684
+ }
2685
+ function isAbort(err) {
2686
+ return !!err && typeof err === "object" && "name" in err && err.name === "AbortError";
2687
+ }
2688
+ /**
2689
+ * 文件搜索:query 变化 180ms 内合并;过期 fetch 真正 abort;
2690
+ * 命中短缓存立刻出结果。离开文件模式才清空列表,避免每个按键闪「检索中」。
2691
+ */
2692
+ function useFileSearch(mention, groupId) {
2693
+ const [fileCandidates, setFileCandidates] = (0, react.useState)([]);
2694
+ const [fileSearchError, setFileSearchError] = (0, react.useState)(null);
2695
+ const [fileSearchLoading, setFileSearchLoading] = (0, react.useState)(false);
2696
+ const query = mention && mention.query !== "" ? mention.query : null;
2697
+ (0, react.useEffect)(() => {
2698
+ if (query === null || !groupId) {
2699
+ setFileCandidates([]);
2700
+ setFileSearchError(null);
2701
+ setFileSearchLoading(false);
2702
+ return;
2703
+ }
2704
+ const key = groupId + "\0" + query;
2705
+ const hit = cacheGet(key);
2706
+ if (hit) {
2707
+ setFileCandidates(hit);
2708
+ setFileSearchError(null);
2709
+ setFileSearchLoading(false);
2710
+ return;
2711
+ }
2712
+ const controller = new AbortController();
2713
+ setFileSearchLoading(true);
2714
+ const timer = window.setTimeout(() => {
2715
+ (async () => {
2716
+ try {
2717
+ const res = await api.action({
2718
+ kind: "fileSearch",
2719
+ groupId,
2720
+ query
2721
+ }, controller.signal);
2722
+ if (controller.signal.aborted) return;
2723
+ if (res.ok) {
2724
+ const next = res.candidates || [];
2725
+ cacheSet(key, next);
2726
+ setFileCandidates(next);
2727
+ setFileSearchError(null);
2728
+ } else {
2729
+ setFileCandidates([]);
2730
+ setFileSearchError(res.error || "文件检索失败");
2731
+ }
2732
+ } catch (err) {
2733
+ if (controller.signal.aborted || isAbort(err)) return;
2734
+ setFileCandidates([]);
2735
+ setFileSearchError(String(err));
2736
+ } finally {
2737
+ if (!controller.signal.aborted) setFileSearchLoading(false);
2738
+ }
2739
+ })();
2740
+ }, DEBOUNCE_MS);
2741
+ return () => {
2742
+ window.clearTimeout(timer);
2743
+ controller.abort();
2744
+ };
2745
+ }, [query, groupId]);
2746
+ return {
2747
+ fileCandidates,
2748
+ fileSearchError,
2749
+ fileSearchLoading
2750
+ };
2751
+ }
2752
+ //#endregion
2753
+ //#region src/client/hooks/useMentionState.ts
2754
+ /**
2755
+ * 提及状态 hook:封装角色/文件提及相关的派生状态计算
2756
+ * @module dsh-group-chat/client/hooks
2757
+ */
2758
+ /**
2759
+ * 计算角色提及候选列表
2760
+ * @param mention - 当前 @ token 状态
2761
+ * @param enabledRoles - 启用的角色列表
2762
+ * @returns 角色候选列表(仅在角色模式且 query 为空时返回)
2763
+ */
2764
+ function useMentionCandidates(mention, enabledRoles) {
2765
+ return (0, react.useMemo)(() => {
2766
+ return mention !== null && mention.query === "" ? enabledRoles : [];
2767
+ }, [mention, enabledRoles]);
2768
+ }
2769
+ /**
2770
+ * 检测输入文本中被 @ 的角色
2771
+ * @param input - 输入文本
2772
+ * @param enabledRoles - 启用的角色列表
2773
+ * @returns 被提及的角色列表
2774
+ */
2775
+ function useMentionedRoles(input, enabledRoles) {
2776
+ return (0, react.useMemo)(() => {
2777
+ return enabledRoles.filter((r) => new RegExp("(^|\\s)@" + escapeRegExp(r.name) + "(?=\\s|$)").test(input));
2778
+ }, [input, enabledRoles]);
2779
+ }
2780
+ /**
2781
+ * 计算安全的候选索引(确保不越界)
2782
+ * @param candidatesLength - 候选列表长度
2783
+ * @param currentIndex - 当前索引
2784
+ * @returns 安全的索引值
2785
+ */
2786
+ function useSafeMentionIndex(candidatesLength, currentIndex) {
2787
+ return (0, react.useMemo)(() => {
2788
+ return candidatesLength ? Math.min(currentIndex, candidatesLength - 1) : 0;
2789
+ }, [candidatesLength, currentIndex]);
2790
+ }
2791
+ //#endregion
2792
+ //#region src/client/GroupChatPanel.tsx
2793
+ /**
2794
+ * 主面板:三区工作台——左导航栏(搜索 + 群组→会话目录树)、中央会话流
2795
+ * (@成员 点名 + markdown 渲染 + 思考折叠)、右上下文栏(群成员角色卡 +
2796
+ * 工作区目录),右栏可收起,角色编辑走右侧滑出抽屉。
2797
+ *
2798
+ * 面板数据走 /api/group-chat/*(fetch + SSE),Host 半持有全部状态。
2799
+ * @module dsh-group-chat/client/panel
2800
+ */
2801
+ function GroupChatPanel() {
2802
+ const { snap, gid, setGid, sid, setSid, search, setSearch, collapsedGroups, setCollapsedGroups, renameDraft, setRenameDraft, confirmDel, setConfirmDel, confirmClear, setConfirmClear, roleDraft, setRoleDraft, roleFormError, setRoleFormError, models, setModels, modelsError, setModelsError, fileBrowser, setFileBrowser, wsDraft, setWsDraft, partsSel, setPartsSel, rounds, setRounds, input, setInput, err, setErr, topicDraft, setTopicDraft, mention, setMention, mentionIdx, setMentionIdx, asideOpen, setAsideOpen, navOpen, setNavOpen, atBottom, setAtBottom, inputRef, sendingRef, scrollRef, action, mutate } = useGroupChatState();
2803
+ const [toast, setToast] = (0, react.useState)(null);
2804
+ const { onMsgsScroll } = useComposerEffects(inputRef, scrollRef, input, atBottom, snap);
2805
+ const { syncFromDOM, onInputCE, onPasteCE, onDragOverCE, onDropCE } = useComposerInput(inputRef, setInput, setMention, setMentionIdx);
2806
+ let group = snap && gid ? groupById(snap, gid) : null;
2807
+ if (!group && snap && snap.groups.length) group = snap.groups[0];
2808
+ let sess = snap && sid ? sessById(snap, sid) : null;
2809
+ if ((!sess || sess.groupId !== group?.id) && group && snap) sess = group.sessionIds.length ? sessById(snap, group.sessionIds[group.sessionIds.length - 1]) : null;
2810
+ const enabledRoles = group ? group.roleIds.map((id) => roleById(snap, id)).filter((r) => !!r && r.enabled) : [];
2811
+ const participants = partsSel || enabledRoles.map((r) => r.id);
2812
+ const busyNow = !!(sess && snap && snap.run.running && snap.run.sessionId === sess.id);
2813
+ const finishedRun = snap ? snap.run.finished : null;
2814
+ (0, react.useEffect)(() => {
2815
+ if (finishedRun && sess && finishedRun.sessionId === sess.id) mutate({ op: "ackFinish" });
2816
+ }, [finishedRun, sess]);
2817
+ const mentionCandidates = useMentionCandidates(mention, enabledRoles);
2818
+ const { fileCandidates, fileSearchError, fileSearchLoading } = useFileSearch(mention, group?.id);
2819
+ const mentionIdxC = useSafeMentionIndex(mentionCandidates.length || fileCandidates.length, mentionIdx);
2820
+ const mentionedRoles = useMentionedRoles(input, enabledRoles);
2821
+ const { insertChip, insertFileChip } = useMentionChip(inputRef, setMention, setMentionIdx, syncFromDOM);
2822
+ const sendMsg = (0, react.useCallback)(async () => {
2823
+ if (busyNow || !sess || sendingRef.current) return;
2824
+ sendingRef.current = true;
2825
+ try {
2826
+ const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants;
2827
+ if (!parts.length) {
2828
+ setErr(mentionedRoles.length ? "" : "请至少选择一个参与角色(或在消息中 @成员)");
2829
+ if (!mentionedRoles.length) return;
2830
+ }
2831
+ const res = await action({
2832
+ kind: "send",
2833
+ sessionId: sess.id,
2834
+ text: input,
2835
+ participantRoleIds: parts,
2836
+ rounds
2837
+ });
2838
+ if (res && !res.ok && res.error) setErr(res.error);
2839
+ else if (res && res.ok) {
2840
+ const el = inputRef.current;
2841
+ if (el) el.innerHTML = "";
2842
+ setInput("");
2843
+ setMention(null);
2844
+ setErr("");
2845
+ setAtBottom(true);
2846
+ syncFromDOM();
2847
+ }
2848
+ } finally {
2849
+ sendingRef.current = false;
2850
+ }
2851
+ }, [
2852
+ busyNow,
2853
+ sess,
2854
+ sendingRef,
2855
+ mentionedRoles,
2856
+ participants,
2857
+ action,
2858
+ rounds,
2859
+ input,
2860
+ inputRef,
2861
+ setInput,
2862
+ setMention,
2863
+ setErr,
2864
+ setAtBottom,
2865
+ syncFromDOM
2866
+ ]);
2867
+ const { onInputKeyDown } = useInputKeyboard(mention, mentionCandidates, mentionIdxC, setMentionIdx, insertChip, insertFileChip, fileCandidates, setMention, sendMsg);
2868
+ if (!snap) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2869
+ className: "dsgc-root",
2870
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2871
+ className: "dsgc-loading",
2872
+ children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, 16), "加载中…"]
2873
+ })
2874
+ });
2875
+ if (!group) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2876
+ className: "dsgc-root",
2877
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2878
+ className: "dsgc-loading",
2879
+ children: "暂无群组"
2880
+ })
2881
+ });
2882
+ const msgById = {};
2883
+ for (const m of snap.messages) msgById[m.id] = m;
2884
+ const togglePart = (rid) => {
2885
+ const has = participants.includes(rid);
2886
+ setPartsSel(has ? participants.filter((x) => x !== rid) : participants.concat([rid]));
2887
+ };
2888
+ const doClear = async () => {
2889
+ if (!sess) return;
2890
+ if (busyNow) {
2891
+ setToast({
2892
+ text: "对话进行中,需先停止才能清空",
2893
+ seq: Date.now()
2894
+ });
2895
+ return;
2896
+ }
2897
+ const res = await mutate({
2898
+ op: "clearMessages",
2899
+ sessionId: sess.id
2900
+ });
2901
+ if (res && res.ok && res.snapshot && !res.snapshot.error) setConfirmClear(false);
2902
+ else if (res && res.snapshot && res.snapshot.error) {
2903
+ setErr("");
2904
+ setToast({
2905
+ text: res.snapshot.error,
2906
+ seq: Date.now()
2907
+ });
2908
+ }
2909
+ };
2910
+ const fetchModels = async () => {
2911
+ try {
2912
+ const m = await api.action({ kind: "models" });
2913
+ if (m && m.ok && Array.isArray(m.providers)) {
2914
+ setModels(m);
2915
+ setModelsError(null);
2916
+ return;
2917
+ }
2918
+ setModelsError(m && m.error ? String(m.error) : "响应异常");
2919
+ } catch (e) {
2920
+ setModelsError(String(e && e.message || e));
2921
+ }
2922
+ };
2923
+ const openRoleEditor = async (role) => {
2924
+ setErr("");
2925
+ setRoleFormError("");
2926
+ if (!models || modelsError) await fetchModels();
2927
+ setRoleDraft(role ? draftFromRole(role) : blankDraft());
2928
+ };
2929
+ const saveRole = async () => {
2930
+ if (!roleDraft) return;
2931
+ if (!roleDraft.name.trim()) {
2932
+ setRoleFormError("角色名称不能为空");
2933
+ return;
2934
+ }
2935
+ if (!roleDraft.provider || !roleDraft.model) {
2936
+ setRoleFormError("请选择角色绑定的模型");
2937
+ return;
2938
+ }
2939
+ const res = await mutate({
2940
+ op: "upsertRole",
2941
+ groupId: group.id,
2942
+ role: roleDraft
2943
+ });
2944
+ if (res && res.ok && res.snapshot && !res.snapshot.error) {
2945
+ setRoleDraft(null);
2946
+ setRoleFormError("");
2947
+ }
2948
+ };
2949
+ const stopRun = async () => {
2950
+ await action({
2951
+ kind: "stop",
2952
+ sessionId: sess.id
2953
+ });
2954
+ };
2955
+ const openBrowser = async (path) => {
2956
+ setFileBrowser({
2957
+ open: true,
2958
+ loading: true,
2959
+ list: null,
2960
+ error: ""
2961
+ });
2962
+ try {
2963
+ const res = await api.action({
2964
+ kind: "browse",
2965
+ path: path || ""
2966
+ });
2967
+ if (res && res.ok) setFileBrowser({
2968
+ open: true,
2969
+ loading: false,
2970
+ list: res,
2971
+ error: ""
2972
+ });
2973
+ else setFileBrowser({
2974
+ open: true,
2975
+ loading: false,
2976
+ list: null,
2977
+ error: res && res.error || "浏览失败"
2978
+ });
2979
+ } catch (e) {
2980
+ setFileBrowser({
2981
+ open: true,
2982
+ loading: false,
2983
+ list: null,
2984
+ error: String(e && e.message || e)
2985
+ });
2986
+ }
2987
+ };
2988
+ const selectCurrentDir = () => {
2989
+ if (!fileBrowser || !fileBrowser.list || !group) return;
2990
+ const path = fileBrowser.list.path;
2991
+ mutate({
2992
+ op: "setWorkspaceDir",
2993
+ groupId: group.id,
2994
+ path
2995
+ });
2996
+ setWsDraft(null);
2997
+ setFileBrowser(null);
2998
+ };
2999
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3000
+ className: "dsgc-root",
3001
+ children: [
3002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NavPanel, {
3003
+ snap,
3004
+ search,
3005
+ setSearch,
3006
+ collapsedGroups,
3007
+ setCollapsedGroups,
3008
+ gid: group.id,
3009
+ sid: sess ? sess.id : sid,
3010
+ setGid,
3011
+ setSid,
3012
+ setPartsSel,
3013
+ renameDraft,
3014
+ setRenameDraft,
3015
+ confirmDel,
3016
+ setConfirmDel,
3017
+ mutate,
3018
+ navOpen
3019
+ }),
3020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatPanel, {
3021
+ snap,
3022
+ sess,
3023
+ group,
3024
+ enabledRoles,
3025
+ participants,
3026
+ mentionedRoles,
3027
+ busyNow,
3028
+ input,
3029
+ mention,
3030
+ mentionCandidates,
3031
+ mentionIdxC,
3032
+ fileCandidates,
3033
+ fileSearchError,
3034
+ fileSearchLoading,
3035
+ rounds,
3036
+ err,
3037
+ atBottom,
3038
+ topicDraft,
3039
+ msgById,
3040
+ navOpen,
3041
+ asideOpen,
3042
+ inputRef,
3043
+ scrollRef,
3044
+ setTopicDraft,
3045
+ setConfirmClear,
3046
+ setMentionIdx,
3047
+ setRounds,
3048
+ setNavOpen,
3049
+ setAsideOpen,
3050
+ togglePart,
3051
+ onMsgsScroll: () => {
3052
+ const isBottom = onMsgsScroll();
3053
+ if (isBottom !== void 0) setAtBottom(isBottom);
3054
+ },
3055
+ onInputCE,
3056
+ onInputKeyDown,
3057
+ onPasteCE,
3058
+ onDropCE,
3059
+ onDragOverCE,
3060
+ insertChip,
3061
+ insertFileChip,
3062
+ sendMsg,
3063
+ stopRun,
3064
+ action,
3065
+ mutate,
3066
+ setMention
3067
+ }),
3068
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AsidePanel, {
3069
+ snap,
3070
+ group,
3071
+ asideOpen,
3072
+ wsDraft,
3073
+ setWsDraft,
3074
+ fileBrowser,
3075
+ setFileBrowser,
3076
+ mutate,
3077
+ openRoleEditor,
3078
+ openBrowser,
3079
+ selectCurrentDir
3080
+ }),
3081
+ sess && confirmClear ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3082
+ open: true,
3083
+ onClose: () => {
3084
+ setConfirmClear(false);
3085
+ },
3086
+ title: "清空本会话的消息记录?",
3087
+ closeLabel: "关闭",
3088
+ description: "会话「" + sess.name + "」的全部 " + sess.messageIds.length + " 条消息将被永久删除",
3089
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3090
+ variant: "outline",
3091
+ size: "sm",
3092
+ onClick: () => {
3093
+ setConfirmClear(false);
3094
+ },
3095
+ children: "取消"
3096
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3097
+ variant: "outline",
3098
+ size: "sm",
3099
+ className: "dsgc-stopbtn",
3100
+ onClick: () => {
3101
+ doClear();
3102
+ },
3103
+ children: "清空"
3104
+ })] }),
3105
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
3106
+ className: "dsgc-clearnotes",
3107
+ children: [
3108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "删除内容:本会话的用户消息与角色发言(含思考、工具调用记录),确认后立即落盘" }),
3109
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "不可恢复:此操作没有回收站,也没有撤销" }),
3110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "不受影响:会话本身与主题、群成员角色、工作区目录、权限档位" })
3111
+ ]
3112
+ })
3113
+ }) : null,
3114
+ toast ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
3115
+ text: toast.text,
3116
+ icon: Icon(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, 16),
3117
+ anchor: inputRef.current,
3118
+ onDone: () => {
3119
+ setToast(null);
3120
+ }
3121
+ }, toast.seq) : null,
3122
+ roleDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDrawer, {
3123
+ draft: roleDraft,
3124
+ set: setRoleDraft,
3125
+ models,
3126
+ modelsError,
3127
+ onRetryModels: () => {
3128
+ fetchModels();
3129
+ },
3130
+ onSave: () => {
3131
+ saveRole();
3132
+ },
3133
+ onCancel: () => {
3134
+ setRoleDraft(null);
3135
+ setRoleFormError("");
3136
+ },
3137
+ formError: roleFormError
3138
+ }) : null
3139
+ ]
3140
+ });
3141
+ }
3142
+ //#endregion
3143
+ //#region src/client/components/GroupChatSettingsSection.tsx
3144
+ /**
3145
+ * 设置页:启停开关(settings.section 插槽)。写 host 的 group-chat 设置
3146
+ * 命名空间(settings.yaml 持久化)。
3147
+ * @module dsh-group-chat/client/settings
3148
+ */
3149
+ function GroupChatSettingsSection(props) {
3150
+ const scope = props.settingsScope;
3151
+ const [snap, setSnap] = (0, react.useState)(null);
3152
+ const [pending, setPending] = (0, react.useState)(false);
3153
+ (0, react.useEffect)(() => {
3154
+ let live = true;
3155
+ const read = () => {
3156
+ if (!live) return;
3157
+ try {
3158
+ setSnap(scope.getSnapshot());
3159
+ } catch {}
3160
+ };
3161
+ read();
3162
+ const off = scope.subscribe(read);
3163
+ return () => {
3164
+ live = false;
3165
+ off();
3166
+ };
3167
+ }, []);
3168
+ const ready = snap !== null && snap.status === "ready";
3169
+ const writable = ready ? snap.writable !== false : false;
3170
+ const enabled = ready ? (snap.value && snap.value.enabled) !== false : true;
3171
+ const toggle = async () => {
3172
+ if (!writable || pending) return;
3173
+ setPending(true);
3174
+ try {
3175
+ await scope.set("enabled", !enabled);
3176
+ } catch (e) {
3177
+ console.error("[dsh-group-chat] settings write failed", e);
3178
+ } finally {
3179
+ setPending(false);
3180
+ }
3181
+ };
3182
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3183
+ className: "dgcs-page",
3184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3185
+ className: "dgcs-head",
3186
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3187
+ className: "dgcs-title",
3188
+ children: "模型群聊"
3189
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3190
+ className: "dgcs-desc",
3191
+ children: "多模型角色群组对话面板:每个角色绑定不同的 provider/model;群组内多会话目录树管理,群内共享对话记录与工作区目录;消息以 markdown 渲染、支持思考折叠;输入框支持 @成员 / @文件。数据持久化于 ~/.dsh/storages/group-chat/,重启 dsh web 后恢复。"
3192
+ })]
3193
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3194
+ className: "dgcs-card",
3195
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3196
+ className: "dgcs-cardtext",
3197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3198
+ className: "dgcs-cardtitle",
3199
+ children: enabled ? "已启用" : "已停用"
3200
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3201
+ className: "dgcs-cardhint",
3202
+ children: writable ? enabled ? "关闭后侧边栏将不再显示「群聊」入口,进行中的对话会被中止。" : "开启后侧边栏显示「群聊」入口。" : "当前设置不可写(可能被配置文件覆盖)。"
3203
+ })]
3204
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
3205
+ checked: enabled,
3206
+ onChange: () => {
3207
+ toggle();
3208
+ },
3209
+ disabled: !writable || pending,
3210
+ label: "模型群聊启停",
3211
+ title: pending ? "正在写入…" : enabled ? "点击停用" : "点击启用"
3212
+ })]
3213
+ })]
3214
+ });
3215
+ }
3216
+ //#endregion
3217
+ //#region src/client/components/Glyph.tsx
3218
+ function Glyph(props) {
3219
+ const size = props && props.size || 18;
3220
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3221
+ className: "dsgc-entryOverlay newSession",
3222
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3223
+ className: "dsgc-entryIcon",
3224
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
3225
+ width: size,
3226
+ height: size,
3227
+ viewBox: "0 0 24 24",
3228
+ fill: "none",
3229
+ stroke: "currentColor",
3230
+ strokeWidth: 1.8,
3231
+ strokeLinecap: "round",
3232
+ strokeLinejoin: "round",
3233
+ "aria-hidden": "true",
3234
+ children: [
3235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
3236
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
3237
+ cx: 9,
3238
+ cy: 7,
3239
+ r: 4
3240
+ }),
3241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M23 21v-2a4 4 0 0 0-3-3.87" }),
3242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
3243
+ ]
3244
+ })
3245
+ })
3246
+ });
3247
+ }
3248
+ //#endregion
3249
+ //#region src/client/index.ts
3250
+ const SETTINGS_NAMESPACE = "group-chat";
3251
+ /** Client plugin id. */
3252
+ const name = "group-chat-client";
3253
+ /** Required services. */
3254
+ const inject = ["slots", "settingsScope"];
3255
+ /**
3256
+ * 客户端插件体:设置页常驻注册 + 启用门控挂载侧边栏入口与主面板。
3257
+ * @param ctx - client root context.
3258
+ */
3259
+ function apply(ctx) {
3260
+ injectStyles();
3261
+ const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
3262
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
3263
+ name: "settings.section",
3264
+ id: "dsh-group-chat",
3265
+ order: 60,
3266
+ label: () => "模型群聊",
3267
+ inject: () => ({ settingsScope: scope })
3268
+ }, (props) => (0, react.createElement)(GroupChatSettingsSection, props)));
3269
+ let uiDisposer;
3270
+ const mountUi = () => {
3271
+ if (uiDisposer !== void 0) return;
3272
+ const disposers = [];
3273
+ try {
3274
+ disposers.push(ctx.slots.inject("main", () => ctx.slots.register({
3275
+ name: "main",
3276
+ key: "group-chat"
3277
+ }, () => (0, react.createElement)(GroupChatPanel, null))));
3278
+ disposers.push(ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
3279
+ name: "sidebar.panellist",
3280
+ id: "group-chat",
3281
+ order: 50,
3282
+ label: "群聊"
3283
+ }, (props) => (0, react.createElement)(Glyph, props))));
3284
+ } catch (e) {
3285
+ console.error("[dsh-group-chat] mount failed:", e);
3286
+ }
3287
+ uiDisposer = () => {
3288
+ uiDisposer = void 0;
3289
+ for (const dispose of disposers.splice(0)) dispose();
3290
+ };
3291
+ };
3292
+ const syncEnabled = () => {
3293
+ const snapshot = scope.getSnapshot();
3294
+ if (snapshot.status === "ready" ? (snapshot.value && snapshot.value.enabled) !== false : snapshot.status === "unavailable") mountUi();
3295
+ else if (uiDisposer !== void 0) uiDisposer();
3296
+ };
3297
+ scope.subscribe(syncEnabled);
3298
+ syncEnabled();
3299
+ }
3300
+ //#endregion
3301
+ exports.apply = apply;
3302
+ exports.inject = inject;
3303
+ exports.name = name;
3304
+ return module.exports;
3305
+ }
3306
+ });
3307
+
3308
+ //# sourceMappingURL=client.js.map