@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,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ChevronDown, Wrench } from "lucide-react";
4
+ import { cn } from "../../utils/cn";
5
+ import { toolGroupLabel } from "../../domain/message-blocks";
6
+ const STATUS_TEXT = {
7
+ running: "执行中",
8
+ done: "",
9
+ error: "有失败",
10
+ };
11
+ function groupStatus(parts) {
12
+ if (parts.some((p) => p.part.state === "error"))
13
+ return "error";
14
+ if (parts.some((p) => p.part.state === "running"))
15
+ return "running";
16
+ return "done";
17
+ }
18
+ export function ToolGroup({ parts, renderPart }) {
19
+ const [open, setOpen] = useState(false);
20
+ const status = groupStatus(parts);
21
+ const names = parts.map((p) => toolGroupLabel(p.part)).join(", ");
22
+ return (_jsxs("div", { className: "ac-tools", "data-cmp": "ac/tool-group", "data-state": open ? "open" : "closed", "data-status": status, children: [_jsxs("button", { type: "button", className: "ac-tools__trigger", "aria-expanded": open, onClick: () => setOpen((v) => !v), children: [_jsx("span", { className: "ac-tools__dot", "data-status": status }), status !== "done" ? (_jsx("span", { className: "sr-only", children: STATUS_TEXT[status] })) : null, _jsx(Wrench, { "aria-hidden": "true", size: 12 }), _jsxs("span", { className: "ac-tools__title", children: ["\u8FDB\u884C\u4E86 ", parts.length, " \u4E2A\u64CD\u4F5C"] }), _jsx("span", { className: "ac-tools__names", title: names, children: names }), _jsx(ChevronDown, { "aria-hidden": "true", size: 12, className: cn("ac-tools__chevron", open && "rotate-180") })] }), open ? (_jsx("div", { className: "ac-tools__body", children: parts.map((item) => renderPart ? (renderPart(item)) : (_jsx("div", { className: "text-xs text-muted-foreground", children: toolGroupLabel(item.part) }, item.key))) })) : null] }));
23
+ }
@@ -0,0 +1,7 @@
1
+ import type { ExecEnvelope } from "../../types/message";
2
+ export declare function parseExecResult(result: string | undefined): ExecEnvelope | null;
3
+ export declare function prettyResult(result: string): string;
4
+ export declare function diffStatOf(result: string | undefined): {
5
+ add: number;
6
+ del: number;
7
+ } | null;
@@ -0,0 +1,43 @@
1
+ export function parseExecResult(result) {
2
+ if (!result)
3
+ return null;
4
+ try {
5
+ const parsed = JSON.parse(result);
6
+ if (parsed !== null &&
7
+ typeof parsed === "object" &&
8
+ typeof parsed.exit_code === "number") {
9
+ return parsed;
10
+ }
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ return null;
16
+ }
17
+ export function prettyResult(result) {
18
+ try {
19
+ return JSON.stringify(JSON.parse(result), null, 2);
20
+ }
21
+ catch {
22
+ return result;
23
+ }
24
+ }
25
+ export function diffStatOf(result) {
26
+ if (!result)
27
+ return null;
28
+ let text = result;
29
+ const envelope = parseExecResult(result);
30
+ if (envelope)
31
+ text = envelope.stdout || envelope.stderr || "";
32
+ let add = 0;
33
+ let del = 0;
34
+ for (const line of text.split("\n")) {
35
+ if (line.startsWith("+") && !line.startsWith("+++"))
36
+ add += 1;
37
+ else if (line.startsWith("-") && !line.startsWith("---"))
38
+ del += 1;
39
+ }
40
+ if (add === 0 && del === 0)
41
+ return null;
42
+ return { add, del };
43
+ }
@@ -0,0 +1,4 @@
1
+ export { parseExecResult, prettyResult } from "./exec-envelope";
2
+ export { ToolCard, type ToolCardProps } from "./ToolCard";
3
+ export { ToolGroup, type ToolGroupProps } from "./ToolGroup";
4
+ export { SandboxCard, type SandboxCardProps } from "./SandboxCard";
@@ -0,0 +1,4 @@
1
+ export { parseExecResult, prettyResult } from "./exec-envelope";
2
+ export { ToolCard } from "./ToolCard";
3
+ export { ToolGroup } from "./ToolGroup";
4
+ export { SandboxCard } from "./SandboxCard";
@@ -0,0 +1,14 @@
1
+ import type { PendingAsk, PendingAskQuestion } from "../types/conversation";
2
+ export declare const ASK_SKIP_SENTINEL = "\uFF08\u672C\u8F6E\u5DF2\u7ED3\u675F\uFF0C\u63D0\u95EE\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u81EA\u884C\u51B3\u65AD\uFF09";
3
+ export declare function isBatchAsk(ask: PendingAsk | null): boolean;
4
+ export declare function askPlaceholder(ask: PendingAsk | null): string | undefined;
5
+ export declare function mergePendingAsk(prev: PendingAsk[], ask: PendingAsk): PendingAsk[];
6
+ export declare function dropPendingAsk(prev: PendingAsk[], callId: string): PendingAsk[];
7
+ export declare function firstPendingAsk(prev: PendingAsk[]): PendingAsk | null;
8
+ export declare function clampAskIndex(index: number, count: number): number;
9
+ export declare function stashAnswer(answers: Readonly<Record<string, string>>, questionId: string, text: string): Record<string, string>;
10
+ export declare function batchAnswersOf(questions: readonly PendingAskQuestion[], answers: Readonly<Record<string, string>>): string;
11
+ export declare function defaultAnswersOf(questions: readonly PendingAskQuestion[]): Record<string, string>;
12
+ export declare function canDefaultAll(questions: readonly PendingAskQuestion[]): boolean;
13
+ export declare function answeredCount(questions: readonly PendingAskQuestion[], answers: Readonly<Record<string, string>>): number;
14
+ export declare const MAX_ASK_QUESTIONS = 3;
@@ -0,0 +1,50 @@
1
+ export const ASK_SKIP_SENTINEL = "(本轮已结束,提问已取消,请自行决断)";
2
+ export function isBatchAsk(ask) {
3
+ return ask != null && ask.questions.length > 0;
4
+ }
5
+ export function askPlaceholder(ask) {
6
+ if (!ask || ask.questions.length > 0)
7
+ return undefined;
8
+ return `回答:${ask.prompt.slice(0, 24)}`;
9
+ }
10
+ export function mergePendingAsk(prev, ask) {
11
+ const without = prev.filter((a) => a.callId !== ask.callId);
12
+ return [...without, ask];
13
+ }
14
+ export function dropPendingAsk(prev, callId) {
15
+ return prev.filter((a) => a.callId !== callId);
16
+ }
17
+ export function firstPendingAsk(prev) {
18
+ return prev[0] ?? null;
19
+ }
20
+ export function clampAskIndex(index, count) {
21
+ if (count === 0)
22
+ return 0;
23
+ return Math.max(0, Math.min(index, count - 1));
24
+ }
25
+ export function stashAnswer(answers, questionId, text) {
26
+ return { ...answers, [questionId]: text };
27
+ }
28
+ export function batchAnswersOf(questions, answers) {
29
+ return JSON.stringify(questions.map((q) => ({
30
+ id: q.id,
31
+ answer: (answers[q.id] ?? "").trim(),
32
+ })));
33
+ }
34
+ export function defaultAnswersOf(questions) {
35
+ const out = {};
36
+ for (const q of questions) {
37
+ const pick = q.default ?? q.choices[0] ?? "";
38
+ if (pick)
39
+ out[q.id] = pick;
40
+ }
41
+ return out;
42
+ }
43
+ export function canDefaultAll(questions) {
44
+ return (questions.length > 0 &&
45
+ questions.every((q) => Boolean(q.default ?? q.choices[0])));
46
+ }
47
+ export function answeredCount(questions, answers) {
48
+ return questions.filter((q) => (answers[q.id] ?? "").trim().length > 0).length;
49
+ }
50
+ export const MAX_ASK_QUESTIONS = 3;
@@ -0,0 +1,16 @@
1
+ import type { Attachment } from "../types/message";
2
+ import type { IncomingPlan, PickedFile } from "../types/composer";
3
+ export declare const MAX_ATTACHMENT_BYTES: number;
4
+ export declare const MAX_ATTACHMENTS = 9;
5
+ export declare const MAX_VIDEO_BYTES: number;
6
+ export declare const MAX_VIDEOS = 3;
7
+ export declare const MAX_TEXT_BYTES: number;
8
+ export declare const MAX_TEXT_FILES = 3;
9
+ export declare function isTextFile(file: PickedFile): boolean;
10
+ export declare function formatBytes(bytes: number): string;
11
+ export declare function planIncoming<T extends PickedFile>(existingImages: number, existingVideos: number, files: readonly T[]): IncomingPlan<T>;
12
+ export declare function attachmentColumns(count: number): 1 | 2 | 3;
13
+ export declare function attachmentSrc(attachment: Attachment): string;
14
+ export declare function attachmentFromDataUrl(filename: string, fallbackType: string, dataUrl: string): Attachment | null;
15
+ export declare function readAttachment(file: File): Promise<Attachment>;
16
+ export declare function textFileToFence(name: string, content: string): string;
@@ -0,0 +1,126 @@
1
+ export const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
2
+ export const MAX_ATTACHMENTS = 9;
3
+ export const MAX_VIDEO_BYTES = 25 * 1024 * 1024;
4
+ export const MAX_VIDEOS = 3;
5
+ export const MAX_TEXT_BYTES = 256 * 1024;
6
+ export const MAX_TEXT_FILES = 3;
7
+ const TEXT_EXTENSIONS = new Set([
8
+ "txt", "md", "markdown", "json", "rs", "ts", "tsx", "js", "jsx", "py",
9
+ "toml", "yaml", "yml", "csv", "log", "css", "html", "sql", "sh", "ini",
10
+ ]);
11
+ export function isTextFile(file) {
12
+ if (file.type.startsWith("text/"))
13
+ return true;
14
+ const dot = file.name.lastIndexOf(".");
15
+ return dot >= 0 && TEXT_EXTENSIONS.has(file.name.slice(dot + 1).toLowerCase());
16
+ }
17
+ export function formatBytes(bytes) {
18
+ if (bytes < 1024)
19
+ return `${bytes} B`;
20
+ if (bytes < 1024 * 1024)
21
+ return `${(bytes / 1024).toFixed(1)} KB`;
22
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
23
+ }
24
+ export function planIncoming(existingImages, existingVideos, files) {
25
+ const plan = {
26
+ images: [],
27
+ videos: [],
28
+ texts: [],
29
+ rejected: [],
30
+ };
31
+ let images = existingImages;
32
+ let videos = existingVideos;
33
+ let texts = 0;
34
+ for (const file of files) {
35
+ if (file.type.startsWith("image/")) {
36
+ if (images >= MAX_ATTACHMENTS) {
37
+ plan.rejected.push({ name: file.name, reason: `图片最多 ${MAX_ATTACHMENTS} 张` });
38
+ }
39
+ else if (file.size > MAX_ATTACHMENT_BYTES) {
40
+ plan.rejected.push({
41
+ name: file.name,
42
+ reason: `图片过大(${formatBytes(file.size)}),上限 ${formatBytes(MAX_ATTACHMENT_BYTES)}`,
43
+ });
44
+ }
45
+ else {
46
+ plan.images.push(file);
47
+ images += 1;
48
+ }
49
+ continue;
50
+ }
51
+ if (file.type.startsWith("video/")) {
52
+ if (videos >= MAX_VIDEOS) {
53
+ plan.rejected.push({ name: file.name, reason: `视频最多 ${MAX_VIDEOS} 个` });
54
+ }
55
+ else if (file.size > MAX_VIDEO_BYTES) {
56
+ plan.rejected.push({
57
+ name: file.name,
58
+ reason: `视频过大(${formatBytes(file.size)}),上限 ${formatBytes(MAX_VIDEO_BYTES)}`,
59
+ });
60
+ }
61
+ else {
62
+ plan.videos.push(file);
63
+ videos += 1;
64
+ }
65
+ continue;
66
+ }
67
+ if (isTextFile(file)) {
68
+ if (texts >= MAX_TEXT_FILES) {
69
+ plan.rejected.push({ name: file.name, reason: `文本文件最多 ${MAX_TEXT_FILES} 个` });
70
+ }
71
+ else if (file.size > MAX_TEXT_BYTES) {
72
+ plan.rejected.push({
73
+ name: file.name,
74
+ reason: `文本过大(${formatBytes(file.size)}),请分段粘贴`,
75
+ });
76
+ }
77
+ else {
78
+ plan.texts.push(file);
79
+ texts += 1;
80
+ }
81
+ continue;
82
+ }
83
+ plan.rejected.push({ name: file.name, reason: `不支持的类型(${file.type || "未知"})` });
84
+ }
85
+ return plan;
86
+ }
87
+ export function attachmentColumns(count) {
88
+ if (count <= 1)
89
+ return 1;
90
+ if (count <= 4)
91
+ return 2;
92
+ return 3;
93
+ }
94
+ export function attachmentSrc(attachment) {
95
+ return `data:${attachment.media_type};base64,${attachment.data_base64}`;
96
+ }
97
+ export function attachmentFromDataUrl(filename, fallbackType, dataUrl) {
98
+ const m = /^data:([^;,]*);base64,(.*)$/.exec(dataUrl);
99
+ if (!m)
100
+ return null;
101
+ return {
102
+ media_type: m[1] || fallbackType,
103
+ data_base64: m[2],
104
+ filename: filename.trim().length > 0 ? filename.trim() : null,
105
+ };
106
+ }
107
+ export function readAttachment(file) {
108
+ return new Promise((resolve, reject) => {
109
+ const reader = new FileReader();
110
+ reader.onload = () => {
111
+ const result = typeof reader.result === "string" ? reader.result : "";
112
+ const parsed = attachmentFromDataUrl(file.name, file.type, result);
113
+ if (parsed)
114
+ resolve(parsed);
115
+ else
116
+ reject(new Error(`读取附件失败:${file.name}`));
117
+ };
118
+ reader.onerror = () => reject(new Error(`读取附件失败:${file.name}`));
119
+ reader.readAsDataURL(file);
120
+ });
121
+ }
122
+ export function textFileToFence(name, content) {
123
+ const normalized = content.replace(/\r\n?/g, "\n");
124
+ const fenced = normalized.replace(/`{3,}/g, "``");
125
+ return `#### 📄 ${name}\n\`\`\`\n${fenced}\n\`\`\`\n`;
126
+ }
@@ -0,0 +1,4 @@
1
+ import type { Agent } from "../types/agent";
2
+ import type { SenderAttribution } from "../types/agent";
3
+ import type { ChatMessage } from "../types/message";
4
+ export declare function attributionOf(message: Pick<ChatMessage, "agentId">, agentById: Record<string, Agent>): SenderAttribution;
@@ -0,0 +1,11 @@
1
+ export function attributionOf(message, agentById) {
2
+ const id = message.agentId;
3
+ if (!id) {
4
+ return { name: "Agent", badge: "?" };
5
+ }
6
+ const agent = agentById[id];
7
+ if (agent) {
8
+ return { name: agent.name, badge: agent.badge, agent };
9
+ }
10
+ return { name: "未知成员", badge: id.slice(0, 1).toUpperCase() };
11
+ }
@@ -0,0 +1,15 @@
1
+ import type { Agent } from "../types/agent";
2
+ import type { PaletteFileEntry, PaletteItem, PaletteKeyAction, PaletteSource, SlashCommand, TriggerToken } from "../types/composer";
3
+ export declare function detectTriggerToken(text: string, caret: number): TriggerToken | null;
4
+ export declare function buildMentionItems(members: Agent[], rawQuery: string): PaletteItem[];
5
+ export declare const SKILL_COMMAND_ID_PREFIX = "skill:";
6
+ export declare function skillNameOfCommand(command: SlashCommand): string | null;
7
+ export declare function buildAgentSkillItems(agent: Agent, skillCommands: readonly SlashCommand[], rawQuery?: string): PaletteItem[];
8
+ export declare const FILE_CANDIDATE_LIMIT = 8;
9
+ export declare function filterFileCandidates(entries: readonly PaletteFileEntry[], rawQuery: string, limit?: number): PaletteItem[];
10
+ export declare function buildPaletteItems(token: TriggerToken, source: PaletteSource): PaletteItem[];
11
+ export declare function moveIndex(index: number, delta: number, length: number): number;
12
+ export declare function paletteKeyDownAction(key: string, shift: boolean): PaletteKeyAction | null;
13
+ export type PaletteRecencyId = string;
14
+ export declare function paletteRecencyId(item: PaletteItem): PaletteRecencyId;
15
+ export declare function rankByRecency(items: PaletteItem[], recent: readonly string[]): PaletteItem[];
@@ -0,0 +1,131 @@
1
+ import { filterSlashCommands } from "./slash-commands";
2
+ const MENTION_RE = /(?:^|\s)@([^\s@]*)$/;
3
+ const SLASH_RE = /(?:^|\s)\/([^\s/]*)$/;
4
+ export function detectTriggerToken(text, caret) {
5
+ const before = text.slice(0, caret);
6
+ const slash = SLASH_RE.exec(before);
7
+ const mention = MENTION_RE.exec(before);
8
+ if (!slash && !mention)
9
+ return null;
10
+ let m;
11
+ let trigger;
12
+ if (slash && mention) {
13
+ if (slash.index >= mention.index) {
14
+ m = slash;
15
+ trigger = "/";
16
+ }
17
+ else {
18
+ m = mention;
19
+ trigger = "@";
20
+ }
21
+ }
22
+ else if (slash) {
23
+ m = slash;
24
+ trigger = "/";
25
+ }
26
+ else {
27
+ m = mention;
28
+ trigger = "@";
29
+ }
30
+ return {
31
+ trigger,
32
+ start: m.index + (m[0].startsWith(" ") ? 1 : 0),
33
+ query: m[1] ?? "",
34
+ };
35
+ }
36
+ export function buildMentionItems(members, rawQuery) {
37
+ const q = rawQuery.trim().toLowerCase();
38
+ const includeAll = q.length === 0 || "所有人".includes(q) || "all".startsWith(q);
39
+ const items = [];
40
+ if (includeAll)
41
+ items.push({ kind: "all" });
42
+ for (const agent of members) {
43
+ if (q.length === 0 ||
44
+ agent.name.toLowerCase().includes(q) ||
45
+ agent.role.toLowerCase().includes(q) ||
46
+ agent.badge === rawQuery) {
47
+ items.push({ kind: "agent", agent });
48
+ }
49
+ }
50
+ return items;
51
+ }
52
+ export const SKILL_COMMAND_ID_PREFIX = "skill:";
53
+ export function skillNameOfCommand(command) {
54
+ return command.source === "skill" && command.id.startsWith(SKILL_COMMAND_ID_PREFIX)
55
+ ? command.id.slice(SKILL_COMMAND_ID_PREFIX.length)
56
+ : null;
57
+ }
58
+ export function buildAgentSkillItems(agent, skillCommands, rawQuery = "") {
59
+ const disabled = new Set(agent.disabledSkills);
60
+ const available = skillCommands.filter((c) => {
61
+ const name = skillNameOfCommand(c);
62
+ return name != null && !disabled.has(name);
63
+ });
64
+ return filterSlashCommands(available, rawQuery).map((command) => ({
65
+ kind: "command",
66
+ command,
67
+ }));
68
+ }
69
+ export const FILE_CANDIDATE_LIMIT = 8;
70
+ export function filterFileCandidates(entries, rawQuery, limit = FILE_CANDIDATE_LIMIT) {
71
+ const q = rawQuery.trim().toLowerCase();
72
+ return entries
73
+ .filter((e) => e.path.toLowerCase().includes(q))
74
+ .sort((a, b) => {
75
+ const da = a.path.split("/").length;
76
+ const db = b.path.split("/").length;
77
+ return da !== db ? da - db : a.path.localeCompare(b.path);
78
+ })
79
+ .slice(0, limit)
80
+ .map((e) => ({ kind: "file", path: e.path, entryKind: e.entryKind }));
81
+ }
82
+ export function buildPaletteItems(token, source) {
83
+ if (token.trigger === "@") {
84
+ return [
85
+ ...buildMentionItems(source.members, token.query),
86
+ ...(source.files ? filterFileCandidates(source.files, token.query) : []),
87
+ ];
88
+ }
89
+ return filterSlashCommands(source.commands, token.query).map((command) => ({
90
+ kind: "command",
91
+ command,
92
+ }));
93
+ }
94
+ export function moveIndex(index, delta, length) {
95
+ if (length === 0)
96
+ return 0;
97
+ return ((index + delta) % length + length) % length;
98
+ }
99
+ export function paletteKeyDownAction(key, shift) {
100
+ switch (key) {
101
+ case "ArrowDown":
102
+ return "move-next";
103
+ case "ArrowUp":
104
+ return "move-prev";
105
+ case "Tab":
106
+ return "accept";
107
+ case "Enter":
108
+ return shift ? "newline" : "accept";
109
+ case "Escape":
110
+ return "close";
111
+ default:
112
+ return null;
113
+ }
114
+ }
115
+ export function paletteRecencyId(item) {
116
+ switch (item.kind) {
117
+ case "all":
118
+ return "all";
119
+ case "agent":
120
+ return `agent:${item.agent.id}`;
121
+ case "command":
122
+ return `command:${item.command.id}`;
123
+ case "file":
124
+ return `file:${item.path}`;
125
+ }
126
+ }
127
+ export function rankByRecency(items, recent) {
128
+ const order = new Map(recent.map((id, i) => [id, i]));
129
+ const rank = (item) => item.kind === "file" ? Number.MAX_SAFE_INTEGER : (order.get(paletteRecencyId(item)) ?? Number.MAX_SAFE_INTEGER - 1);
130
+ return [...items].sort((a, b) => rank(a) - rank(b));
131
+ }
@@ -0,0 +1,12 @@
1
+ export * from "./message-blocks";
2
+ export * from "./attribution";
3
+ export * from "./composer-palette";
4
+ export * from "./slash-commands";
5
+ export * from "./selection-actions";
6
+ export * from "./selection-gates";
7
+ export * from "./attachments";
8
+ export * from "./message-menu";
9
+ export * from "./reducer";
10
+ export * from "./queue";
11
+ export * from "./ask";
12
+ export * from "./mention";
@@ -0,0 +1,12 @@
1
+ export * from "./message-blocks";
2
+ export * from "./attribution";
3
+ export * from "./composer-palette";
4
+ export * from "./slash-commands";
5
+ export * from "./selection-actions";
6
+ export * from "./selection-gates";
7
+ export * from "./attachments";
8
+ export * from "./message-menu";
9
+ export * from "./reducer";
10
+ export * from "./queue";
11
+ export * from "./ask";
12
+ export * from "./mention";
@@ -0,0 +1,17 @@
1
+ import type { Agent } from "../types/agent";
2
+ export interface MentionTarget {
3
+ id: string;
4
+ name: string;
5
+ }
6
+ export declare function mentionTargetsFromAgents(agents: readonly Agent[]): MentionTarget[];
7
+ type MdastNode = {
8
+ type: string;
9
+ value?: string;
10
+ children?: MdastNode[];
11
+ data?: {
12
+ hName?: string;
13
+ hProperties?: Record<string, unknown>;
14
+ };
15
+ };
16
+ export declare function remarkMentions(targets: readonly MentionTarget[]): () => (tree: MdastNode) => void;
17
+ export {};
@@ -0,0 +1,95 @@
1
+ export function mentionTargetsFromAgents(agents) {
2
+ return agents.map((a) => ({ id: a.id, name: a.name }));
3
+ }
4
+ function escapeRegExp(s) {
5
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+ }
7
+ export function remarkMentions(targets) {
8
+ return () => (tree) => {
9
+ if (targets.length === 0)
10
+ return;
11
+ const names = targets
12
+ .map((t) => t.name)
13
+ .filter((n) => n.length > 0)
14
+ .sort((a, b) => b.length - a.length);
15
+ if (names.length === 0)
16
+ return;
17
+ const re = new RegExp(`@(${names.map(escapeRegExp).join("|")})`, "g");
18
+ const walk = (node, inCode) => {
19
+ if (node.type === "code" || node.type === "inlineCode")
20
+ return;
21
+ if (node.type === "text" && typeof node.value === "string") {
22
+ const text = node.value;
23
+ if (!re.test(text))
24
+ return;
25
+ re.lastIndex = 0;
26
+ const parts = [];
27
+ let last = 0;
28
+ let m;
29
+ while ((m = re.exec(text)) !== null) {
30
+ if (m.index > last) {
31
+ parts.push({ type: "text", value: text.slice(last, m.index) });
32
+ }
33
+ const name = m[1];
34
+ const target = targets.find((t) => t.name === name);
35
+ parts.push({
36
+ type: "text",
37
+ value: m[0],
38
+ data: {
39
+ hName: "mention",
40
+ hProperties: { agent: target?.id, name },
41
+ },
42
+ });
43
+ last = m.index + m[0].length;
44
+ }
45
+ if (last < text.length)
46
+ parts.push({ type: "text", value: text.slice(last) });
47
+ const parent = node;
48
+ if (parts.length > 0) {
49
+ const first = parts[0];
50
+ node.value = first.value ?? "";
51
+ if (first.data)
52
+ node.data = first.data;
53
+ }
54
+ void parent;
55
+ void inCode;
56
+ return;
57
+ }
58
+ if (Array.isArray(node.children)) {
59
+ const next = [];
60
+ for (const child of node.children) {
61
+ if (child.type === "text" && typeof child.value === "string" && re.test(child.value)) {
62
+ re.lastIndex = 0;
63
+ const text = child.value;
64
+ let last = 0;
65
+ let m;
66
+ while ((m = re.exec(text)) !== null) {
67
+ if (m.index > last) {
68
+ next.push({ type: "text", value: text.slice(last, m.index) });
69
+ }
70
+ const name = m[1];
71
+ const target = targets.find((t) => t.name === name);
72
+ next.push({
73
+ type: "text",
74
+ value: m[0],
75
+ data: {
76
+ hName: "mention",
77
+ hProperties: { agent: target?.id, name },
78
+ },
79
+ });
80
+ last = m.index + m[0].length;
81
+ }
82
+ if (last < text.length)
83
+ next.push({ type: "text", value: text.slice(last) });
84
+ }
85
+ else {
86
+ walk(child, inCode);
87
+ next.push(child);
88
+ }
89
+ }
90
+ node.children = next;
91
+ }
92
+ };
93
+ walk(tree, false);
94
+ };
95
+ }
@@ -0,0 +1,29 @@
1
+ import type { ChatMessage, MessagePart, SandboxPart, ToolCallPart } from "../types/message";
2
+ export interface KeyedPart {
3
+ key: string;
4
+ part: MessagePart;
5
+ }
6
+ export type ToolGroupPart = ToolCallPart | SandboxPart;
7
+ export interface KeyedToolPart {
8
+ key: string;
9
+ part: ToolGroupPart;
10
+ }
11
+ export type MessageBlock = {
12
+ type: "part";
13
+ key: string;
14
+ part: MessagePart;
15
+ } | {
16
+ type: "tool-group";
17
+ key: string;
18
+ parts: KeyedToolPart[];
19
+ } | {
20
+ type: "text";
21
+ key: string;
22
+ };
23
+ export declare const TOOL_GROUP_MIN = 2;
24
+ export declare function isToolGroupPart(part: MessagePart): part is ToolGroupPart;
25
+ export declare function toolGroupLabel(part: ToolGroupPart): string;
26
+ export declare function keyMessageParts(message: ChatMessage): KeyedPart[];
27
+ export declare function orderMessageBlocks(message: ChatMessage, showBubble: boolean): MessageBlock[];
28
+ export declare function promoteMemoryFirst(blocks: MessageBlock[]): MessageBlock[];
29
+ export declare function blockKind(block: MessageBlock): MessagePart["kind"] | "text" | "tool-group";