@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,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState, } from "react";
3
+ import { ArrowDown } from "lucide-react";
4
+ import { Button } from "../../primitives/Button";
5
+ import { Empty } from "../../primitives/Empty";
6
+ import { cn } from "../../utils/cn";
7
+ export const NEAR_BOTTOM_PX = 80;
8
+ export function ConversationShell({ className, ...props }) {
9
+ return (_jsx("div", { "data-cmp": "ai/conversation", className: cn("flex min-h-0 flex-col", className), ...props }));
10
+ }
11
+ export function ConversationContent({ className, ...props }) {
12
+ return (_jsx("div", { "data-cmp": "ai/conversation-content", className: cn("flex min-h-0 flex-1 flex-col overflow-y-auto px-4 py-4", className), ...props }));
13
+ }
14
+ export function ConversationScroller({ className, children, ...props }) {
15
+ const viewportRef = useRef(null);
16
+ const [nearBottom, setNearBottom] = useState(true);
17
+ const measure = useCallback(() => {
18
+ const el = viewportRef.current;
19
+ if (!el)
20
+ return;
21
+ const near = el.scrollHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_PX;
22
+ setNearBottom((prev) => (prev === near ? prev : near));
23
+ }, []);
24
+ useEffect(() => {
25
+ if (!nearBottom)
26
+ return;
27
+ const el = viewportRef.current;
28
+ if (el)
29
+ el.scrollTop = el.scrollHeight;
30
+ });
31
+ return (_jsxs("div", { "data-cmp": "ai/conversation-scroller", className: cn("relative flex min-h-0 flex-1 flex-col", className), ...props, children: [_jsx("div", { ref: viewportRef, onScroll: measure, className: "flex min-h-0 flex-1 flex-col overflow-y-auto px-4 py-4", children: children }), !nearBottom ? (_jsx(Button, { type: "button", variant: "outline", size: "icon-sm", "aria-label": "\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F", "data-state": "visible", className: "absolute bottom-3 left-1/2 z-panel -translate-x-1/2 rounded-full shadow-lg", onClick: () => {
32
+ viewportRef.current?.scrollTo({
33
+ top: viewportRef.current.scrollHeight,
34
+ behavior: "smooth",
35
+ });
36
+ setNearBottom(true);
37
+ }, children: _jsx(ArrowDown, {}) })) : null] }));
38
+ }
39
+ export function ConversationEmpty({ title, hint, className, ...props }) {
40
+ return (_jsx("div", { "data-cmp": "ai/conversation-empty", "data-state": "empty", className: cn("contents", className), ...props, children: _jsx(Empty, { title: title, hint: hint }) }));
41
+ }
@@ -0,0 +1,14 @@
1
+ export interface LineSidebarItem {
2
+ id: string;
3
+ label: string;
4
+ badge?: string | number;
5
+ }
6
+ export interface LineSidebarProps {
7
+ items: readonly LineSidebarItem[];
8
+ activeId?: string | null;
9
+ onItemClick?: (id: string, label: string) => void;
10
+ proximityRadius?: number;
11
+ maxShift?: number;
12
+ className?: string;
13
+ }
14
+ export declare function LineSidebar({ items, activeId, onItemClick, proximityRadius, maxShift, className, }: LineSidebarProps): import("react").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState, } from "react";
3
+ import { cn } from "../../utils/cn";
4
+ export function LineSidebar({ items, activeId = null, onItemClick, proximityRadius = 100, maxShift = 30, className, }) {
5
+ const [prox, setProx] = useState({});
6
+ const rootRef = useRef(null);
7
+ const onMove = useCallback((e) => {
8
+ const next = {};
9
+ for (const el of rootRef.current?.querySelectorAll("[data-line-item]") ?? []) {
10
+ const id = el.dataset.lineItem ?? "";
11
+ const rect = el.getBoundingClientRect();
12
+ const mid = rect.top + rect.height / 2;
13
+ const dist = Math.abs(e.clientY - mid);
14
+ const t = Math.max(0, 1 - dist / proximityRadius);
15
+ const eased = t * t * (3 - 2 * t);
16
+ next[id] = { shift: eased * maxShift, t: eased };
17
+ }
18
+ setProx(next);
19
+ }, [proximityRadius, maxShift]);
20
+ useEffect(() => {
21
+ const reset = () => setProx({});
22
+ window.addEventListener("blur", reset);
23
+ return () => window.removeEventListener("blur", reset);
24
+ }, []);
25
+ return (_jsx("div", { ref: rootRef, role: "navigation", "aria-label": "\u4F1A\u8BDD\u5217\u8868", "data-cmp": "ai/line-sidebar", "data-id": "line-sidebar", className: cn("ac-line-nav", className), onMouseMove: onMove, onMouseLeave: () => setProx({}), children: items.map((item, i) => {
26
+ const p = prox[item.id] ?? { shift: 0, t: 0 };
27
+ const active = item.id === activeId;
28
+ return (_jsxs("button", { type: "button", "data-line-item": item.id, "data-state": active ? "active" : "idle", className: "ac-line-nav__item", style: { transform: `translateX(${p.shift}px)` }, onClick: () => onItemClick?.(item.id, item.label), children: [_jsx("span", { className: "ac-line-nav__marker", style: {
29
+ width: `${24 + p.t * 36}px`,
30
+ opacity: 0.35 + p.t * 0.55,
31
+ background: active
32
+ ? "var(--accent-primary)"
33
+ : `color-mix(in srgb, var(--accent-primary) ${Math.round(p.t * 70)}%, var(--border-500))`,
34
+ } }), _jsx("span", { className: "ac-line-nav__index", children: String(i + 1).padStart(2, "0") }), _jsx("span", { className: "ac-line-nav__label", style: {
35
+ color: active
36
+ ? "var(--accent-primary)"
37
+ : p.t > 0
38
+ ? `color-mix(in srgb, var(--accent-primary) ${Math.round(p.t * 60)}%, var(--text-300))`
39
+ : undefined,
40
+ }, children: item.label }), item.badge != null ? (_jsx("span", { className: "ac-line-nav__badge", children: item.badge })) : null] }, item.id));
41
+ }) }));
42
+ }
@@ -0,0 +1,5 @@
1
+ export { ConversationShell, ConversationContent, ConversationScroller, ConversationEmpty, NEAR_BOTTOM_PX, } from "./Conversation";
2
+ export { ChatEmptyScene, type ChatEmptySceneProps } from "./ChatEmptyScene";
3
+ export { ChatHeader, type ChatHeaderProps } from "./ChatHeader";
4
+ export { ChatPane, planSmartStop, type ChatPaneProps, type SmartStopPlan, } from "./ChatPane";
5
+ export { LineSidebar, type LineSidebarProps, type LineSidebarItem, } from "./LineSidebar";
@@ -0,0 +1,5 @@
1
+ export { ConversationShell, ConversationContent, ConversationScroller, ConversationEmpty, NEAR_BOTTOM_PX, } from "./Conversation";
2
+ export { ChatEmptyScene } from "./ChatEmptyScene";
3
+ export { ChatHeader } from "./ChatHeader";
4
+ export { ChatPane, planSmartStop, } from "./ChatPane";
5
+ export { LineSidebar, } from "./LineSidebar";
@@ -0,0 +1,9 @@
1
+ export * from "./message";
2
+ export * from "./reasoning";
3
+ export * from "./tool";
4
+ export * from "./cards";
5
+ export * from "./composer";
6
+ export * from "./selection";
7
+ export * from "./conversation";
8
+ export * from "./status";
9
+ export * from "./settings";
@@ -0,0 +1,9 @@
1
+ export * from "./message";
2
+ export * from "./reasoning";
3
+ export * from "./tool";
4
+ export * from "./cards";
5
+ export * from "./composer";
6
+ export * from "./selection";
7
+ export * from "./conversation";
8
+ export * from "./status";
9
+ export * from "./settings";
@@ -0,0 +1,10 @@
1
+ export type { ChatMessage } from "../../types/message";
2
+ export declare function dayLabelOf(iso: string | undefined): string | null;
3
+ export declare function dayDividerFor(prev: {
4
+ createdAt?: string;
5
+ } | undefined, current: {
6
+ createdAt?: string;
7
+ }): string | null;
8
+ export declare function DayDivider({ label }: {
9
+ label: string;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function dayLabelOf(iso) {
3
+ if (!iso)
4
+ return null;
5
+ const date = new Date(iso);
6
+ if (Number.isNaN(date.getTime()))
7
+ return iso;
8
+ const now = new Date();
9
+ const sameDay = (a, b) => a.getFullYear() === b.getFullYear() &&
10
+ a.getMonth() === b.getMonth() &&
11
+ a.getDate() === b.getDate();
12
+ const yesterday = new Date(now);
13
+ yesterday.setDate(now.getDate() - 1);
14
+ if (sameDay(date, now))
15
+ return "今天";
16
+ if (sameDay(date, yesterday))
17
+ return "昨天";
18
+ return `${date.getMonth() + 1}月${date.getDate()}日`;
19
+ }
20
+ export function dayDividerFor(prev, current) {
21
+ if (!prev?.createdAt || !current.createdAt)
22
+ return null;
23
+ const a = prev.createdAt.slice(0, 10);
24
+ const b = current.createdAt.slice(0, 10);
25
+ return a === b ? null : dayLabelOf(current.createdAt);
26
+ }
27
+ export function DayDivider({ label }) {
28
+ return (_jsx("div", { className: "ac-stream__day", "data-cmp": "ac/day-divider", role: "separator", children: label }));
29
+ }
@@ -0,0 +1,5 @@
1
+ export declare function looksLikeDiff(code: string): boolean;
2
+ export declare function DiffView({ code, className, }: {
3
+ code: string;
4
+ className?: string;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../utils/cn";
3
+ export function looksLikeDiff(code) {
4
+ const lines = code.split("\n").filter(Boolean);
5
+ if (lines.length === 0)
6
+ return false;
7
+ const marked = lines.filter((l) => (l.startsWith("+") && !l.startsWith("+++")) ||
8
+ (l.startsWith("-") && !l.startsWith("---"))).length;
9
+ return marked > 0 && marked / lines.length >= 0.3;
10
+ }
11
+ export function DiffView({ code, className, }) {
12
+ const lines = code.replace(/\n$/, "").split("\n");
13
+ return (_jsx("div", { className: cn("overflow-hidden rounded-md border border-border bg-background", className), "data-cmp": "ai/diff-view", children: lines.map((line, i) => {
14
+ const isAdd = line.startsWith("+") && !line.startsWith("+++");
15
+ const isDel = line.startsWith("-") && !line.startsWith("---");
16
+ return (_jsxs("div", { "data-tone": isAdd ? "add" : isDel ? "del" : "ctx", className: "flex text-xs leading-5", children: [_jsx("span", { className: "w-10 shrink-0 select-none border-r border-border px-2 text-right font-mono text-micro text-muted-foreground", children: i + 1 }), _jsx("pre", { className: cn("min-w-0 flex-1 overflow-x-auto px-2.5 font-mono whitespace-pre", isAdd && "bg-[var(--diff-add-bg)] text-[var(--diff-add-fg)]", isDel && "bg-[var(--diff-del-bg)] text-[var(--diff-del-fg)]", !isAdd && !isDel && "text-muted-foreground"), children: line || " " })] }, i));
17
+ }) }));
18
+ }
@@ -0,0 +1,4 @@
1
+ export interface MermaidDiagramProps {
2
+ code: string;
3
+ }
4
+ export declare function MermaidDiagram({ code }: MermaidDiagramProps): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ let apiPromise = null;
4
+ function loadMermaid() {
5
+ if (!apiPromise) {
6
+ apiPromise = import("mermaid").then((mod) => {
7
+ const api = (mod.default ?? mod);
8
+ api.initialize({
9
+ startOnLoad: false,
10
+ securityLevel: "strict",
11
+ theme: "dark",
12
+ });
13
+ return api;
14
+ });
15
+ }
16
+ return apiPromise;
17
+ }
18
+ export function MermaidDiagram({ code }) {
19
+ const [svg, setSvg] = useState(null);
20
+ const [error, setError] = useState(null);
21
+ useEffect(() => {
22
+ let cancelled = false;
23
+ setSvg(null);
24
+ setError(null);
25
+ loadMermaid()
26
+ .then((api) => api.render(`mmd-${Math.random().toString(36).slice(2)}`, code))
27
+ .then((result) => {
28
+ if (!cancelled)
29
+ setSvg(result.svg);
30
+ })
31
+ .catch((err) => {
32
+ if (!cancelled)
33
+ setError(err instanceof Error ? err.message : String(err));
34
+ });
35
+ return () => {
36
+ cancelled = true;
37
+ };
38
+ }, [code]);
39
+ if (error !== null) {
40
+ return (_jsxs("div", { "data-cmp": "ai/mermaid", "data-state": "error", className: "my-2 space-y-1 rounded border border-destructive/40 p-2", children: [_jsxs("p", { className: "text-xs text-destructive", children: ["Mermaid \u6E32\u67D3\u5931\u8D25\uFF1A", error] }), _jsx("pre", { className: "overflow-x-auto font-mono text-xs text-muted-foreground", children: code })] }));
41
+ }
42
+ return (_jsx("div", { "data-cmp": "ai/mermaid", "data-state": svg === null ? "loading" : "ready", "aria-label": "Mermaid \u56FE", className: "my-2 overflow-x-auto rounded border border-border bg-background/60 p-2 [&_svg]:mx-auto [&_svg]:max-w-full", children: svg === null ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "Mermaid \u6E32\u67D3\u4E2D\u2026" })) : (_jsx("div", { dangerouslySetInnerHTML: { __html: svg } })) }));
43
+ }
@@ -0,0 +1,17 @@
1
+ import { type ComponentProps, type ReactElement, type ReactNode } from "react";
2
+ import { type Components, type Options } from "react-markdown";
3
+ export type { MessageRole } from "../../types/message";
4
+ import type { MessageRole } from "../../types/message";
5
+ export interface MessageProps extends ComponentProps<"div"> {
6
+ from: MessageRole;
7
+ }
8
+ export declare function Message({ from, className, children, ...props }: MessageProps): import("react").JSX.Element;
9
+ export declare function MessageContent({ from, className, children, ...props }: MessageProps): import("react").JSX.Element;
10
+ export interface MessageResponseProps extends Omit<ComponentProps<"div">, "children"> {
11
+ children?: string;
12
+ remarkPlugins?: Options["remarkPlugins"];
13
+ rehypePlugins?: Options["rehypePlugins"];
14
+ markdownComponents?: Components;
15
+ }
16
+ export declare function MessageResponse({ children, remarkPlugins, rehypePlugins, markdownComponents, className, ...props }: MessageResponseProps): import("react").JSX.Element;
17
+ export type { Components, ReactNode, ReactElement };
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, } from "react";
3
+ import ReactMarkdown, {} from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import { Check, Copy } from "lucide-react";
6
+ import { cn } from "../../utils/cn";
7
+ import { writeClipboard } from "../../utils/clipboard";
8
+ import { collectCodeText } from "./code-text";
9
+ import { MermaidDiagram } from "./MermaidDiagram";
10
+ import { DiffView, looksLikeDiff } from "./DiffView";
11
+ const ROLE_BUBBLE = {
12
+ user: "ml-auto max-w-[85%] rounded-2xl bg-primary/15 px-4 py-2.5 text-foreground",
13
+ assistant: "w-full max-w-none rounded-none border-0 bg-transparent px-0 py-0 text-foreground",
14
+ system: "mx-auto max-w-[92%] rounded-lg bg-secondary/50 px-3 py-1.5 text-meta text-muted-foreground",
15
+ tool: "w-full rounded-lg border border-dashed border-border bg-secondary/40 px-3 py-2",
16
+ };
17
+ export function Message({ from, className, children, ...props }) {
18
+ return (_jsx("div", { "data-cmp": "ai/message", "data-msg-role": from, className: cn("flex w-full", className), ...props, children: children }));
19
+ }
20
+ export function MessageContent({ from, className, children, ...props }) {
21
+ return (_jsx("div", { "data-cmp": "ai/message-content", "data-msg-role": from, className: cn("text-body", ROLE_BUBBLE[from], className), ...props, children: children }));
22
+ }
23
+ function CodeBlock({ node, children, ...props }) {
24
+ const [copied, setCopied] = useState("idle");
25
+ const classNames = Array.isArray(node?.properties?.className)
26
+ ? node?.properties?.className
27
+ : [];
28
+ const isMermaid = classNames.some((c) => c === "language-mermaid");
29
+ const isDiff = classNames.some((c) => c === "language-diff") ||
30
+ (!isMermaid && looksLikeDiff(collectCodeText(node)));
31
+ if (isMermaid) {
32
+ return _jsx(MermaidDiagram, { code: collectCodeText(node) });
33
+ }
34
+ if (isDiff) {
35
+ return (_jsxs("div", { className: "relative", children: [_jsx(DiffView, { code: collectCodeText(node) }), _jsxs("button", { type: "button", "data-cmp": "ai/code-copy", "data-state": copied, "aria-label": "\u590D\u5236\u4EE3\u7801", className: "absolute top-1.5 right-1.5 inline-flex items-center gap-1 rounded border border-border bg-muted px-1.5 py-0.5 text-micro leading-none text-muted-foreground hover:text-foreground", onClick: () => {
36
+ void writeClipboard(collectCodeText(node)).then((ok) => setCopied(ok ? "done" : "error"));
37
+ }, children: [copied === "done" ? (_jsx(Check, { className: "size-3" })) : (_jsx(Copy, { className: "size-3" })), copied === "idle" ? null : copied === "done" ? "已复制" : "复制失败"] })] }));
38
+ }
39
+ return (_jsxs("div", { className: "relative my-2", children: [_jsx("pre", { ...props, children: children }), _jsxs("button", { type: "button", "data-cmp": "ai/code-copy", "data-state": copied, "aria-label": "\u590D\u5236\u4EE3\u7801", className: "absolute top-1.5 right-1.5 inline-flex items-center gap-1 rounded border border-border bg-muted px-1.5 py-0.5 text-micro leading-none text-muted-foreground hover:text-foreground", onClick: () => {
40
+ void writeClipboard(collectCodeText(node)).then((ok) => setCopied(ok ? "done" : "error"));
41
+ }, children: [copied === "done" ? (_jsx(Check, { className: "size-3" })) : (_jsx(Copy, { className: "size-3" })), copied === "idle" ? null : copied === "done" ? "已复制" : "复制失败"] })] }));
42
+ }
43
+ export function MessageResponse({ children, remarkPlugins, rehypePlugins, markdownComponents, className, ...props }) {
44
+ return (_jsx("div", { "data-cmp": "ai/message-response", className: cn("[&_a]:text-primary [&_a]:underline", "[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_code:not(pre code)]:rounded [&_code:not(pre code)]:bg-background [&_code:not(pre code)]:px-1 [&_code:not(pre code)]:py-0.5 [&_code:not(pre code)]:font-mono [&_code:not(pre code)]:text-[0.85em]", "[&_h1]:mt-3 [&_h1]:mb-2 [&_h1]:text-base [&_h1]:font-semibold [&_h1]:leading-snug", "[&_h2]:mt-3 [&_h2]:mb-1.5 [&_h2]:text-heading [&_h2]:font-semibold", "[&_h3]:mt-2.5 [&_h3]:mb-1.5 [&_h3]:text-sm [&_h3]:font-semibold", "[&_h4]:mt-2 [&_h4]:mb-1 [&_h4]:font-semibold", "[&_hr]:my-3 [&_hr]:border-border", "[&_li]:my-0.5", "[&_ol]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5", "[&_p]:my-1.5", "[&_pre]:my-0 [&_pre]:break-normal [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-background [&_pre]:p-2.5 [&_pre]:pr-16 [&_pre]:font-mono [&_pre]:text-xs [&_pre]:whitespace-pre", "[&_strong]:font-semibold", "[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse", "[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1", "[&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:font-semibold", "[&_ul]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5", "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className), ...props, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, ...(remarkPlugins ?? [])], rehypePlugins: rehypePlugins, components: { pre: CodeBlock, ...markdownComponents }, children: children ?? "" }) }));
45
+ }
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ export interface MessageActionBarProps {
3
+ copyText: string;
4
+ onLike?: () => void;
5
+ onDislike?: () => void;
6
+ liked?: boolean;
7
+ disliked?: boolean;
8
+ onReadAloud?: () => void;
9
+ onRetry?: () => void;
10
+ onMore?: () => void;
11
+ cost?: string;
12
+ model?: string;
13
+ timeLabel?: string;
14
+ onNotice?: (message: string) => void;
15
+ className?: string;
16
+ trailing?: ReactNode;
17
+ }
18
+ export declare function MessageActionBar({ copyText, onLike, onDislike, liked, disliked, onReadAloud, onRetry, onMore, cost, model, timeLabel, onNotice, className, trailing, }: MessageActionBarProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Copy, MoreHorizontal, RefreshCw, ThumbsDown, ThumbsUp, Volume2, } from "lucide-react";
3
+ import { writeClipboard } from "../../utils/clipboard";
4
+ import { cn } from "../../utils/cn";
5
+ function IconBtn({ label, active, onClick, children, }) {
6
+ return (_jsx("button", { type: "button", "data-cmp": "ac/msg-action", "data-action": label, "data-state": active ? "active" : "idle", "aria-label": label, title: label, className: cn("inline-grid size-6 place-items-center rounded text-muted-foreground hover:bg-secondary hover:text-foreground", active && "text-foreground"), onClick: onClick, children: children }));
7
+ }
8
+ export function MessageActionBar({ copyText, onLike, onDislike, liked = false, disliked = false, onReadAloud, onRetry, onMore, cost, model, timeLabel, onNotice, className, trailing, }) {
9
+ return (_jsxs("div", { className: cn("ac-msg-actions", className), "data-cmp": "ac/msg-actions", "data-id": "msg-actions", children: [_jsx(IconBtn, { label: "\u590D\u5236", onClick: () => {
10
+ void writeClipboard(copyText).then((ok) => {
11
+ if (!ok)
12
+ onNotice?.("复制失败,请手动选择文本");
13
+ });
14
+ }, children: _jsx(Copy, { className: "size-3.5" }) }), onLike ? (_jsx(IconBtn, { label: "\u70B9\u8D5E", active: liked, onClick: onLike, children: _jsx(ThumbsUp, { className: "size-3.5" }) })) : null, onDislike ? (_jsx(IconBtn, { label: "\u70B9\u8E29", active: disliked, onClick: onDislike, children: _jsx(ThumbsDown, { className: "size-3.5" }) })) : null, onReadAloud ? (_jsx(IconBtn, { label: "\u6717\u8BFB", onClick: onReadAloud, children: _jsx(Volume2, { className: "size-3.5" }) })) : null, onRetry ? (_jsx(IconBtn, { label: "\u91CD\u65B0\u751F\u6210", onClick: onRetry, children: _jsx(RefreshCw, { className: "size-3.5" }) })) : null, onMore ? (_jsx(IconBtn, { label: "\u66F4\u591A", onClick: onMore, children: _jsx(MoreHorizontal, { className: "size-3.5" }) })) : null, trailing, cost || model || timeLabel ? (_jsxs("span", { className: "ac-msg-actions__meta", children: [cost ? _jsx("span", { "data-cmp": "ac/msg-cost", children: cost }) : null, model ? _jsx("span", { "data-cmp": "ac/msg-model", children: model }) : null, timeLabel ? _jsx("span", { children: timeLabel }) : null] })) : null] }));
15
+ }
@@ -0,0 +1,13 @@
1
+ export interface MessageArtifact {
2
+ id: string;
3
+ name: string;
4
+ size?: string;
5
+ }
6
+ export interface MessageArtifactsProps {
7
+ items: readonly MessageArtifact[];
8
+ total?: number;
9
+ onOpen?: (artifact: MessageArtifact) => void;
10
+ onOpenAll?: () => void;
11
+ className?: string;
12
+ }
13
+ export declare function MessageArtifacts({ items, total, onOpen, onOpenAll, className, }: MessageArtifactsProps): import("react").JSX.Element | null;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FileText } from "lucide-react";
3
+ import { cn } from "../../utils/cn";
4
+ export function MessageArtifacts({ items, total, onOpen, onOpenAll, className, }) {
5
+ if (items.length === 0)
6
+ return null;
7
+ const count = total ?? items.length;
8
+ return (_jsxs("div", { className: cn("ac-artifacts", className), "data-cmp": "ac/msg-artifacts", "data-id": "msg-artifacts", "data-count": count, children: [items.map((item) => (_jsxs("button", { type: "button", "data-cmp": "ac/artifact-chip", "data-artifact": item.id, className: "ac-artifacts__chip", disabled: onOpen === undefined, title: onOpen ? `打开 ${item.name}` : item.name, onClick: () => onOpen?.(item), children: [_jsx(FileText, { className: "ac-artifacts__icon" }), _jsx("span", { className: "ac-artifacts__name", children: item.name }), item.size ? (_jsx("span", { className: "ac-artifacts__size", children: item.size })) : null] }, item.id))), onOpenAll ? (_jsxs("button", { type: "button", "data-cmp": "ac/artifact-all", className: "ac-artifacts__all", onClick: onOpenAll, children: ["\u67E5\u770B\u6240\u6709\u4EA7\u7269 (", count, ")"] })) : null] }));
9
+ }
@@ -0,0 +1,5 @@
1
+ import type { Attachment } from "../../types/message";
2
+ export interface MessageAttachmentsProps {
3
+ attachments: Attachment[];
4
+ }
5
+ export declare function MessageAttachments({ attachments }: MessageAttachmentsProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Dialog } from "../../primitives/Dialog";
4
+ import { attachmentColumns, attachmentSrc, } from "../../domain/attachments";
5
+ function labelOf(attachment, index) {
6
+ const name = attachment.filename?.trim();
7
+ return name && name.length > 0 ? name : `图片 ${index + 1}`;
8
+ }
9
+ function AttachmentTile({ attachment, index, onOpen, }) {
10
+ const isVideo = attachment.media_type.startsWith("video/");
11
+ const label = labelOf(attachment, index);
12
+ const [state, setState] = useState("loading");
13
+ if (isVideo) {
14
+ return (_jsx("video", { className: "ac-attach__img", "data-cmp": "ac/attachment-video", src: attachmentSrc(attachment), controls: true, preload: "metadata", "aria-label": label }));
15
+ }
16
+ return (_jsxs("button", { type: "button", className: "ac-attach__tile", "data-cmp": "ac/attachment-tile", "data-state": state, "aria-label": `查看图片 ${index + 1}`, disabled: state === "error", onClick: () => onOpen(index), children: [state !== "ready" ? (_jsx("span", { className: "ac-attach__ph", children: state === "loading" ? "加载中…" : "图片加载失败" })) : null, _jsx("img", { className: "ac-attach__img", src: attachmentSrc(attachment), alt: label, onLoad: () => setState("ready"), onError: () => setState("error") })] }));
17
+ }
18
+ export function MessageAttachments({ attachments }) {
19
+ const [active, setActive] = useState(null);
20
+ const count = attachments.length;
21
+ const cols = attachmentColumns(count);
22
+ const current = active !== null ? attachments[active] : undefined;
23
+ const isVideo = current?.media_type.startsWith("video/") ?? false;
24
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "ac-attach__grid", "data-cmp": "ac/msg-attachments", "data-count": count, "data-cols": cols, "aria-label": "\u56FE\u7247\u9644\u4EF6", children: attachments.map((attachment, index) => (_jsx(AttachmentTile, { attachment: attachment, index: index, onOpen: setActive }, `${index}-${attachment.filename ?? attachment.media_type}`))) }), _jsx(Dialog, { open: active !== null && !isVideo, onOpenChange: (open) => {
25
+ if (!open)
26
+ setActive(null);
27
+ }, title: current ? labelOf(current, active ?? 0) : "图片预览", className: "ac-attach__preview", children: current ? (_jsx("img", { className: "ac-attach__preview-img", src: attachmentSrc(current), alt: labelOf(current, active ?? 0) })) : null })] }));
28
+ }
@@ -0,0 +1,11 @@
1
+ import { cn } from "../../utils/cn";
2
+ import type { Agent } from "../../types/agent";
3
+ import type { ChatMessage } from "../../types/message";
4
+ export interface MessageBubbleProps {
5
+ message: ChatMessage;
6
+ agent?: Agent;
7
+ mentionedAgents?: Agent[];
8
+ onOpenAgent?: (id: string) => void;
9
+ }
10
+ export declare function MessageBubble({ message, agent, mentionedAgents, onOpenAgent, }: MessageBubbleProps): import("react").JSX.Element;
11
+ export { cn };
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { cn } from "../../utils/cn";
4
+ import { mentionTargetsFromAgents, remarkMentions, } from "../../domain/mention";
5
+ import { MessageResponse } from "./Message";
6
+ import { MessageAttachments } from "./MessageAttachments";
7
+ function createMentionComponents(targetById, onOpenAgent) {
8
+ const Mention = ({ agent, name }) => {
9
+ const target = agent ? targetById.get(agent) : undefined;
10
+ const label = `@${name ?? ""}`;
11
+ if (target && onOpenAgent) {
12
+ return (_jsx("button", { type: "button", className: "ac-bubble__mention", "data-cmp": "ac/mention-chip", "aria-label": `查看 ${target.name} 资料`, onClick: () => onOpenAgent(target.id), children: label }));
13
+ }
14
+ return _jsx("span", { className: "ac-bubble__mention", children: label });
15
+ };
16
+ return { mention: Mention };
17
+ }
18
+ export function MessageBubble({ message, agent, mentionedAgents = [], onOpenAgent, }) {
19
+ const targets = useMemo(() => mentionTargetsFromAgents(mentionedAgents), [mentionedAgents]);
20
+ const targetById = useMemo(() => {
21
+ const map = new Map();
22
+ for (const t of targets)
23
+ map.set(t.id, t);
24
+ return map;
25
+ }, [targets]);
26
+ const components = useMemo(() => createMentionComponents(targetById, onOpenAgent), [targetById, onOpenAgent]);
27
+ const hit = (message.mentions?.length ?? 0) > 0;
28
+ const attachments = message.attachments ?? [];
29
+ return (_jsxs("div", { className: "ac-bubble", "data-cmp": "ac/bubble", "data-id": "msg-bubble", "data-ctx": "ac.message", "data-message": message.id, "data-source-id": message.sourceId, "data-msg-role": message.role, "data-agent": agent?.id ?? "none", "data-variant": hit ? "mention" : "plain", "data-state": message.state ?? "done", children: [attachments.length > 0 ? (_jsx(MessageAttachments, { attachments: attachments })) : null, _jsx(MessageResponse, { remarkPlugins: [remarkMentions(targets)], markdownComponents: components, children: message.text })] }));
30
+ }
31
+ export { cn };
@@ -0,0 +1,20 @@
1
+ import type { Agent } from "../../types/agent";
2
+ import type { ChatMessage } from "../../types/message";
3
+ import { type MessageActionBarProps } from "./MessageActionBar";
4
+ import { type MessageArtifact } from "./MessageArtifacts";
5
+ export interface MessageRowProps {
6
+ message: ChatMessage;
7
+ agentById: Record<string, Agent>;
8
+ onOpenAgent?: (id: string) => void;
9
+ selectMode?: boolean;
10
+ selected?: boolean;
11
+ onToggleSelect?: (messageId: string) => void;
12
+ actions?: Omit<MessageActionBarProps, "copyText"> & {
13
+ copyText?: string;
14
+ };
15
+ artifacts?: readonly MessageArtifact[];
16
+ artifactTotal?: number;
17
+ onOpenArtifact?: (artifact: MessageArtifact) => void;
18
+ onOpenAllArtifacts?: () => void;
19
+ }
20
+ export declare function MessageRow({ message, agentById, onOpenAgent, selectMode, selected, onToggleSelect, actions, artifacts, artifactTotal, onOpenArtifact, onOpenAllArtifacts, }: MessageRowProps): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Check } from "lucide-react";
3
+ import { attributionOf } from "../../domain/attribution";
4
+ import { orderMessageBlocks, promoteMemoryFirst, } from "../../domain/message-blocks";
5
+ import { ToolGroup } from "../tool/ToolGroup";
6
+ import { LatticeLoader } from "../status/LatticeLoader";
7
+ import { MessageBubble } from "./MessageBubble";
8
+ import { MessageActionBar } from "./MessageActionBar";
9
+ import { MessageArtifacts } from "./MessageArtifacts";
10
+ import { PartView } from "./PartView";
11
+ function AgentAvatar({ agent, kind, badge, }) {
12
+ return (_jsx("span", { className: "ac-avatar", "data-cmp": "ac/avatar", "data-kind": kind, "data-size": "sm", "aria-hidden": "true", title: agent?.name, children: agent ? agent.badge || agent.name.slice(0, 1) : badge }));
13
+ }
14
+ export function MessageRow({ message, agentById, onOpenAgent, selectMode = false, selected = false, onToggleSelect, actions, artifacts, artifactTotal, onOpenArtifact, onOpenAllArtifacts, }) {
15
+ const parts = message.parts ?? [];
16
+ const self = message.role === "user";
17
+ const agent = message.agentId ? agentById[message.agentId] : undefined;
18
+ const attribution = attributionOf(message, agentById);
19
+ const mentioned = (message.mentions ?? [])
20
+ .map((id) => agentById[id])
21
+ .filter((a) => a !== undefined);
22
+ const hasText = message.text.trim() !== "";
23
+ const hasAttachments = (message.attachments?.length ?? 0) > 0;
24
+ const showBubble = (hasText || hasAttachments) &&
25
+ (!self ? message.role !== "tool" || parts.length === 0 : true);
26
+ const blocks = promoteMemoryFirst(orderMessageBlocks(message, showBubble));
27
+ const align = self ? "end" : "start";
28
+ const handleSlotClick = () => {
29
+ if (selectMode)
30
+ onToggleSelect?.(message.id);
31
+ };
32
+ const handleSlotKey = (e) => {
33
+ if (!selectMode)
34
+ return;
35
+ if (e.key === "Enter" || e.key === " ") {
36
+ e.preventDefault();
37
+ onToggleSelect?.(message.id);
38
+ }
39
+ };
40
+ return (_jsxs("div", { className: "ac-msg-slot", "data-cmp": "ac/msg-slot", "data-id": "msg-row", "data-ctx": "ac.message", "data-message": message.id, "data-source-id": message.sourceId, "data-msg-role": message.role, "data-align": align, "data-state": message.state ?? "done", "data-select-mode": selectMode ? "true" : "false", "data-selected": selected ? "true" : "false", role: selectMode ? "checkbox" : undefined, "aria-checked": selectMode ? selected : undefined, "aria-label": selectMode ? `${selected ? "取消选择" : "选择"}这条消息` : undefined, tabIndex: selectMode ? 0 : undefined, onClick: handleSlotClick, onKeyDown: handleSlotKey, children: [selectMode ? (_jsx("span", { className: "ac-select-box", "data-cmp": "ac/select-box", "data-state": selected ? "checked" : "idle", "aria-hidden": "true", children: selected ? _jsx(Check, { className: "size-3" }) : null })) : null, _jsxs("div", { className: "ac-msg", "data-cmp": "ac/msg", "data-align": align, children: [self || attribution.agent ? (_jsx(AgentAvatar, { agent: self ? undefined : attribution.agent, kind: self ? "self" : "agent", badge: attribution.badge })) : (_jsx(AgentAvatar, { kind: "agent", badge: attribution.badge })), _jsxs("div", { className: "ac-msg__col", children: [_jsxs("div", { className: "ac-msg__meta", children: [!self ? (_jsx("span", { className: "ac-msg__who", children: attribution.name })) : null, self ? _jsx("span", { children: "\u6211" }) : _jsx("span", { children: agent?.role ?? "" }), _jsx("span", { children: message.timeLabel })] }), blocks.map((block) => {
41
+ if (block.type === "text") {
42
+ return (_jsx(MessageBubble, { message: message, agent: agent, mentionedAgents: mentioned, onOpenAgent: onOpenAgent }, block.key));
43
+ }
44
+ if (block.type === "tool-group") {
45
+ return (_jsx(ToolGroup, { parts: block.parts, renderPart: (item) => _jsx(PartView, { part: item.part }, item.key) }, block.key));
46
+ }
47
+ return _jsx(PartView, { part: block.part }, block.key);
48
+ }), message.state === "streaming" && !hasText ? (_jsx(LatticeLoader, { status: "working", label: `${agent?.name ?? "助手"}工作中`, "data-id": "lattice-loader" })) : null, artifacts && artifacts.length > 0 ? (_jsx(MessageArtifacts, { items: artifacts, total: artifactTotal, onOpen: onOpenArtifact, onOpenAll: onOpenAllArtifacts })) : null, actions ? (_jsx(MessageActionBar, { ...actions, copyText: actions.copyText ?? message.text })) : null] })] })] }));
49
+ }
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Agent } from "../../types/agent";
3
+ import type { ChatMessage, ConversationStatus, PendingPermission } from "../../types/index";
4
+ export interface MessageStreamProps {
5
+ messages: ChatMessage[];
6
+ members: Agent[];
7
+ status: ConversationStatus;
8
+ onOpenAgent?: (id: string) => void;
9
+ permissions?: PendingPermission[];
10
+ onRespondPermission?: (permissionId: string, approved: boolean, remembered: boolean) => void;
11
+ selectMode?: boolean;
12
+ selectedIds?: ReadonlySet<string>;
13
+ onToggleSelect?: (messageId: string) => void;
14
+ messageActions?: (message: ChatMessage) => import("./MessageRow").MessageRowProps["actions"];
15
+ messageArtifacts?: (message: ChatMessage) => {
16
+ items: readonly import("./MessageArtifacts").MessageArtifact[];
17
+ total?: number;
18
+ onOpen?: (a: import("./MessageArtifacts").MessageArtifact) => void;
19
+ onOpenAll?: () => void;
20
+ } | undefined;
21
+ renderNote?: (part: ReactNode) => ReactNode;
22
+ }
23
+ export declare function MessageStream({ messages, members, status, onOpenAgent, permissions, onRespondPermission, selectMode, selectedIds, onToggleSelect, messageActions, messageArtifacts, }: MessageStreamProps): import("react").JSX.Element;