@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.
- package/LICENSE +21 -0
- package/README.md +100 -2
- package/lib/adapters/event-bus.d.ts +80 -0
- package/lib/adapters/event-bus.js +73 -0
- package/lib/adapters/index.d.ts +3 -0
- package/lib/adapters/index.js +3 -0
- package/lib/adapters/labels.d.ts +6 -0
- package/lib/adapters/labels.js +17 -0
- package/lib/adapters/stream-bridge.d.ts +9 -0
- package/lib/adapters/stream-bridge.js +7 -0
- package/lib/blocks/cards/AskCard.d.ts +8 -0
- package/lib/blocks/cards/AskCard.js +97 -0
- package/lib/blocks/cards/CompactionCard.d.ts +5 -0
- package/lib/blocks/cards/CompactionCard.js +5 -0
- package/lib/blocks/cards/ContextUsage.d.ts +14 -0
- package/lib/blocks/cards/ContextUsage.js +136 -0
- package/lib/blocks/cards/DisplayCard.d.ts +7 -0
- package/lib/blocks/cards/DisplayCard.js +27 -0
- package/lib/blocks/cards/MemoryPill.d.ts +5 -0
- package/lib/blocks/cards/MemoryPill.js +5 -0
- package/lib/blocks/cards/OutputCard.d.ts +5 -0
- package/lib/blocks/cards/OutputCard.js +13 -0
- package/lib/blocks/cards/PermissionCard.d.ts +21 -0
- package/lib/blocks/cards/PermissionCard.js +26 -0
- package/lib/blocks/cards/SpringCheck.d.ts +9 -0
- package/lib/blocks/cards/SpringCheck.js +17 -0
- package/lib/blocks/cards/TodoCard.d.ts +19 -0
- package/lib/blocks/cards/TodoCard.js +28 -0
- package/lib/blocks/cards/index.d.ts +10 -0
- package/lib/blocks/cards/index.js +10 -0
- package/lib/blocks/composer/Composer.d.ts +66 -0
- package/lib/blocks/composer/Composer.js +73 -0
- package/lib/blocks/composer/ComposerAttachments.d.ts +17 -0
- package/lib/blocks/composer/ComposerAttachments.js +10 -0
- package/lib/blocks/composer/ComposerGrip.d.ts +1 -0
- package/lib/blocks/composer/ComposerGrip.js +39 -0
- package/lib/blocks/composer/ComposerInput.d.ts +18 -0
- package/lib/blocks/composer/ComposerInput.js +60 -0
- package/lib/blocks/composer/ComposerMenus.d.ts +10 -0
- package/lib/blocks/composer/ComposerMenus.js +78 -0
- package/lib/blocks/composer/ComposerToolbar.d.ts +27 -0
- package/lib/blocks/composer/ComposerToolbar.js +16 -0
- package/lib/blocks/composer/MentionPicker.d.ts +13 -0
- package/lib/blocks/composer/MentionPicker.js +71 -0
- package/lib/blocks/composer/PromptPop.d.ts +7 -0
- package/lib/blocks/composer/PromptPop.js +40 -0
- package/lib/blocks/composer/QueuedPrompts.d.ts +14 -0
- package/lib/blocks/composer/QueuedPrompts.js +21 -0
- package/lib/blocks/composer/SlingButton.d.ts +10 -0
- package/lib/blocks/composer/SlingButton.js +112 -0
- package/lib/blocks/composer/index.d.ts +7 -0
- package/lib/blocks/composer/index.js +1 -0
- package/lib/blocks/conversation/ChatEmptyScene.d.ts +4 -0
- package/lib/blocks/conversation/ChatEmptyScene.js +21 -0
- package/lib/blocks/conversation/ChatHeader.d.ts +13 -0
- package/lib/blocks/conversation/ChatHeader.js +20 -0
- package/lib/blocks/conversation/ChatPane.d.ts +90 -0
- package/lib/blocks/conversation/ChatPane.js +60 -0
- package/lib/blocks/conversation/Conversation.d.ts +11 -0
- package/lib/blocks/conversation/Conversation.js +41 -0
- package/lib/blocks/conversation/LineSidebar.d.ts +14 -0
- package/lib/blocks/conversation/LineSidebar.js +42 -0
- package/lib/blocks/conversation/index.d.ts +5 -0
- package/lib/blocks/conversation/index.js +5 -0
- package/lib/blocks/index.d.ts +9 -0
- package/lib/blocks/index.js +9 -0
- package/lib/blocks/message/DayDivider.d.ts +10 -0
- package/lib/blocks/message/DayDivider.js +29 -0
- package/lib/blocks/message/DiffView.d.ts +5 -0
- package/lib/blocks/message/DiffView.js +18 -0
- package/lib/blocks/message/MermaidDiagram.d.ts +4 -0
- package/lib/blocks/message/MermaidDiagram.js +43 -0
- package/lib/blocks/message/Message.d.ts +17 -0
- package/lib/blocks/message/Message.js +45 -0
- package/lib/blocks/message/MessageActionBar.d.ts +18 -0
- package/lib/blocks/message/MessageActionBar.js +15 -0
- package/lib/blocks/message/MessageArtifacts.d.ts +13 -0
- package/lib/blocks/message/MessageArtifacts.js +9 -0
- package/lib/blocks/message/MessageAttachments.d.ts +5 -0
- package/lib/blocks/message/MessageAttachments.js +28 -0
- package/lib/blocks/message/MessageBubble.d.ts +11 -0
- package/lib/blocks/message/MessageBubble.js +31 -0
- package/lib/blocks/message/MessageRow.d.ts +20 -0
- package/lib/blocks/message/MessageRow.js +49 -0
- package/lib/blocks/message/MessageStream.d.ts +23 -0
- package/lib/blocks/message/MessageStream.js +80 -0
- package/lib/blocks/message/MessageTrace.d.ts +16 -0
- package/lib/blocks/message/MessageTrace.js +39 -0
- package/lib/blocks/message/PartView.d.ts +10 -0
- package/lib/blocks/message/PartView.js +29 -0
- package/lib/blocks/message/code-text.d.ts +15 -0
- package/lib/blocks/message/code-text.js +17 -0
- package/lib/blocks/message/index.d.ts +12 -0
- package/lib/blocks/message/index.js +12 -0
- package/lib/blocks/reasoning/Reasoning.d.ts +7 -0
- package/lib/blocks/reasoning/Reasoning.js +26 -0
- package/lib/blocks/reasoning/ReasoningBlock.d.ts +6 -0
- package/lib/blocks/reasoning/ReasoningBlock.js +5 -0
- package/lib/blocks/reasoning/index.d.ts +2 -0
- package/lib/blocks/reasoning/index.js +2 -0
- package/lib/blocks/selection/SelectionActions.d.ts +12 -0
- package/lib/blocks/selection/SelectionActions.js +29 -0
- package/lib/blocks/selection/SelectionBar.d.ts +10 -0
- package/lib/blocks/selection/SelectionBar.js +6 -0
- package/lib/blocks/selection/index.d.ts +2 -0
- package/lib/blocks/selection/index.js +2 -0
- package/lib/blocks/settings/DisplaySettingsPanel.d.ts +5 -0
- package/lib/blocks/settings/DisplaySettingsPanel.js +33 -0
- package/lib/blocks/settings/SquishSwitch.d.ts +8 -0
- package/lib/blocks/settings/SquishSwitch.js +21 -0
- package/lib/blocks/settings/index.d.ts +2 -0
- package/lib/blocks/settings/index.js +2 -0
- package/lib/blocks/status/LatticeLoader.d.ts +12 -0
- package/lib/blocks/status/LatticeLoader.js +22 -0
- package/lib/blocks/status/TypingIndicator.d.ts +5 -0
- package/lib/blocks/status/TypingIndicator.js +5 -0
- package/lib/blocks/status/index.d.ts +2 -0
- package/lib/blocks/status/index.js +2 -0
- package/lib/blocks/tool/SandboxCard.d.ts +5 -0
- package/lib/blocks/tool/SandboxCard.js +10 -0
- package/lib/blocks/tool/ToolCard.d.ts +10 -0
- package/lib/blocks/tool/ToolCard.js +32 -0
- package/lib/blocks/tool/ToolGroup.d.ts +6 -0
- package/lib/blocks/tool/ToolGroup.js +23 -0
- package/lib/blocks/tool/exec-envelope.d.ts +7 -0
- package/lib/blocks/tool/exec-envelope.js +43 -0
- package/lib/blocks/tool/index.d.ts +4 -0
- package/lib/blocks/tool/index.js +4 -0
- package/lib/domain/ask.d.ts +14 -0
- package/lib/domain/ask.js +50 -0
- package/lib/domain/attachments.d.ts +16 -0
- package/lib/domain/attachments.js +126 -0
- package/lib/domain/attribution.d.ts +4 -0
- package/lib/domain/attribution.js +11 -0
- package/lib/domain/composer-palette.d.ts +15 -0
- package/lib/domain/composer-palette.js +131 -0
- package/lib/domain/index.d.ts +12 -0
- package/lib/domain/index.js +12 -0
- package/lib/domain/mention.d.ts +17 -0
- package/lib/domain/mention.js +95 -0
- package/lib/domain/message-blocks.d.ts +29 -0
- package/lib/domain/message-blocks.js +63 -0
- package/lib/domain/message-menu.d.ts +4 -0
- package/lib/domain/message-menu.js +46 -0
- package/lib/domain/queue.d.ts +23 -0
- package/lib/domain/queue.js +52 -0
- package/lib/domain/reducer.d.ts +137 -0
- package/lib/domain/reducer.js +442 -0
- package/lib/domain/selection-actions.d.ts +13 -0
- package/lib/domain/selection-actions.js +56 -0
- package/lib/domain/selection-gates.d.ts +6 -0
- package/lib/domain/selection-gates.js +36 -0
- package/lib/domain/slash-commands.d.ts +16 -0
- package/lib/domain/slash-commands.js +142 -0
- package/lib/hooks/index.d.ts +8 -0
- package/lib/hooks/index.js +8 -0
- package/lib/hooks/useComposerPalette.d.ts +35 -0
- package/lib/hooks/useComposerPalette.js +120 -0
- package/lib/hooks/useComposerSubmit.d.ts +31 -0
- package/lib/hooks/useComposerSubmit.js +135 -0
- package/lib/hooks/useInputHistory.d.ts +14 -0
- package/lib/hooks/useInputHistory.js +52 -0
- package/lib/hooks/usePendingAsk.d.ts +11 -0
- package/lib/hooks/usePendingAsk.js +33 -0
- package/lib/hooks/usePendingPermissions.d.ts +10 -0
- package/lib/hooks/usePendingPermissions.js +26 -0
- package/lib/hooks/useQueuedPrompts.d.ts +12 -0
- package/lib/hooks/useQueuedPrompts.js +91 -0
- package/lib/hooks/useSelectionPop.d.ts +13 -0
- package/lib/hooks/useSelectionPop.js +130 -0
- package/lib/hooks/useStreamBridge.d.ts +1 -0
- package/lib/hooks/useStreamBridge.js +23 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +10 -0
- package/lib/primitives/Badge.d.ts +7 -0
- package/lib/primitives/Badge.js +13 -0
- package/lib/primitives/Button.d.ts +9 -0
- package/lib/primitives/Button.js +21 -0
- package/lib/primitives/Collapsible.d.ts +12 -0
- package/lib/primitives/Collapsible.js +14 -0
- package/lib/primitives/Dialog.d.ts +10 -0
- package/lib/primitives/Dialog.js +19 -0
- package/lib/primitives/DropdownMenu.d.ts +28 -0
- package/lib/primitives/DropdownMenu.js +37 -0
- package/lib/primitives/Empty.d.ts +8 -0
- package/lib/primitives/Empty.js +6 -0
- package/lib/primitives/Input.d.ts +3 -0
- package/lib/primitives/Input.js +6 -0
- package/lib/primitives/Kbd.d.ts +2 -0
- package/lib/primitives/Kbd.js +5 -0
- package/lib/primitives/Popover.d.ts +15 -0
- package/lib/primitives/Popover.js +32 -0
- package/lib/primitives/ScrollArea.d.ts +5 -0
- package/lib/primitives/ScrollArea.js +5 -0
- package/lib/primitives/Separator.d.ts +5 -0
- package/lib/primitives/Separator.js +5 -0
- package/lib/primitives/Spinner.d.ts +5 -0
- package/lib/primitives/Spinner.js +5 -0
- package/lib/primitives/Textarea.d.ts +3 -0
- package/lib/primitives/Textarea.js +6 -0
- package/lib/primitives/Tooltip.d.ts +8 -0
- package/lib/primitives/Tooltip.js +7 -0
- package/lib/primitives/index.d.ts +14 -0
- package/lib/primitives/index.js +14 -0
- package/lib/reactbits/Motion.d.ts +24 -0
- package/lib/reactbits/Motion.js +52 -0
- package/lib/reactbits/index.d.ts +1 -0
- package/lib/reactbits/index.js +1 -0
- package/lib/stores/chat-store.d.ts +33 -0
- package/lib/stores/chat-store.js +93 -0
- package/lib/stores/display-store.d.ts +15 -0
- package/lib/stores/display-store.js +116 -0
- package/lib/stores/index.d.ts +3 -0
- package/lib/stores/index.js +3 -0
- package/lib/stores/theme-store.d.ts +8 -0
- package/lib/stores/theme-store.js +29 -0
- package/lib/tokens/index.d.ts +21 -0
- package/lib/tokens/index.js +66 -0
- package/lib/types/agent.d.ts +62 -0
- package/lib/types/agent.js +1 -0
- package/lib/types/callbacks.d.ts +55 -0
- package/lib/types/callbacks.js +1 -0
- package/lib/types/composer.d.ts +116 -0
- package/lib/types/composer.js +1 -0
- package/lib/types/conversation.d.ts +54 -0
- package/lib/types/conversation.js +1 -0
- package/lib/types/display.d.ts +25 -0
- package/lib/types/display.js +40 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.js +8 -0
- package/lib/types/message.d.ts +100 -0
- package/lib/types/message.js +1 -0
- package/lib/types/selection.d.ts +60 -0
- package/lib/types/selection.js +1 -0
- package/lib/types/theme.d.ts +100 -0
- package/lib/types/theme.js +1 -0
- package/lib/utils/clipboard.d.ts +4 -0
- package/lib/utils/clipboard.js +41 -0
- package/lib/utils/cn.d.ts +2 -0
- package/lib/utils/cn.js +14 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/storage.d.ts +11 -0
- package/lib/utils/storage.js +48 -0
- package/package.json +93 -4
- package/src/styles/blocks.css +2439 -0
- package/src/styles/index.css +3 -0
- 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,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,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,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,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;
|