@fromsko/chat-ui 0.0.0-stage → 0.1.6

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 (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -2
  3. package/lib/adapters/event-bus.d.ts +80 -0
  4. package/lib/adapters/event-bus.js +73 -0
  5. package/lib/adapters/index.d.ts +3 -0
  6. package/lib/adapters/index.js +3 -0
  7. package/lib/adapters/labels.d.ts +6 -0
  8. package/lib/adapters/labels.js +17 -0
  9. package/lib/adapters/stream-bridge.d.ts +9 -0
  10. package/lib/adapters/stream-bridge.js +7 -0
  11. package/lib/blocks/cards/AskCard.d.ts +8 -0
  12. package/lib/blocks/cards/AskCard.js +97 -0
  13. package/lib/blocks/cards/CompactionCard.d.ts +5 -0
  14. package/lib/blocks/cards/CompactionCard.js +5 -0
  15. package/lib/blocks/cards/ContextUsage.d.ts +14 -0
  16. package/lib/blocks/cards/ContextUsage.js +136 -0
  17. package/lib/blocks/cards/DisplayCard.d.ts +7 -0
  18. package/lib/blocks/cards/DisplayCard.js +27 -0
  19. package/lib/blocks/cards/MemoryPill.d.ts +5 -0
  20. package/lib/blocks/cards/MemoryPill.js +5 -0
  21. package/lib/blocks/cards/OutputCard.d.ts +5 -0
  22. package/lib/blocks/cards/OutputCard.js +13 -0
  23. package/lib/blocks/cards/PermissionCard.d.ts +21 -0
  24. package/lib/blocks/cards/PermissionCard.js +26 -0
  25. package/lib/blocks/cards/SpringCheck.d.ts +9 -0
  26. package/lib/blocks/cards/SpringCheck.js +17 -0
  27. package/lib/blocks/cards/TodoCard.d.ts +19 -0
  28. package/lib/blocks/cards/TodoCard.js +28 -0
  29. package/lib/blocks/cards/index.d.ts +10 -0
  30. package/lib/blocks/cards/index.js +10 -0
  31. package/lib/blocks/composer/Composer.d.ts +66 -0
  32. package/lib/blocks/composer/Composer.js +73 -0
  33. package/lib/blocks/composer/ComposerAttachments.d.ts +17 -0
  34. package/lib/blocks/composer/ComposerAttachments.js +10 -0
  35. package/lib/blocks/composer/ComposerGrip.d.ts +1 -0
  36. package/lib/blocks/composer/ComposerGrip.js +39 -0
  37. package/lib/blocks/composer/ComposerInput.d.ts +18 -0
  38. package/lib/blocks/composer/ComposerInput.js +60 -0
  39. package/lib/blocks/composer/ComposerMenus.d.ts +10 -0
  40. package/lib/blocks/composer/ComposerMenus.js +78 -0
  41. package/lib/blocks/composer/ComposerToolbar.d.ts +27 -0
  42. package/lib/blocks/composer/ComposerToolbar.js +16 -0
  43. package/lib/blocks/composer/MentionPicker.d.ts +13 -0
  44. package/lib/blocks/composer/MentionPicker.js +71 -0
  45. package/lib/blocks/composer/PromptPop.d.ts +7 -0
  46. package/lib/blocks/composer/PromptPop.js +40 -0
  47. package/lib/blocks/composer/QueuedPrompts.d.ts +14 -0
  48. package/lib/blocks/composer/QueuedPrompts.js +21 -0
  49. package/lib/blocks/composer/SlingButton.d.ts +10 -0
  50. package/lib/blocks/composer/SlingButton.js +112 -0
  51. package/lib/blocks/composer/index.d.ts +7 -0
  52. package/lib/blocks/composer/index.js +1 -0
  53. package/lib/blocks/conversation/ChatEmptyScene.d.ts +4 -0
  54. package/lib/blocks/conversation/ChatEmptyScene.js +21 -0
  55. package/lib/blocks/conversation/ChatHeader.d.ts +13 -0
  56. package/lib/blocks/conversation/ChatHeader.js +20 -0
  57. package/lib/blocks/conversation/ChatPane.d.ts +90 -0
  58. package/lib/blocks/conversation/ChatPane.js +60 -0
  59. package/lib/blocks/conversation/Conversation.d.ts +11 -0
  60. package/lib/blocks/conversation/Conversation.js +41 -0
  61. package/lib/blocks/conversation/LineSidebar.d.ts +14 -0
  62. package/lib/blocks/conversation/LineSidebar.js +42 -0
  63. package/lib/blocks/conversation/index.d.ts +5 -0
  64. package/lib/blocks/conversation/index.js +5 -0
  65. package/lib/blocks/index.d.ts +9 -0
  66. package/lib/blocks/index.js +9 -0
  67. package/lib/blocks/message/DayDivider.d.ts +10 -0
  68. package/lib/blocks/message/DayDivider.js +29 -0
  69. package/lib/blocks/message/DiffView.d.ts +5 -0
  70. package/lib/blocks/message/DiffView.js +18 -0
  71. package/lib/blocks/message/MermaidDiagram.d.ts +4 -0
  72. package/lib/blocks/message/MermaidDiagram.js +43 -0
  73. package/lib/blocks/message/Message.d.ts +17 -0
  74. package/lib/blocks/message/Message.js +45 -0
  75. package/lib/blocks/message/MessageActionBar.d.ts +18 -0
  76. package/lib/blocks/message/MessageActionBar.js +15 -0
  77. package/lib/blocks/message/MessageArtifacts.d.ts +13 -0
  78. package/lib/blocks/message/MessageArtifacts.js +9 -0
  79. package/lib/blocks/message/MessageAttachments.d.ts +5 -0
  80. package/lib/blocks/message/MessageAttachments.js +28 -0
  81. package/lib/blocks/message/MessageBubble.d.ts +11 -0
  82. package/lib/blocks/message/MessageBubble.js +31 -0
  83. package/lib/blocks/message/MessageRow.d.ts +20 -0
  84. package/lib/blocks/message/MessageRow.js +49 -0
  85. package/lib/blocks/message/MessageStream.d.ts +23 -0
  86. package/lib/blocks/message/MessageStream.js +80 -0
  87. package/lib/blocks/message/MessageTrace.d.ts +16 -0
  88. package/lib/blocks/message/MessageTrace.js +39 -0
  89. package/lib/blocks/message/PartView.d.ts +10 -0
  90. package/lib/blocks/message/PartView.js +29 -0
  91. package/lib/blocks/message/code-text.d.ts +15 -0
  92. package/lib/blocks/message/code-text.js +17 -0
  93. package/lib/blocks/message/index.d.ts +12 -0
  94. package/lib/blocks/message/index.js +12 -0
  95. package/lib/blocks/reasoning/Reasoning.d.ts +7 -0
  96. package/lib/blocks/reasoning/Reasoning.js +26 -0
  97. package/lib/blocks/reasoning/ReasoningBlock.d.ts +6 -0
  98. package/lib/blocks/reasoning/ReasoningBlock.js +5 -0
  99. package/lib/blocks/reasoning/index.d.ts +2 -0
  100. package/lib/blocks/reasoning/index.js +2 -0
  101. package/lib/blocks/selection/SelectionActions.d.ts +12 -0
  102. package/lib/blocks/selection/SelectionActions.js +29 -0
  103. package/lib/blocks/selection/SelectionBar.d.ts +10 -0
  104. package/lib/blocks/selection/SelectionBar.js +6 -0
  105. package/lib/blocks/selection/index.d.ts +2 -0
  106. package/lib/blocks/selection/index.js +2 -0
  107. package/lib/blocks/settings/DisplaySettingsPanel.d.ts +5 -0
  108. package/lib/blocks/settings/DisplaySettingsPanel.js +33 -0
  109. package/lib/blocks/settings/SquishSwitch.d.ts +8 -0
  110. package/lib/blocks/settings/SquishSwitch.js +21 -0
  111. package/lib/blocks/settings/index.d.ts +2 -0
  112. package/lib/blocks/settings/index.js +2 -0
  113. package/lib/blocks/status/LatticeLoader.d.ts +12 -0
  114. package/lib/blocks/status/LatticeLoader.js +22 -0
  115. package/lib/blocks/status/TypingIndicator.d.ts +5 -0
  116. package/lib/blocks/status/TypingIndicator.js +5 -0
  117. package/lib/blocks/status/index.d.ts +2 -0
  118. package/lib/blocks/status/index.js +2 -0
  119. package/lib/blocks/tool/SandboxCard.d.ts +5 -0
  120. package/lib/blocks/tool/SandboxCard.js +10 -0
  121. package/lib/blocks/tool/ToolCard.d.ts +10 -0
  122. package/lib/blocks/tool/ToolCard.js +32 -0
  123. package/lib/blocks/tool/ToolGroup.d.ts +6 -0
  124. package/lib/blocks/tool/ToolGroup.js +23 -0
  125. package/lib/blocks/tool/exec-envelope.d.ts +7 -0
  126. package/lib/blocks/tool/exec-envelope.js +43 -0
  127. package/lib/blocks/tool/index.d.ts +4 -0
  128. package/lib/blocks/tool/index.js +4 -0
  129. package/lib/domain/ask.d.ts +14 -0
  130. package/lib/domain/ask.js +50 -0
  131. package/lib/domain/attachments.d.ts +16 -0
  132. package/lib/domain/attachments.js +126 -0
  133. package/lib/domain/attribution.d.ts +4 -0
  134. package/lib/domain/attribution.js +11 -0
  135. package/lib/domain/composer-palette.d.ts +15 -0
  136. package/lib/domain/composer-palette.js +131 -0
  137. package/lib/domain/index.d.ts +12 -0
  138. package/lib/domain/index.js +12 -0
  139. package/lib/domain/mention.d.ts +17 -0
  140. package/lib/domain/mention.js +95 -0
  141. package/lib/domain/message-blocks.d.ts +29 -0
  142. package/lib/domain/message-blocks.js +63 -0
  143. package/lib/domain/message-menu.d.ts +4 -0
  144. package/lib/domain/message-menu.js +46 -0
  145. package/lib/domain/queue.d.ts +23 -0
  146. package/lib/domain/queue.js +52 -0
  147. package/lib/domain/reducer.d.ts +137 -0
  148. package/lib/domain/reducer.js +442 -0
  149. package/lib/domain/selection-actions.d.ts +13 -0
  150. package/lib/domain/selection-actions.js +56 -0
  151. package/lib/domain/selection-gates.d.ts +6 -0
  152. package/lib/domain/selection-gates.js +36 -0
  153. package/lib/domain/slash-commands.d.ts +16 -0
  154. package/lib/domain/slash-commands.js +142 -0
  155. package/lib/hooks/index.d.ts +8 -0
  156. package/lib/hooks/index.js +8 -0
  157. package/lib/hooks/useComposerPalette.d.ts +35 -0
  158. package/lib/hooks/useComposerPalette.js +120 -0
  159. package/lib/hooks/useComposerSubmit.d.ts +31 -0
  160. package/lib/hooks/useComposerSubmit.js +135 -0
  161. package/lib/hooks/useInputHistory.d.ts +14 -0
  162. package/lib/hooks/useInputHistory.js +52 -0
  163. package/lib/hooks/usePendingAsk.d.ts +11 -0
  164. package/lib/hooks/usePendingAsk.js +33 -0
  165. package/lib/hooks/usePendingPermissions.d.ts +10 -0
  166. package/lib/hooks/usePendingPermissions.js +26 -0
  167. package/lib/hooks/useQueuedPrompts.d.ts +12 -0
  168. package/lib/hooks/useQueuedPrompts.js +91 -0
  169. package/lib/hooks/useSelectionPop.d.ts +13 -0
  170. package/lib/hooks/useSelectionPop.js +130 -0
  171. package/lib/hooks/useStreamBridge.d.ts +1 -0
  172. package/lib/hooks/useStreamBridge.js +23 -0
  173. package/lib/index.d.ts +10 -0
  174. package/lib/index.js +10 -0
  175. package/lib/primitives/Badge.d.ts +7 -0
  176. package/lib/primitives/Badge.js +13 -0
  177. package/lib/primitives/Button.d.ts +9 -0
  178. package/lib/primitives/Button.js +21 -0
  179. package/lib/primitives/Collapsible.d.ts +12 -0
  180. package/lib/primitives/Collapsible.js +14 -0
  181. package/lib/primitives/Dialog.d.ts +10 -0
  182. package/lib/primitives/Dialog.js +19 -0
  183. package/lib/primitives/DropdownMenu.d.ts +28 -0
  184. package/lib/primitives/DropdownMenu.js +37 -0
  185. package/lib/primitives/Empty.d.ts +8 -0
  186. package/lib/primitives/Empty.js +6 -0
  187. package/lib/primitives/Input.d.ts +3 -0
  188. package/lib/primitives/Input.js +6 -0
  189. package/lib/primitives/Kbd.d.ts +2 -0
  190. package/lib/primitives/Kbd.js +5 -0
  191. package/lib/primitives/Popover.d.ts +15 -0
  192. package/lib/primitives/Popover.js +32 -0
  193. package/lib/primitives/ScrollArea.d.ts +5 -0
  194. package/lib/primitives/ScrollArea.js +5 -0
  195. package/lib/primitives/Separator.d.ts +5 -0
  196. package/lib/primitives/Separator.js +5 -0
  197. package/lib/primitives/Spinner.d.ts +5 -0
  198. package/lib/primitives/Spinner.js +5 -0
  199. package/lib/primitives/Textarea.d.ts +3 -0
  200. package/lib/primitives/Textarea.js +6 -0
  201. package/lib/primitives/Tooltip.d.ts +8 -0
  202. package/lib/primitives/Tooltip.js +7 -0
  203. package/lib/primitives/index.d.ts +14 -0
  204. package/lib/primitives/index.js +14 -0
  205. package/lib/reactbits/Motion.d.ts +24 -0
  206. package/lib/reactbits/Motion.js +52 -0
  207. package/lib/reactbits/index.d.ts +1 -0
  208. package/lib/reactbits/index.js +1 -0
  209. package/lib/stores/chat-store.d.ts +33 -0
  210. package/lib/stores/chat-store.js +93 -0
  211. package/lib/stores/display-store.d.ts +15 -0
  212. package/lib/stores/display-store.js +116 -0
  213. package/lib/stores/index.d.ts +3 -0
  214. package/lib/stores/index.js +3 -0
  215. package/lib/stores/theme-store.d.ts +8 -0
  216. package/lib/stores/theme-store.js +29 -0
  217. package/lib/tokens/index.d.ts +21 -0
  218. package/lib/tokens/index.js +66 -0
  219. package/lib/types/agent.d.ts +62 -0
  220. package/lib/types/agent.js +1 -0
  221. package/lib/types/callbacks.d.ts +55 -0
  222. package/lib/types/callbacks.js +1 -0
  223. package/lib/types/composer.d.ts +116 -0
  224. package/lib/types/composer.js +1 -0
  225. package/lib/types/conversation.d.ts +54 -0
  226. package/lib/types/conversation.js +1 -0
  227. package/lib/types/display.d.ts +25 -0
  228. package/lib/types/display.js +40 -0
  229. package/lib/types/index.d.ts +8 -0
  230. package/lib/types/index.js +8 -0
  231. package/lib/types/message.d.ts +100 -0
  232. package/lib/types/message.js +1 -0
  233. package/lib/types/selection.d.ts +60 -0
  234. package/lib/types/selection.js +1 -0
  235. package/lib/types/theme.d.ts +100 -0
  236. package/lib/types/theme.js +1 -0
  237. package/lib/utils/clipboard.d.ts +4 -0
  238. package/lib/utils/clipboard.js +41 -0
  239. package/lib/utils/cn.d.ts +2 -0
  240. package/lib/utils/cn.js +14 -0
  241. package/lib/utils/index.d.ts +3 -0
  242. package/lib/utils/index.js +3 -0
  243. package/lib/utils/storage.d.ts +11 -0
  244. package/lib/utils/storage.js +48 -0
  245. package/package.json +93 -4
  246. package/src/styles/blocks.css +2439 -0
  247. package/src/styles/index.css +3 -0
  248. package/src/tokens/theme.css +464 -0
@@ -0,0 +1,442 @@
1
+ const EMPTY = [];
2
+ function cloneMessages(messages, id, patch) {
3
+ const list = messages[id] ?? EMPTY;
4
+ return { ...messages, [id]: patch(list) };
5
+ }
6
+ function patchMessageAt(state, id, index, patch) {
7
+ return {
8
+ ...state,
9
+ messages: cloneMessages(state.messages, id, (list) => list.map((m, i) => (i === index ? patch(m) : m))),
10
+ };
11
+ }
12
+ function settleParts(parts) {
13
+ if (!parts)
14
+ return parts;
15
+ return parts.map((p) => p.kind === "reasoning" && p.streaming ? { ...p, streaming: false } : p);
16
+ }
17
+ function patchStreaming(state, requestId, patch) {
18
+ for (const [cid, list] of Object.entries(state.messages)) {
19
+ const idx = list.findIndex((m) => m.state === "streaming" && m.requestId === requestId);
20
+ if (idx >= 0)
21
+ return patchMessageAt(state, cid, idx, patch);
22
+ }
23
+ const active = state.activeId ? state.messages[state.activeId] : undefined;
24
+ if (active) {
25
+ for (let i = active.length - 1; i >= 0; i--) {
26
+ const m = active[i];
27
+ if (m.state === "streaming" && m.requestId === undefined) {
28
+ return patchMessageAt(state, state.activeId, i, patch);
29
+ }
30
+ }
31
+ }
32
+ return state;
33
+ }
34
+ function patchByRequest(state, requestId, patch) {
35
+ for (const [cid, list] of Object.entries(state.messages)) {
36
+ const idx = list.findIndex((m) => m.requestId === requestId);
37
+ if (idx >= 0)
38
+ return patchMessageAt(state, cid, idx, patch);
39
+ }
40
+ return state;
41
+ }
42
+ function resolveTargetConversation(state, requestId) {
43
+ if (!requestId)
44
+ return state.activeId ?? undefined;
45
+ for (const [cid, list] of Object.entries(state.messages)) {
46
+ if (list.some((m) => m.requestId === requestId))
47
+ return cid;
48
+ }
49
+ return undefined;
50
+ }
51
+ export function conversationReducer(state, action) {
52
+ switch (action.type) {
53
+ case "select":
54
+ return {
55
+ ...state,
56
+ activeId: action.id,
57
+ conversations: state.conversations.map((c) => c.id === action.id ? { ...c, unread: 0 } : c),
58
+ };
59
+ case "query":
60
+ return { ...state, query: action.value };
61
+ case "draft":
62
+ return {
63
+ ...state,
64
+ drafts: { ...state.drafts, [action.id]: action.value },
65
+ };
66
+ case "append":
67
+ return {
68
+ ...state,
69
+ messages: cloneMessages(state.messages, action.id, (list) => [
70
+ ...list,
71
+ action.message,
72
+ ]),
73
+ };
74
+ case "hydrateMessages": {
75
+ const existing = state.messages[action.id];
76
+ if (existing && existing.length > 0)
77
+ return state;
78
+ return {
79
+ ...state,
80
+ messages: { ...state.messages, [action.id]: action.messages },
81
+ };
82
+ }
83
+ case "deleteMessage":
84
+ return {
85
+ ...state,
86
+ messages: cloneMessages(state.messages, action.id, (list) => list.filter((m) => m.id !== action.messageId)),
87
+ };
88
+ case "insertMessageAt":
89
+ return {
90
+ ...state,
91
+ messages: cloneMessages(state.messages, action.id, (list) => {
92
+ const at = Math.max(0, Math.min(action.index, list.length));
93
+ const next = [...list];
94
+ next.splice(at, 0, action.message);
95
+ return next;
96
+ }),
97
+ };
98
+ case "bindRequest":
99
+ return patchStreaming(state, action.requestId, (m) => ({
100
+ ...m,
101
+ requestId: action.requestId,
102
+ }));
103
+ case "streamDelta":
104
+ return patchStreaming(state, action.requestId, (m) => ({
105
+ ...m,
106
+ text: m.text + action.text,
107
+ parts: settleParts(m.parts),
108
+ }));
109
+ case "streamEnd": {
110
+ const owner = resolveTargetConversation(state, action.requestId);
111
+ if (!owner)
112
+ return state;
113
+ let next = patchStreaming(state, action.requestId, (m) => ({
114
+ ...m,
115
+ state: action.outcome,
116
+ parts: settleParts(m.parts),
117
+ text: action.outcome === "error"
118
+ ? `${m.text}\n【失败】${action.error ?? "未知错误"}`
119
+ : m.text,
120
+ }));
121
+ if (owner !== next.activeId && action.outcome === "done") {
122
+ next = {
123
+ ...next,
124
+ conversations: next.conversations.map((c) => c.id === owner ? { ...c, unread: c.unread + 1 } : c),
125
+ };
126
+ }
127
+ return next;
128
+ }
129
+ case "streamToolCall":
130
+ return patchStreaming(state, action.requestId, (m) => ({
131
+ ...m,
132
+ parts: [
133
+ ...(m.parts ?? []),
134
+ {
135
+ kind: "tool",
136
+ toolCallId: action.toolCallId,
137
+ tool: action.name,
138
+ state: "running",
139
+ args: action.args,
140
+ },
141
+ ],
142
+ }));
143
+ case "streamToolResult":
144
+ return patchByRequest(state, action.requestId, (m) => ({
145
+ ...m,
146
+ parts: (m.parts ?? []).map((p) => p.kind === "tool" && p.toolCallId === action.toolCallId
147
+ ? {
148
+ ...p,
149
+ state: action.isError ? "error" : "finished",
150
+ result: action.result,
151
+ }
152
+ : p),
153
+ }));
154
+ case "streamReasoning":
155
+ return patchStreaming(state, action.requestId, (m) => {
156
+ const parts = m.parts ?? [];
157
+ const idx = parts.findIndex((p) => p.kind === "reasoning");
158
+ if (idx >= 0) {
159
+ const next = [...parts];
160
+ const r = next[idx];
161
+ if (r.kind === "reasoning") {
162
+ next[idx] = {
163
+ ...r,
164
+ text: r.text + action.text,
165
+ streaming: true,
166
+ };
167
+ }
168
+ return { ...m, parts: next };
169
+ }
170
+ return {
171
+ ...m,
172
+ parts: [
173
+ { kind: "reasoning", text: action.text, streaming: true },
174
+ ...parts,
175
+ ],
176
+ };
177
+ });
178
+ case "streamContextUsage":
179
+ return patchStreaming(state, action.requestId, (m) => ({
180
+ ...m,
181
+ contextUsage: {
182
+ input: action.inputTokens,
183
+ output: action.outputTokens,
184
+ window: action.window,
185
+ },
186
+ }));
187
+ case "streamMemory": {
188
+ const withPart = patchByRequest(state, action.requestId, (m) => ({
189
+ ...m,
190
+ parts: [
191
+ ...(m.parts ?? []),
192
+ {
193
+ kind: "memory",
194
+ count: action.count,
195
+ excerpt: action.excerpt,
196
+ requestId: action.requestId,
197
+ },
198
+ ],
199
+ }));
200
+ if (withPart !== state)
201
+ return withPart;
202
+ const target = resolveTargetConversation(state, action.requestId) ??
203
+ state.activeId ??
204
+ undefined;
205
+ if (!target)
206
+ return state;
207
+ return {
208
+ ...state,
209
+ messages: cloneMessages(state.messages, target, (list) => [
210
+ ...list,
211
+ {
212
+ id: `${target}-m-${action.requestId}`,
213
+ role: "system",
214
+ text: "",
215
+ timeLabel: "",
216
+ parts: [
217
+ {
218
+ kind: "memory",
219
+ count: action.count,
220
+ excerpt: action.excerpt,
221
+ requestId: action.requestId,
222
+ },
223
+ ],
224
+ },
225
+ ]),
226
+ };
227
+ }
228
+ case "streamCompaction": {
229
+ const target = resolveTargetConversation(state, action.requestId) ??
230
+ state.activeId ??
231
+ undefined;
232
+ if (!target)
233
+ return state;
234
+ return {
235
+ ...state,
236
+ messages: cloneMessages(state.messages, target, (list) => [
237
+ ...list,
238
+ {
239
+ id: `${target}-c-${action.requestId ?? "legacy"}`,
240
+ role: "system",
241
+ text: "",
242
+ timeLabel: "",
243
+ parts: [
244
+ {
245
+ kind: "compact",
246
+ beforeTokens: action.beforeTokens,
247
+ afterTokens: action.afterTokens,
248
+ reason: action.reason,
249
+ requestId: action.requestId ?? undefined,
250
+ },
251
+ ],
252
+ },
253
+ ]),
254
+ };
255
+ }
256
+ case "streamDisplay": {
257
+ const withPart = patchByRequest(state, action.requestId, (m) => ({
258
+ ...m,
259
+ parts: [
260
+ ...(m.parts ?? []),
261
+ {
262
+ kind: "display",
263
+ view: action.view,
264
+ title: action.title,
265
+ payload: action.payload,
266
+ requestId: action.requestId,
267
+ },
268
+ ],
269
+ }));
270
+ if (withPart !== state)
271
+ return withPart;
272
+ const target = resolveTargetConversation(state, action.requestId) ??
273
+ state.activeId ??
274
+ undefined;
275
+ if (!target)
276
+ return state;
277
+ return {
278
+ ...state,
279
+ messages: cloneMessages(state.messages, target, (list) => [
280
+ ...list,
281
+ {
282
+ id: `${target}-d-${action.requestId}`,
283
+ role: "system",
284
+ text: "",
285
+ timeLabel: "",
286
+ parts: [
287
+ {
288
+ kind: "display",
289
+ view: action.view,
290
+ title: action.title,
291
+ payload: action.payload,
292
+ requestId: action.requestId,
293
+ },
294
+ ],
295
+ },
296
+ ]),
297
+ };
298
+ }
299
+ case "streamOutput": {
300
+ const withPart = patchByRequest(state, action.requestId, (m) => ({
301
+ ...m,
302
+ parts: [
303
+ ...(m.parts ?? []),
304
+ {
305
+ kind: "output",
306
+ outputKind: action.outputKind,
307
+ label: action.label,
308
+ value: action.value,
309
+ requestId: action.requestId,
310
+ },
311
+ ],
312
+ }));
313
+ if (withPart !== state)
314
+ return withPart;
315
+ const target = resolveTargetConversation(state, action.requestId) ??
316
+ state.activeId ??
317
+ undefined;
318
+ if (!target)
319
+ return state;
320
+ return {
321
+ ...state,
322
+ messages: cloneMessages(state.messages, target, (list) => [
323
+ ...list,
324
+ {
325
+ id: `${target}-o-${action.requestId}`,
326
+ role: "system",
327
+ text: "",
328
+ timeLabel: "",
329
+ parts: [
330
+ {
331
+ kind: "output",
332
+ outputKind: action.outputKind,
333
+ label: action.label,
334
+ value: action.value,
335
+ requestId: action.requestId,
336
+ },
337
+ ],
338
+ },
339
+ ]),
340
+ };
341
+ }
342
+ case "addConversation":
343
+ return {
344
+ ...state,
345
+ conversations: [action.conversation, ...state.conversations],
346
+ activeId: action.conversation.id,
347
+ };
348
+ case "dropConversation": {
349
+ const messages = { ...state.messages };
350
+ delete messages[action.id];
351
+ const drafts = { ...state.drafts };
352
+ delete drafts[action.id];
353
+ const conversations = state.conversations.filter((c) => c.id !== action.id);
354
+ return {
355
+ ...state,
356
+ messages,
357
+ drafts,
358
+ conversations,
359
+ activeId: state.activeId === action.id
360
+ ? (conversations[0]?.id ?? null)
361
+ : state.activeId,
362
+ };
363
+ }
364
+ case "persistError":
365
+ return { ...state, persistError: action.value };
366
+ case "togglePin":
367
+ return {
368
+ ...state,
369
+ conversations: state.conversations.map((c) => c.id === action.id ? { ...c, pinned: !c.pinned } : c),
370
+ };
371
+ case "toggleMute":
372
+ return {
373
+ ...state,
374
+ conversations: state.conversations.map((c) => c.id === action.id ? { ...c, muted: !c.muted } : c),
375
+ };
376
+ case "renameConversation":
377
+ return {
378
+ ...state,
379
+ conversations: state.conversations.map((c) => c.id === action.id ? { ...c, title: action.title } : c),
380
+ };
381
+ case "setMembers":
382
+ return {
383
+ ...state,
384
+ conversations: state.conversations.map((c) => c.id === action.id
385
+ ? {
386
+ ...c,
387
+ memberIds: action.memberIds,
388
+ kind: action.memberIds.length > 1 ? "group" : "direct",
389
+ }
390
+ : c),
391
+ };
392
+ case "setAutoRespond":
393
+ return {
394
+ ...state,
395
+ conversations: state.conversations.map((c) => c.id === action.id ? { ...c, autoRespond: action.value } : c),
396
+ };
397
+ case "createGroup":
398
+ return {
399
+ ...state,
400
+ conversations: [
401
+ {
402
+ id: action.id,
403
+ kind: "group",
404
+ title: action.title,
405
+ memberIds: action.members,
406
+ autoRespond: false,
407
+ unread: 0,
408
+ pinned: false,
409
+ muted: false,
410
+ lastPreview: "群聊已创建",
411
+ timeLabel: "刚刚",
412
+ },
413
+ ...state.conversations,
414
+ ],
415
+ activeId: action.id,
416
+ };
417
+ case "replaceAgents":
418
+ return { ...state, agents: action.agents };
419
+ default:
420
+ return state;
421
+ }
422
+ }
423
+ export function createConversationState(partial = {}) {
424
+ return {
425
+ agents: {},
426
+ conversations: [],
427
+ messages: {},
428
+ drafts: {},
429
+ activeId: null,
430
+ query: "",
431
+ persistError: null,
432
+ ...partial,
433
+ };
434
+ }
435
+ export function isConversationStreaming(state, id) {
436
+ return (state.messages[id] ?? []).some((m) => m.role === "assistant" && m.state === "streaming" && m.requestId !== undefined);
437
+ }
438
+ export function inFlightRequestIds(state, id) {
439
+ return (state.messages[id] ?? [])
440
+ .filter((m) => m.state === "streaming" && m.requestId !== undefined)
441
+ .map((m) => m.requestId);
442
+ }
@@ -0,0 +1,13 @@
1
+ import type { SelectionActionId, SelectionActionPayload, SelectionActionSpec, SelectionFacts, SelectionScaffold } from "../types/selection";
2
+ import type { SelectionTarget } from "../types/callbacks";
3
+ export declare const MIN_SELECTION_LENGTH = 2;
4
+ export declare const MAX_SELECTION_LENGTH = 600;
5
+ export declare function normalizeSelectedText(raw: string): string;
6
+ export declare function selectionLengthOk(text: string): boolean;
7
+ export declare function resolveSelection(facts: SelectionFacts): SelectionTarget | null;
8
+ export declare function selectionActionsFor(target: SelectionTarget | null, options: {
9
+ canFavorite: boolean;
10
+ }): SelectionActionSpec[];
11
+ export declare function quoteFragment(text: string): string;
12
+ export declare function buildScaffold(kind: SelectionScaffold, text: string): string;
13
+ export declare function selectionActionPayload(action: SelectionActionId, target: SelectionTarget): SelectionActionPayload | null;
@@ -0,0 +1,56 @@
1
+ export const MIN_SELECTION_LENGTH = 2;
2
+ export const MAX_SELECTION_LENGTH = 600;
3
+ export function normalizeSelectedText(raw) {
4
+ return raw.replace(/\r\n?/g, "\n").trim();
5
+ }
6
+ export function selectionLengthOk(text) {
7
+ return text.length >= MIN_SELECTION_LENGTH && text.length <= MAX_SELECTION_LENGTH;
8
+ }
9
+ export function resolveSelection(facts) {
10
+ const text = normalizeSelectedText(facts.raw);
11
+ if (facts.blocked)
12
+ return null;
13
+ if (facts.bubbleRole !== "assistant")
14
+ return null;
15
+ if (!selectionLengthOk(text))
16
+ return null;
17
+ const agentId = facts.agentId && facts.agentId !== "none" ? facts.agentId : null;
18
+ return { text, agentId };
19
+ }
20
+ export function selectionActionsFor(target, options) {
21
+ const actions = [
22
+ { id: "copy", label: "复制" },
23
+ { id: "addToConversation", label: "添加到对话" },
24
+ { id: "followup", label: "深入追问" },
25
+ { id: "breakdown", label: "拆解" },
26
+ ];
27
+ if (options.canFavorite && target && target.agentId !== null) {
28
+ actions.push({ id: "favorite", label: "收藏" });
29
+ }
30
+ return actions;
31
+ }
32
+ export function quoteFragment(text) {
33
+ return text
34
+ .split("\n")
35
+ .map((line) => `> ${line}`)
36
+ .join("\n");
37
+ }
38
+ const SCAFFOLD_PROMPTS = {
39
+ followup: "请就上面这段继续深入:补充前提、细节与边界情况。",
40
+ breakdown: "请把上面这段拆解成可执行的步骤。",
41
+ };
42
+ export function buildScaffold(kind, text) {
43
+ return `${quoteFragment(text)}\n\n${SCAFFOLD_PROMPTS[kind]}\n`;
44
+ }
45
+ export function selectionActionPayload(action, target) {
46
+ if (action === "favorite") {
47
+ if (target.agentId == null)
48
+ return null;
49
+ return { kind: "favorite", agentId: target.agentId, content: target.text };
50
+ }
51
+ if (action === "copy") {
52
+ return { kind: "copy", text: target.text };
53
+ }
54
+ const scaffold = buildScaffold(action === "addToConversation" ? "followup" : action, target.text);
55
+ return scaffold.length > 0 ? { kind: "insert", text: scaffold } : null;
56
+ }
@@ -0,0 +1,6 @@
1
+ import type { ChatMessage } from "../types/message";
2
+ import type { SelectionCapabilities } from "../types/selection";
3
+ export declare function toggleId(ids: readonly string[], id: string): string[];
4
+ export declare function selectedMessages(messages: readonly ChatMessage[], ids: readonly string[]): ChatMessage[];
5
+ export declare function capabilitiesOf(messages: readonly ChatMessage[], ids: readonly string[]): SelectionCapabilities;
6
+ export declare function composeCopyText(picked: readonly ChatMessage[]): string;
@@ -0,0 +1,36 @@
1
+ export function toggleId(ids, id) {
2
+ return ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id];
3
+ }
4
+ export function selectedMessages(messages, ids) {
5
+ const set = new Set(ids);
6
+ return messages.filter((m) => set.has(m.id));
7
+ }
8
+ export function capabilitiesOf(messages, ids) {
9
+ const picked = selectedMessages(messages, ids);
10
+ const reason = {};
11
+ if (picked.length === 0) {
12
+ return {
13
+ copy: false,
14
+ remove: false,
15
+ reason: {
16
+ copy: "先选中至少一条消息",
17
+ remove: "先选中至少一条消息",
18
+ },
19
+ };
20
+ }
21
+ const copy = picked.some((m) => m.text.trim().length > 0);
22
+ if (!copy)
23
+ reason.copy = "选中消息均无可复制正文";
24
+ const allHaveSource = picked.every((m) => Boolean(m.sourceId));
25
+ const remove = allHaveSource;
26
+ if (!allHaveSource) {
27
+ reason.remove = "部分消息缺后端身份(本地消息),删除会留不可撤销的洞";
28
+ }
29
+ return { copy, remove, reason };
30
+ }
31
+ export function composeCopyText(picked) {
32
+ return picked
33
+ .map((m) => m.text.trim())
34
+ .filter((t) => t.length > 0)
35
+ .join("\n\n");
36
+ }
@@ -0,0 +1,16 @@
1
+ import type { SlashCommand, SlashCommandContext, SlashPickPlan } from "../types/composer";
2
+ export type { SlashCommand, SlashCommandContext, SlashPickPlan };
3
+ export declare const BUILT_IN_SLASH_COMMANDS: readonly SlashCommand[];
4
+ export declare function filterSlashCommands(commands: readonly SlashCommand[], query: string): SlashCommand[];
5
+ export declare function findSlashCommand(commands: readonly SlashCommand[], id: string): SlashCommand | undefined;
6
+ export declare function mergeSlashCommands(builtin: readonly SlashCommand[], ...extras: ReadonlyArray<readonly SlashCommand[]>): SlashCommand[];
7
+ export declare function expandCommandTemplate(template: string, args: string): string;
8
+ export declare function expandSlashDraft(commands: readonly SlashCommand[], draft: string): string | null;
9
+ export declare function planSlashCommandPick(params: {
10
+ value: string;
11
+ tokenStart: number;
12
+ caret: number;
13
+ trigger: string;
14
+ template: string;
15
+ }): SlashPickPlan;
16
+ export declare function slashCommandAvailable(command: SlashCommand, ctx: SlashCommandContext): boolean;