@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,27 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ContextUsageSegment, ContextWindowPreset } from "../../types/message";
|
|
3
|
+
import { type ComposerModelSwitch, type PermissionModeSwitch } from "./ComposerMenus";
|
|
4
|
+
export interface ComposerToolbarProps {
|
|
5
|
+
picker: ReactNode;
|
|
6
|
+
permissionMode?: PermissionModeSwitch;
|
|
7
|
+
modelSwitch?: ComposerModelSwitch;
|
|
8
|
+
onManageModel?: () => void;
|
|
9
|
+
streaming: boolean;
|
|
10
|
+
canStop: boolean;
|
|
11
|
+
empty: boolean;
|
|
12
|
+
onStop?: () => void;
|
|
13
|
+
onSubmit: () => void;
|
|
14
|
+
contextUsage?: {
|
|
15
|
+
input: number;
|
|
16
|
+
output?: number;
|
|
17
|
+
window: number;
|
|
18
|
+
breakdown?: readonly ContextUsageSegment[];
|
|
19
|
+
windowPresets?: readonly ContextWindowPreset[];
|
|
20
|
+
onWindowChange?: (window: number) => void;
|
|
21
|
+
};
|
|
22
|
+
visionTag: ReactNode;
|
|
23
|
+
onMic?: () => void;
|
|
24
|
+
micState?: "idle" | "listening";
|
|
25
|
+
trailing?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare function ComposerToolbar({ picker, permissionMode, modelSwitch, onManageModel, streaming, canStop, empty, onStop, onSubmit, contextUsage, visionTag, onMic, micState, trailing, }: ComposerToolbarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Mic, Square } from "lucide-react";
|
|
3
|
+
import { ContextUsage } from "../cards/ContextUsage";
|
|
4
|
+
import { SlingButton } from "./SlingButton";
|
|
5
|
+
import { ComposerModelChip, ComposerPermChip, } from "./ComposerMenus";
|
|
6
|
+
export function ComposerToolbar({ picker, permissionMode, modelSwitch, onManageModel, streaming, canStop, empty, onStop, onSubmit, contextUsage, visionTag, onMic, micState = "idle", trailing, }) {
|
|
7
|
+
return (_jsxs("div", { className: "ac-composer__bar", children: [_jsxs("div", { className: "ac-composer__left", children: [picker, permissionMode ? (_jsx(ComposerPermChip, { permissionMode: permissionMode })) : null] }), _jsx("span", { className: "ac-composer__count", children: streaming
|
|
8
|
+
? "Enter 进入待发队列 · Shift+Enter 换行"
|
|
9
|
+
: empty
|
|
10
|
+
? ""
|
|
11
|
+
: "Enter 发送 · Shift+Enter 换行" }), _jsxs("div", { className: "ac-composer__right", children: [canStop ? (_jsx("button", { type: "button", className: "ac-composer__stop-ring", "aria-label": "\u505C\u6B62\u751F\u6210", title: "\u505C\u6B62\u751F\u6210", onClick: onStop, children: _jsx(Square, { className: "size-3" }) })) : null, modelSwitch ? (_jsx(ComposerModelChip, { modelSwitch: modelSwitch, onManage: onManageModel })) : null, contextUsage ? _jsx(ContextUsage, { ...contextUsage }) : null, visionTag, trailing, onMic ? (_jsx("button", { type: "button", className: "ac-composer__mic", "data-state": micState, "aria-label": micState === "listening" ? "停止录音" : "语音输入", title: micState === "listening" ? "停止录音" : "语音输入", onClick: onMic, children: _jsx(Mic, { className: "size-4" }) })) : null, _jsx(SlingButton, { onSend: canStop ? () => onStop?.() : onSubmit, disabled: !canStop && empty, ariaLabel: canStop
|
|
12
|
+
? "停止生成"
|
|
13
|
+
: empty
|
|
14
|
+
? "输入消息后发送(Enter)"
|
|
15
|
+
: "发送消息(Enter · Shift+Enter 换行)", children: canStop ? _jsx(Square, { className: "size-4" }) : undefined })] })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComposerTrigger, PaletteItem } from "../../types/composer";
|
|
2
|
+
export interface MentionPickerProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
trigger: ComposerTrigger | null;
|
|
5
|
+
selectedAgent?: {
|
|
6
|
+
name: string;
|
|
7
|
+
} | null;
|
|
8
|
+
items: PaletteItem[];
|
|
9
|
+
index: number;
|
|
10
|
+
onMove: (delta: number) => void;
|
|
11
|
+
onPick: (item: PaletteItem) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function MentionPicker({ open, trigger, selectedAgent, items, index, onMove, onPick, }: MentionPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef } from "react";
|
|
3
|
+
import { File, Folder, Users } from "lucide-react";
|
|
4
|
+
const SOURCE_LABEL = {
|
|
5
|
+
builtin: "内置",
|
|
6
|
+
skill: "技能",
|
|
7
|
+
custom: "自定义",
|
|
8
|
+
};
|
|
9
|
+
export function MentionPicker({ open, trigger, selectedAgent, items, index, onMove, onPick, }) {
|
|
10
|
+
const uid = useId();
|
|
11
|
+
const rootRef = useRef(null);
|
|
12
|
+
const shownRef = useRef(false);
|
|
13
|
+
const skillMode = Boolean(selectedAgent);
|
|
14
|
+
const mentionMode = trigger === "@";
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (open && !shownRef.current) {
|
|
17
|
+
shownRef.current = true;
|
|
18
|
+
}
|
|
19
|
+
if (!open)
|
|
20
|
+
shownRef.current = false;
|
|
21
|
+
if (!open)
|
|
22
|
+
return;
|
|
23
|
+
const el = rootRef.current?.querySelector(`[data-opt="${index}"]`);
|
|
24
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
25
|
+
}, [open, index]);
|
|
26
|
+
const optId = (i) => `${uid}-opt-${i}`;
|
|
27
|
+
const optionProps = (i) => ({
|
|
28
|
+
id: optId(i),
|
|
29
|
+
role: "option",
|
|
30
|
+
"data-opt": i,
|
|
31
|
+
"data-state": i === index ? "active" : "idle",
|
|
32
|
+
"aria-selected": i === index,
|
|
33
|
+
"aria-current": i === index,
|
|
34
|
+
onMouseEnter: () => onMove(i - index),
|
|
35
|
+
onClick: () => onPick(items[i]),
|
|
36
|
+
});
|
|
37
|
+
const mention = items.filter((i) => i.kind === "all" || i.kind === "agent");
|
|
38
|
+
const commands = items.filter((i) => i.kind === "command");
|
|
39
|
+
const files = items.filter((i) => i.kind === "file");
|
|
40
|
+
const gi = (item) => items.indexOf(item);
|
|
41
|
+
return (_jsx("div", { ref: rootRef, className: "ac-mention shadow-lg", "data-cmp": "ac/mention-menu", "data-trigger": trigger ?? undefined, "data-state": open ? "open" : "closed", hidden: !open, onMouseDown: (e) => e.preventDefault(), children: open ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ac-mention__list", role: "listbox", "aria-label": skillMode
|
|
42
|
+
? `@${selectedAgent?.name ?? ""} 的技能`
|
|
43
|
+
: mentionMode
|
|
44
|
+
? "提及成员与文件"
|
|
45
|
+
: "斜杠命令", "aria-activedescendant": optId(index), children: [skillMode ? (_jsxs("div", { role: "group", "aria-labelledby": `${uid}-g-skill`, children: [_jsxs("div", { className: "ac-mention__label", id: `${uid}-g-skill`, children: ["@", selectedAgent?.name, " \u7684\u6280\u80FD \u00B7 Esc \u8FD4\u56DE"] }), commands.map((item) => {
|
|
46
|
+
if (item.kind !== "command")
|
|
47
|
+
return null;
|
|
48
|
+
const Icon = item.command.icon;
|
|
49
|
+
return (_jsxs("button", { type: "button", className: "ac-mention__cmd", "data-cmp": "ac/mention-skill-row", title: item.command.description, ...optionProps(gi(item)), children: [_jsx(Icon, { className: "ac-mention__cmd-icon", size: 16 }), _jsxs("span", { className: "ac-mention__cmd-body", children: [_jsx("span", { className: "ac-mention__cmd-label", children: item.command.label }), _jsx("span", { className: "ac-mention__cmd-desc", children: item.command.description })] }), _jsx("span", { className: "ac-mention__cmd-trigger", children: item.command.trigger }), _jsx("span", { className: "ac-mention__src", "data-src": "skill", children: "\u6280\u80FD" })] }, item.command.id));
|
|
50
|
+
})] })) : null, !skillMode && mention.length > 0 ? (_jsxs("div", { role: "group", children: [_jsx("div", { className: "ac-mention__label", children: "\u63D0\u53CA" }), mention.map((item) => {
|
|
51
|
+
if (item.kind === "all") {
|
|
52
|
+
return (_jsxs("button", { type: "button", className: "ac-mention__all", ...optionProps(gi(item)), children: [_jsx(Users, { size: 14 }), "\u6240\u6709\u4EBA"] }, "all"));
|
|
53
|
+
}
|
|
54
|
+
if (item.kind === "agent") {
|
|
55
|
+
return (_jsxs("button", { type: "button", className: "ac-mention__item", ...optionProps(gi(item)), children: [_jsx("span", { className: "ac-avatar", "data-size": "sm", "aria-hidden": "true", children: item.agent.badge || item.agent.name.slice(0, 1) }), item.agent.name] }, item.agent.id));
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
})] })) : null, !skillMode && commands.length > 0 ? (_jsxs("div", { role: "group", children: [_jsx("div", { className: "ac-mention__label", children: "\u547D\u4EE4" }), commands.map((item) => {
|
|
59
|
+
if (item.kind !== "command")
|
|
60
|
+
return null;
|
|
61
|
+
const Icon = item.command.icon;
|
|
62
|
+
return (_jsxs("button", { type: "button", className: "ac-mention__cmd", title: item.command.description, ...optionProps(gi(item)), children: [_jsx(Icon, { className: "ac-mention__cmd-icon", size: 16 }), _jsxs("span", { className: "ac-mention__cmd-body", children: [_jsx("span", { className: "ac-mention__cmd-label", children: item.command.label }), _jsx("span", { className: "ac-mention__cmd-desc", children: item.command.description })] }), _jsx("span", { className: "ac-mention__cmd-trigger", children: item.command.trigger }), _jsx("span", { className: "ac-mention__src", "data-src": item.command.source ?? "builtin", children: SOURCE_LABEL[item.command.source ?? "builtin"] })] }, item.command.id));
|
|
63
|
+
})] })) : null, !skillMode && files.length > 0 ? (_jsxs("div", { role: "group", children: [_jsx("div", { className: "ac-mention__label", children: "\u6587\u4EF6" }), files.map((item) => {
|
|
64
|
+
if (item.kind !== "file")
|
|
65
|
+
return null;
|
|
66
|
+
const Icon = item.entryKind === "dir" ? Folder : File;
|
|
67
|
+
return (_jsxs("button", { type: "button", className: "ac-mention__file", title: item.path, ...optionProps(gi(item)), children: [_jsx(Icon, { size: 14 }), _jsx("span", { className: "ac-mention__file-path", children: item.path })] }, item.path));
|
|
68
|
+
})] })) : null] }), _jsx("div", { className: "ac-mention__hint", children: skillMode
|
|
69
|
+
? "↑↓ 选择 · Tab/Enter 确认 · Esc 返回"
|
|
70
|
+
: "↑↓ 选择 · Tab/Enter 确认 · Esc 关闭" })] })) : null }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface PromptPopProps {
|
|
2
|
+
items: readonly string[];
|
|
3
|
+
onPick: (text: string) => void;
|
|
4
|
+
maxMarks?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function PromptPop({ items, onPick, maxMarks, className, }: PromptPopProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { cn } from "../../utils/cn";
|
|
4
|
+
export function PromptPop({ items, onPick, maxMarks = 8, className, }) {
|
|
5
|
+
const [open, setOpen] = useState(false);
|
|
6
|
+
const [hovered, setHovered] = useState(null);
|
|
7
|
+
const rootRef = useRef(null);
|
|
8
|
+
const closeTimer = useRef(0);
|
|
9
|
+
const marks = items.slice(0, maxMarks);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
return () => window.clearTimeout(closeTimer.current);
|
|
12
|
+
}, []);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!open)
|
|
15
|
+
return;
|
|
16
|
+
const onKey = (e) => {
|
|
17
|
+
if (e.key === "Escape")
|
|
18
|
+
setOpen(false);
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener("keydown", onKey);
|
|
21
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
22
|
+
}, [open]);
|
|
23
|
+
if (items.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
const hold = () => {
|
|
26
|
+
window.clearTimeout(closeTimer.current);
|
|
27
|
+
setOpen(true);
|
|
28
|
+
};
|
|
29
|
+
const release = () => {
|
|
30
|
+
window.clearTimeout(closeTimer.current);
|
|
31
|
+
closeTimer.current = window.setTimeout(() => {
|
|
32
|
+
setOpen(false);
|
|
33
|
+
setHovered(null);
|
|
34
|
+
}, 120);
|
|
35
|
+
};
|
|
36
|
+
return (_jsxs("div", { ref: rootRef, className: cn("ac-prompt-pop", className), "data-cmp": "ai/prompt-pop", "data-id": "prompt-pop", "data-state": open ? "open" : "idle", onMouseEnter: hold, onMouseLeave: release, onFocus: hold, onBlur: release, children: [_jsx("button", { type: "button", className: "ac-prompt-pop__rail", "data-cmp": "ai/prompt-pop-rail", "aria-label": `历史提示词(${items.length} 条)`, "aria-expanded": open, onClick: () => setOpen((v) => !v), children: marks.map((_, i) => (_jsx("span", { className: "ac-prompt-pop__dash", "data-active": hovered === i ? "true" : "false", "data-latest": i === 0 ? "true" : "false", onMouseEnter: () => setHovered(i) }, i))) }), open ? (_jsxs("div", { className: "ac-prompt-pop__panel", "data-cmp": "ai/prompt-pop-panel", "data-id": "prompt-pop-panel", role: "listbox", "aria-label": "\u5386\u53F2\u63D0\u793A\u8BCD", children: [_jsx("div", { className: "ac-prompt-pop__head", children: "\u5386\u53F2\u63D0\u793A\u8BCD" }), items.map((text, i) => (_jsxs("button", { type: "button", role: "option", "aria-selected": false, "data-cmp": "ai/prompt-pop-item", className: "ac-prompt-pop__row", title: text, onMouseEnter: () => setHovered(i), onClick: () => {
|
|
37
|
+
onPick(text);
|
|
38
|
+
setOpen(false);
|
|
39
|
+
}, children: [_jsx("span", { className: "ac-prompt-pop__text", children: text }), _jsx("span", { className: "ac-prompt-pop__marker", "aria-hidden": "true" })] }, `${i}-${text.slice(0, 12)}`)))] })) : null] }));
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { QueuedPrompt } from "../../types/conversation";
|
|
2
|
+
export declare const MAX_QUEUED_PROMPTS = 8;
|
|
3
|
+
export declare const QUEUED_FULL_NOTICE = "\u5F85\u53D1\u961F\u5217\u5DF2\u6EE1\uFF088 \u6761\uFF09\uFF0C\u8BF7\u5148\u5904\u7406";
|
|
4
|
+
export interface QueuedPromptsProps {
|
|
5
|
+
items: readonly QueuedPrompt[];
|
|
6
|
+
canSendNow: boolean;
|
|
7
|
+
onMoveUp: (id: string) => void;
|
|
8
|
+
onMoveDown: (id: string) => void;
|
|
9
|
+
onSendNow: (id: string) => void;
|
|
10
|
+
onEdit: (id: string) => void;
|
|
11
|
+
onRemove: (id: string) => void;
|
|
12
|
+
onClear?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function QueuedPrompts({ items, canSendNow, onMoveUp, onMoveDown, onSendNow, onEdit, onRemove, onClear, }: QueuedPromptsProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ArrowDown, ArrowUp, ChevronDown, GripVertical, ListOrdered, Paperclip, Pencil, Send, Trash2, X, } from "lucide-react";
|
|
4
|
+
export const MAX_QUEUED_PROMPTS = 8;
|
|
5
|
+
export const QUEUED_FULL_NOTICE = `待发队列已满(${MAX_QUEUED_PROMPTS} 条),请先处理`;
|
|
6
|
+
function ActBtn({ label, disabled, onClick, accent, children, }) {
|
|
7
|
+
return (_jsx("button", { type: "button", "data-cmp": "ac/queued-action", "data-action": label, "data-state": disabled ? "disabled" : "idle", title: label, "aria-label": label, disabled: disabled, className: accent
|
|
8
|
+
? "ac-queued-row__act ac-queued-row__act--accent"
|
|
9
|
+
: "ac-queued-row__act", onClick: onClick, children: children }));
|
|
10
|
+
}
|
|
11
|
+
export function QueuedPrompts({ items, canSendNow, onMoveUp, onMoveDown, onSendNow, onEdit, onRemove, onClear, }) {
|
|
12
|
+
const [open, setOpen] = useState(true);
|
|
13
|
+
if (items.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const full = items.length >= MAX_QUEUED_PROMPTS;
|
|
16
|
+
return (_jsxs("div", { className: "ac-queued", "data-cmp": "ac/queued-list", "data-count": items.length, "data-state": open ? "open" : full ? "full" : "idle", role: "list", "aria-label": "\u5F85\u53D1\u961F\u5217", children: [items.length > 1 ? (_jsxs("div", { className: "ac-queued__head", children: [_jsxs("button", { type: "button", "data-cmp": "ac/queued-toggle", className: "ac-queued__pill", "aria-expanded": open, onClick: () => setOpen((v) => !v), title: open ? "收起待发队列" : "展开待发队列", children: [_jsx(ListOrdered, { className: "ac-queued__head-icon" }), _jsx("span", { className: "ac-queued__head-title", children: "\u5F85\u53D1" }), _jsx("span", { className: "ac-queued__badge", "data-state": full ? "full" : "idle", title: `${items.length}/${MAX_QUEUED_PROMPTS}`, children: items.length }), !open ? (_jsx("span", { className: "ac-queued__preview", children: items[0]?.text })) : null, _jsx(ChevronDown, { className: "ac-queued__chev", "data-open": open ? "true" : "false" })] }), onClear ? (_jsx("button", { type: "button", "data-cmp": "ac/queued-clear", className: "ac-queued__clear", onClick: onClear, title: "\u6E05\u7A7A\u5F85\u53D1\u961F\u5217", children: _jsx(Trash2, { size: 11 }) })) : null] })) : null, open ? (_jsx("div", { className: "ac-queued__body", children: items.map((item, i) => {
|
|
17
|
+
const first = i === 0;
|
|
18
|
+
const last = i === items.length - 1;
|
|
19
|
+
return (_jsxs("div", { className: "ac-queued-row", "data-cmp": "ac/queued-row", "data-state": first ? "head" : "waiting", role: "listitem", children: [_jsx(GripVertical, { className: "ac-queued-row__grip", "aria-hidden": "true", size: 12 }), _jsxs("div", { className: "ac-queued-row__text", children: [_jsx("span", { className: "ac-queued-row__content", title: item.text, children: item.text }), (item.attachments?.length ?? 0) > 0 ? (_jsxs("span", { className: "ac-queued-row__att", title: `含 ${item.attachments?.length} 个附件`, children: [_jsx(Paperclip, { size: 10 }), item.attachments?.length] })) : null] }), _jsxs("div", { className: "ac-queued-row__acts", children: [canSendNow ? (_jsx(ActBtn, { label: "\u7ACB\u5373\u53D1\u9001", accent: true, onClick: () => onSendNow(item.id), children: _jsx(Send, { size: 11 }) })) : null, _jsx(ActBtn, { label: "\u4E0A\u79FB", disabled: first, onClick: () => onMoveUp(item.id), children: _jsx(ArrowUp, { size: 11 }) }), _jsx(ActBtn, { label: "\u4E0B\u79FB", disabled: last, onClick: () => onMoveDown(item.id), children: _jsx(ArrowDown, { size: 11 }) }), _jsx(ActBtn, { label: "\u53D6\u56DE\u7F16\u8F91", onClick: () => onEdit(item.id), children: _jsx(Pencil, { size: 11 }) }), _jsx(ActBtn, { label: "\u79FB\u51FA\u961F\u5217", onClick: () => onRemove(item.id), children: _jsx(X, { size: 11 }) })] })] }, item.id));
|
|
20
|
+
}) })) : null] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface SlingButtonProps {
|
|
3
|
+
onSend: () => void;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
size?: number;
|
|
8
|
+
ariaLabel?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function SlingButton({ onSend, disabled, children, className, size, ariaLabel, }: SlingButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, } from "react";
|
|
3
|
+
import { ArrowUp } from "lucide-react";
|
|
4
|
+
import { cn } from "../../utils/cn";
|
|
5
|
+
const ARM_AT = 36;
|
|
6
|
+
const MAX_PULL = 96;
|
|
7
|
+
export function SlingButton({ onSend, disabled = false, children, className, size = 44, ariaLabel = "发送", }) {
|
|
8
|
+
const wellRef = useRef(null);
|
|
9
|
+
const canvasRef = useRef(null);
|
|
10
|
+
const [pull, setPull] = useState(0);
|
|
11
|
+
const [angle, setAngle] = useState(-Math.PI / 2);
|
|
12
|
+
const pulling = useRef(false);
|
|
13
|
+
const origin = useRef({ x: 0, y: 0 });
|
|
14
|
+
const particles = useRef([]);
|
|
15
|
+
const raf = useRef(0);
|
|
16
|
+
const fire = useCallback(() => {
|
|
17
|
+
const canvas = canvasRef.current;
|
|
18
|
+
if (canvas) {
|
|
19
|
+
const ctx = canvas.getContext("2d");
|
|
20
|
+
if (ctx) {
|
|
21
|
+
const cx = size / 2;
|
|
22
|
+
const cy = size / 2;
|
|
23
|
+
for (let i = 0; i < 14; i++) {
|
|
24
|
+
const a = -Math.PI / 2 + (Math.random() - 0.5) * (Math.PI / 1.5);
|
|
25
|
+
const sp = 1.6 + Math.random() * 3;
|
|
26
|
+
particles.current.push({
|
|
27
|
+
x: cx,
|
|
28
|
+
y: cy,
|
|
29
|
+
vx: Math.cos(a) * sp,
|
|
30
|
+
vy: Math.sin(a) * sp,
|
|
31
|
+
r: 1 + Math.random() * 2,
|
|
32
|
+
life: 1,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
onSend();
|
|
38
|
+
setPull(0);
|
|
39
|
+
const el = wellRef.current;
|
|
40
|
+
if (el) {
|
|
41
|
+
el.animate([
|
|
42
|
+
{ transform: "scale(0.82)" },
|
|
43
|
+
{ transform: "scale(1.08)" },
|
|
44
|
+
{ transform: "scale(1)" },
|
|
45
|
+
], { duration: 340, easing: "cubic-bezier(0.22, 1, 0.36, 1)" });
|
|
46
|
+
}
|
|
47
|
+
}, [onSend, size]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const canvas = canvasRef.current;
|
|
50
|
+
if (!canvas)
|
|
51
|
+
return;
|
|
52
|
+
const ctx = canvas.getContext("2d");
|
|
53
|
+
if (!ctx)
|
|
54
|
+
return;
|
|
55
|
+
const tick = () => {
|
|
56
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
57
|
+
particles.current = particles.current.filter((p) => p.life > 0);
|
|
58
|
+
for (const p of particles.current) {
|
|
59
|
+
p.x += p.vx;
|
|
60
|
+
p.y += p.vy;
|
|
61
|
+
p.vy += 0.05;
|
|
62
|
+
p.life -= 0.035;
|
|
63
|
+
ctx.globalAlpha = Math.max(0, p.life);
|
|
64
|
+
ctx.fillStyle = "var(--accent-primary)";
|
|
65
|
+
ctx.beginPath();
|
|
66
|
+
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
|
|
67
|
+
ctx.fill();
|
|
68
|
+
}
|
|
69
|
+
ctx.globalAlpha = 1;
|
|
70
|
+
raf.current = requestAnimationFrame(tick);
|
|
71
|
+
};
|
|
72
|
+
raf.current = requestAnimationFrame(tick);
|
|
73
|
+
return () => cancelAnimationFrame(raf.current);
|
|
74
|
+
}, []);
|
|
75
|
+
const onPointerDown = (e) => {
|
|
76
|
+
if (disabled)
|
|
77
|
+
return;
|
|
78
|
+
pulling.current = true;
|
|
79
|
+
origin.current = { x: e.clientX, y: e.clientY };
|
|
80
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
81
|
+
};
|
|
82
|
+
const onPointerMove = (e) => {
|
|
83
|
+
if (!pulling.current)
|
|
84
|
+
return;
|
|
85
|
+
const dx = e.clientX - origin.current.x;
|
|
86
|
+
const dy = e.clientY - origin.current.y;
|
|
87
|
+
const dist = Math.min(Math.hypot(dx, dy), MAX_PULL);
|
|
88
|
+
setPull(dist);
|
|
89
|
+
setAngle(Math.atan2(dy, dx));
|
|
90
|
+
};
|
|
91
|
+
const onPointerUp = () => {
|
|
92
|
+
if (!pulling.current)
|
|
93
|
+
return;
|
|
94
|
+
pulling.current = false;
|
|
95
|
+
if (pull >= ARM_AT)
|
|
96
|
+
fire();
|
|
97
|
+
else {
|
|
98
|
+
setPull(0);
|
|
99
|
+
if (pull > 0)
|
|
100
|
+
onSend();
|
|
101
|
+
else
|
|
102
|
+
onSend();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const pct = Math.min(1, pull / MAX_PULL);
|
|
106
|
+
const arcR = size / 2 - 2;
|
|
107
|
+
return (_jsxs("button", { ref: wellRef, type: "button", "data-cmp": "ai/sling-button", "data-id": "send-btn", "data-state": disabled ? "disabled" : pull > 0 ? "pulling" : "idle", "aria-label": ariaLabel, title: ariaLabel, disabled: disabled, className: cn("relative touch-none select-none rounded-full", disabled && "opacity-40", className), style: { width: size, height: size }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, children: [_jsx("canvas", { ref: canvasRef, width: size * 2, height: size * 2, className: "pointer-events-none absolute inset-0", style: { width: size, height: size } }), _jsxs("svg", { className: "pointer-events-none absolute inset-0", width: size, height: size, "aria-hidden": "true", children: [_jsx("circle", { cx: size / 2, cy: size / 2, r: arcR, fill: "none", stroke: "var(--border-600)", strokeWidth: 2 }), pct > 0 ? (_jsx("circle", { cx: size / 2, cy: size / 2, r: arcR, fill: "none", stroke: "var(--accent-primary)", strokeWidth: 2.5, strokeLinecap: "round", strokeDasharray: 2 * Math.PI * arcR, strokeDashoffset: 2 * Math.PI * arcR * (1 - pct), transform: `rotate(-90 ${size / 2} ${size / 2})` })) : null] }), _jsx("span", { className: "absolute inset-[5px] grid place-items-center rounded-full transition-transform", style: {
|
|
108
|
+
background: "var(--primary)",
|
|
109
|
+
color: "var(--primary-foreground)",
|
|
110
|
+
transform: `translate(${Math.cos(angle) * pull * 0.28}px, ${Math.sin(angle) * pull * 0.28}px) scale(${1 - pct * 0.12})`,
|
|
111
|
+
}, children: children ?? _jsx(ArrowUp, { className: "size-[42%]" }) })] }));
|
|
112
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Composer, ComposerAttachmentsStrip, ComposerGrip, ComposerInput, ComposerModelChip, ComposerPermChip, ComposerToolbar, MentionPicker, QueuedPrompts, VisionTag, focusFieldAt, } from "./Composer";
|
|
2
|
+
export type { ComposerProps, ComposerPaletteLike, ComposerSubmitApi, QueuedPromptsApi, StagedAttachment, ComposerModelSwitch, PermissionModeSwitch, } from "./Composer";
|
|
3
|
+
export type { ComposerInputProps } from "./ComposerInput";
|
|
4
|
+
export type { ComposerToolbarProps } from "./ComposerToolbar";
|
|
5
|
+
export type { MentionPickerProps } from "./MentionPicker";
|
|
6
|
+
export type { ComposerAttachmentsStripProps } from "./ComposerAttachments";
|
|
7
|
+
export type { QueuedPromptsProps } from "./QueuedPrompts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Composer, ComposerAttachmentsStrip, ComposerGrip, ComposerInput, ComposerModelChip, ComposerPermChip, ComposerToolbar, MentionPicker, QueuedPrompts, VisionTag, focusFieldAt, } from "./Composer";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MessageSquarePlus, Sparkles, Users } from "lucide-react";
|
|
3
|
+
import { Button } from "../../primitives/Button";
|
|
4
|
+
const START_POINTS = [
|
|
5
|
+
{
|
|
6
|
+
icon: Sparkles,
|
|
7
|
+
title: "让 Agent 干活",
|
|
8
|
+
hint: "描述目标,Agent 自己拆步骤、动手、汇报结果",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
icon: Users,
|
|
12
|
+
title: "多 Agent 协作",
|
|
13
|
+
hint: "@ 提及不同角色,让专家们一起把事办成",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
export function ChatEmptyScene({ onStart }) {
|
|
17
|
+
return (_jsxs("div", { className: "ac-empty-scene", "data-cmp": "ac/chat-empty-scene", "data-state": "empty", children: [_jsx("span", { className: "ac-empty-scene__mark", children: _jsx(MessageSquarePlus, { size: 22 }) }), _jsx("p", { className: "ac-empty-scene__title", children: "\u5F00\u59CB\u4E00\u6BB5\u5BF9\u8BDD" }), _jsx("p", { className: "ac-empty-scene__hint", children: "\u4ECE\u5DE6\u4FA7\u9009\u4E00\u4E2A\u4F1A\u8BDD\u7EE7\u7EED\uFF0C\u6216\u65B0\u5EFA\u4E00\u4E2A\u2014\u2014\u4F60\u7684 Agent \u4F19\u4F34\u5728\u8FD9\u91CC\u7B49\u4F60\u3002" }), onStart ? (_jsx(Button, { className: "ac-empty-scene__cta", "data-cmp": "ac/chat-empty-start", onClick: onStart, children: "\u65B0\u5EFA\u4F1A\u8BDD" })) : null, _jsx("ul", { className: "ac-empty-scene__points", children: START_POINTS.map((point) => {
|
|
18
|
+
const Icon = point.icon;
|
|
19
|
+
return (_jsxs("li", { className: "ac-empty-scene__point", children: [_jsx("span", { className: "ac-empty-scene__point-icon", children: _jsx(Icon, { size: 14 }) }), _jsxs("span", { className: "ac-empty-scene__point-body", children: [_jsx("span", { className: "ac-empty-scene__point-title", children: point.title }), _jsx("span", { className: "ac-empty-scene__point-hint", children: point.hint })] })] }, point.title));
|
|
20
|
+
}) })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Agent } from "../../types/agent";
|
|
2
|
+
export interface ChatHeaderProps {
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle: string;
|
|
5
|
+
members?: readonly Agent[];
|
|
6
|
+
timeLabel?: string;
|
|
7
|
+
detailOpen: boolean;
|
|
8
|
+
showBack: boolean;
|
|
9
|
+
onBack: () => void;
|
|
10
|
+
onOpenDetail: () => void;
|
|
11
|
+
onSearch?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function ChatHeader({ title, subtitle, members, timeLabel, detailOpen, showBack, onBack, onOpenDetail, onSearch, }: ChatHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowLeft, Info, Search } from "lucide-react";
|
|
3
|
+
import { Button } from "../../primitives/Button";
|
|
4
|
+
import { Tooltip } from "../../primitives/Tooltip";
|
|
5
|
+
const AVATAR_STACK_MAX = 4;
|
|
6
|
+
function AvatarStack({ members }) {
|
|
7
|
+
const shown = members.slice(0, AVATAR_STACK_MAX);
|
|
8
|
+
const overflow = members.length - shown.length;
|
|
9
|
+
return (_jsxs("div", { className: "ac-id-avatars", "data-cmp": "ac/chat-header-avatars", "aria-label": `成员 ${members.length} 位`, children: [overflow > 0 ? (_jsxs("span", { className: "ac-id-avatars__more", title: `还有 ${overflow} 位成员`, children: ["+", overflow] })) : null, shown.map((member, index) => (_jsx("span", { className: "ac-id-avatars__item", title: member.name, children: _jsx("span", { className: "ac-avatar", "data-kind": "agent", "data-size": "sm", "data-show-status": index === 0 ? "true" : undefined, "aria-hidden": "true", children: member.badge || member.name.slice(0, 1) }) }, member.id)))] }));
|
|
10
|
+
}
|
|
11
|
+
function IdentityMeta({ models, timeLabel, }) {
|
|
12
|
+
if (models.length === 0 && !timeLabel)
|
|
13
|
+
return null;
|
|
14
|
+
return (_jsxs("div", { className: "ac-id-meta", "data-cmp": "ac/chat-header-meta", children: [models.slice(0, 3).map((model) => (_jsx("span", { className: "ac-id-meta__chip", children: model }, model))), models.length > 3 ? (_jsxs("span", { className: "ac-id-meta__chip", children: ["+", models.length - 3, " \u6A21\u578B"] })) : null, timeLabel ? (_jsx("span", { className: "ac-id-meta__time", children: timeLabel })) : null] }));
|
|
15
|
+
}
|
|
16
|
+
export function ChatHeader({ title, subtitle, members, timeLabel, detailOpen, showBack, onBack, onOpenDetail, onSearch, }) {
|
|
17
|
+
const isGroup = (members?.length ?? 0) > 1;
|
|
18
|
+
const models = Array.from(new Set((members ?? []).map((m) => m.model).filter(Boolean)));
|
|
19
|
+
return (_jsxs("header", { className: "ac-chat__head", "data-cmp": "ac/chat-header", "data-state": detailOpen ? "open" : "closed", children: [showBack ? (_jsx(Button, { variant: "ghost", size: "icon-sm", className: "ac-icon-btn ac-chat__back", "aria-label": "\u8FD4\u56DE\u4F1A\u8BDD\u5217\u8868", onClick: onBack, children: _jsx(ArrowLeft, { size: 15 }) })) : null, _jsxs("div", { className: "ac-chat__titles", children: [isGroup && members ? _jsx(AvatarStack, { members: members }) : null, _jsxs("div", { className: "ac-chat__headings", children: [_jsx("div", { className: "ac-chat__title", children: title }), _jsxs("div", { className: "ac-chat__subline", children: [_jsx("span", { className: "ac-chat__sub", children: subtitle }), _jsx(IdentityMeta, { models: models, timeLabel: timeLabel })] })] })] }), _jsx("span", { className: "ac-chat__grow" }), _jsxs("div", { className: "ac-chat__actions", children: [onSearch ? (_jsx(Tooltip, { content: "\u641C\u7D22\u804A\u5929\uFF08\u2318K\uFF09", children: _jsx(Button, { variant: "ghost", size: "icon-sm", className: "ac-icon-btn", "aria-label": "\u641C\u7D22\u804A\u5929\u8BB0\u5F55", onClick: onSearch, children: _jsx(Search, { size: 15 }) }) })) : null, _jsx(Button, { variant: "ghost", size: "icon-sm", className: "ac-icon-btn", "aria-label": "\u4F1A\u8BDD\u4FE1\u606F", "data-state": detailOpen ? "active" : "idle", onClick: onOpenDetail, children: _jsx(Info, { size: 15 }) })] })] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Agent } from "../../types/agent";
|
|
3
|
+
import type { Attachment, ChatMessage } from "../../types/message";
|
|
4
|
+
import type { Conversation, ConversationStatus, PendingAsk, PendingPermission } from "../../types/conversation";
|
|
5
|
+
import type { ComposerModelSwitch, ComposerPaletteLike, ComposerSubmitApi, PermissionModeSwitch, QueuedPromptsApi, SlashCommand } from "../../types/composer";
|
|
6
|
+
import type { SelectionTarget } from "../../types/callbacks";
|
|
7
|
+
import { type MessageStreamProps } from "../message/MessageStream";
|
|
8
|
+
import { type TodoState } from "../cards/TodoCard";
|
|
9
|
+
export type SmartStopPlan = {
|
|
10
|
+
kind: "restore";
|
|
11
|
+
draft: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "settle";
|
|
14
|
+
};
|
|
15
|
+
export declare function planSmartStop(messages: readonly ChatMessage[]): SmartStopPlan;
|
|
16
|
+
export interface ChatPaneProps {
|
|
17
|
+
conversation: Conversation | null;
|
|
18
|
+
members: Agent[];
|
|
19
|
+
messages: ChatMessage[];
|
|
20
|
+
status: ConversationStatus;
|
|
21
|
+
draft: string;
|
|
22
|
+
onDraftChange: (v: string) => void;
|
|
23
|
+
onSend: (text: string, attachments?: Attachment[]) => void;
|
|
24
|
+
onOpenDetail: () => void;
|
|
25
|
+
onOpenAgent?: (id: string) => void;
|
|
26
|
+
onBack: () => void;
|
|
27
|
+
onStop?: () => void;
|
|
28
|
+
detailOpen?: boolean;
|
|
29
|
+
onSearch?: () => void;
|
|
30
|
+
permissions?: PendingPermission[];
|
|
31
|
+
onRespondPermission?: (permissionId: string, approved: boolean, remembered: boolean) => void;
|
|
32
|
+
pendingAsk?: PendingAsk | null;
|
|
33
|
+
askQueued?: number;
|
|
34
|
+
onAnswerAsk?: (callId: string, answer: string) => void;
|
|
35
|
+
onSkipAsk?: (callId: string) => void;
|
|
36
|
+
onNotice?: (message: string) => void;
|
|
37
|
+
onNewConversation?: () => void;
|
|
38
|
+
onOpenProfile?: () => void;
|
|
39
|
+
modelSwitch?: ComposerModelSwitch;
|
|
40
|
+
permissionMode?: PermissionModeSwitch;
|
|
41
|
+
onMic?: () => void;
|
|
42
|
+
micState?: "idle" | "listening";
|
|
43
|
+
toolbarTrailing?: ReactNode;
|
|
44
|
+
onContextWindowChange?: (window: number) => void;
|
|
45
|
+
slashExtra?: readonly SlashCommand[];
|
|
46
|
+
slashExpand?: (draft: string) => string | null;
|
|
47
|
+
onFavoriteSelection?: (target: SelectionTarget) => void;
|
|
48
|
+
selectionActive?: boolean;
|
|
49
|
+
selectedIds?: string[];
|
|
50
|
+
onToggleSelect?: (messageId: string) => void;
|
|
51
|
+
onBatchCopy?: () => void;
|
|
52
|
+
onBatchRemove?: () => void;
|
|
53
|
+
batchBusy?: boolean;
|
|
54
|
+
onExitSelection?: () => void;
|
|
55
|
+
messageActions?: MessageStreamProps["messageActions"];
|
|
56
|
+
messageArtifacts?: MessageStreamProps["messageArtifacts"];
|
|
57
|
+
selectionPop?: {
|
|
58
|
+
left: number;
|
|
59
|
+
top: number;
|
|
60
|
+
target: SelectionTarget;
|
|
61
|
+
} | null;
|
|
62
|
+
onDismissSelectionPop?: () => void;
|
|
63
|
+
onInsertSelectionText?: (text: string) => void;
|
|
64
|
+
composer?: {
|
|
65
|
+
palette: ComposerPaletteLike;
|
|
66
|
+
submission: ComposerSubmitApi;
|
|
67
|
+
queued?: QueuedPromptsApi;
|
|
68
|
+
onArrowUp?: () => boolean;
|
|
69
|
+
onArrowDown?: () => boolean;
|
|
70
|
+
attachmentPicker?: ReactNode;
|
|
71
|
+
attachmentHeader?: ReactNode;
|
|
72
|
+
stagedAttachments?: import("../composer/Composer").StagedAttachment[];
|
|
73
|
+
onRemoveAttachment?: (id: string) => void;
|
|
74
|
+
dragHandlers?: {
|
|
75
|
+
onPaste?: (e: React.ClipboardEvent) => void;
|
|
76
|
+
onDragOver?: (e: React.DragEvent) => void;
|
|
77
|
+
onDrop?: (e: React.DragEvent) => void;
|
|
78
|
+
};
|
|
79
|
+
visionTag?: ReactNode;
|
|
80
|
+
slashExtra?: readonly SlashCommand[];
|
|
81
|
+
};
|
|
82
|
+
todoState?: TodoState | null;
|
|
83
|
+
promptPop?: {
|
|
84
|
+
items: readonly string[];
|
|
85
|
+
onPick: (text: string) => void;
|
|
86
|
+
};
|
|
87
|
+
subtitle?: string;
|
|
88
|
+
children?: ReactNode;
|
|
89
|
+
}
|
|
90
|
+
export declare function ChatPane(props: ChatPaneProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { capabilitiesOf } from "../../domain/selection-gates";
|
|
4
|
+
import { Composer } from "../composer/Composer";
|
|
5
|
+
import { PromptPop } from "../composer/PromptPop";
|
|
6
|
+
import { MessageStream, } from "../message/MessageStream";
|
|
7
|
+
import { SelectionActions } from "../selection/SelectionActions";
|
|
8
|
+
import { SelectionBar } from "../selection/SelectionBar";
|
|
9
|
+
import { TodoCard } from "../cards/TodoCard";
|
|
10
|
+
import { ChatEmptyScene } from "./ChatEmptyScene";
|
|
11
|
+
import { ChatHeader } from "./ChatHeader";
|
|
12
|
+
export function planSmartStop(messages) {
|
|
13
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
14
|
+
const m = messages[i];
|
|
15
|
+
if (m.role === "user") {
|
|
16
|
+
return { kind: "restore", draft: m.text };
|
|
17
|
+
}
|
|
18
|
+
if ((m.role === "assistant" && m.text.trim().length > 0) ||
|
|
19
|
+
(m.parts && m.parts.length > 0)) {
|
|
20
|
+
return { kind: "settle" };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { kind: "settle" };
|
|
24
|
+
}
|
|
25
|
+
export function ChatPane(props) {
|
|
26
|
+
const { conversation, members, messages, status, draft, onDraftChange, onSend, onOpenDetail, onOpenAgent, onBack, onStop, detailOpen = false, onSearch, permissions, onRespondPermission, pendingAsk, askQueued, onAnswerAsk, onSkipAsk, onNotice, onNewConversation, onOpenProfile, modelSwitch, permissionMode, onMic, micState, toolbarTrailing, onContextWindowChange, slashExtra, slashExpand, onFavoriteSelection, selectionActive = false, selectedIds = [], onToggleSelect, onBatchCopy, onBatchRemove, batchBusy = false, onExitSelection, messageActions, messageArtifacts, selectionPop, onDismissSelectionPop, onInsertSelectionText, composer, todoState, promptPop, subtitle, children, } = props;
|
|
27
|
+
const lastContextUsage = useMemo(() => {
|
|
28
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
29
|
+
const u = messages[i].contextUsage;
|
|
30
|
+
if (u && u.window > 0)
|
|
31
|
+
return u;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}, [messages]);
|
|
35
|
+
if (!conversation) {
|
|
36
|
+
return (_jsx("section", { className: "ac-chat", "data-cmp": "ac/chat", "data-state": "idle", children: _jsx(ChatEmptyScene, { onStart: onNewConversation }) }));
|
|
37
|
+
}
|
|
38
|
+
const capabilities = capabilitiesOf(messages, selectedIds);
|
|
39
|
+
const handleSmartStop = () => {
|
|
40
|
+
if (!onStop)
|
|
41
|
+
return;
|
|
42
|
+
const plan = planSmartStop(messages);
|
|
43
|
+
if (plan.kind === "restore" && draft.trim().length === 0) {
|
|
44
|
+
onDraftChange(plan.draft);
|
|
45
|
+
onNotice?.("已停止:输入框已还原上一条消息,可修改后重发");
|
|
46
|
+
}
|
|
47
|
+
onStop();
|
|
48
|
+
};
|
|
49
|
+
return (_jsxs("section", { className: "ac-chat", "data-cmp": "ac/chat", "data-state": status, children: [_jsx(ChatHeader, { title: conversation.title, subtitle: subtitle ?? `${members.length} 位成员`, members: members, timeLabel: conversation.timeLabel, detailOpen: detailOpen, showBack: false, onBack: onBack, onOpenDetail: onOpenDetail, onSearch: onSearch }), _jsx(TodoCard, { state: todoState ?? null }), _jsxs("div", { className: "ac-chat__main", children: [_jsx(MessageStream, { messages: messages, members: members, status: status, onOpenAgent: onOpenAgent, permissions: permissions, onRespondPermission: onRespondPermission, messageActions: messageActions, messageArtifacts: messageArtifacts, selectMode: selectionActive, selectedIds: new Set(selectedIds), onToggleSelect: onToggleSelect }), promptPop ? (_jsx(PromptPop, { items: promptPop.items, onPick: promptPop.onPick, className: "ac-chat__prompt-pop" })) : null] }), selectionActive ? (_jsx(SelectionBar, { count: selectedIds.length, capabilities: {
|
|
50
|
+
copy: capabilities.copy && Boolean(onBatchCopy),
|
|
51
|
+
remove: capabilities.remove && Boolean(onBatchRemove),
|
|
52
|
+
reason: {
|
|
53
|
+
...capabilities.reason,
|
|
54
|
+
...(onBatchCopy ? {} : { copy: "未接线:批量复制不可用" }),
|
|
55
|
+
...(onBatchRemove ? {} : { remove: "未接线:批量删除不可用" }),
|
|
56
|
+
},
|
|
57
|
+
}, busy: batchBusy, onCopy: () => onBatchCopy?.(), onRemove: () => onBatchRemove?.(), onExit: () => onExitSelection?.() })) : composer ? (_jsx(Composer, { value: draft, onChange: onDraftChange, members: members, status: status, conversationId: conversation.id, onSend: onSend, onStop: onStop ? handleSmartStop : undefined, onNotice: onNotice, onNewConversation: onNewConversation, onOpenProfile: onOpenProfile, modelSwitch: modelSwitch, permissionMode: permissionMode, slashExtra: slashExtra ?? composer.slashExtra, slashExpand: slashExpand, contextUsage: lastContextUsage
|
|
58
|
+
? { ...lastContextUsage, onWindowChange: onContextWindowChange }
|
|
59
|
+
: undefined, pendingAsk: pendingAsk, askQueued: askQueued, onAnswerAsk: onAnswerAsk, onSkipAsk: onSkipAsk, palette: composer.palette, submission: composer.submission, queued: composer.queued, onArrowUp: composer.onArrowUp, onArrowDown: composer.onArrowDown, attachmentPicker: composer.attachmentPicker, attachmentHeader: composer.attachmentHeader, stagedAttachments: composer.stagedAttachments, onRemoveAttachment: composer.onRemoveAttachment, dragHandlers: composer.dragHandlers, visionTag: composer.visionTag, onMic: onMic, micState: micState, toolbarTrailing: toolbarTrailing })) : null, !selectionActive ? (_jsx(SelectionActions, { pop: selectionPop ?? null, onDismiss: () => onDismissSelectionPop?.(), onInsert: (text) => onInsertSelectionText?.(text), onFavorite: onFavoriteSelection })) : null, children] }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
export declare const NEAR_BOTTOM_PX = 80;
|
|
3
|
+
export declare function ConversationShell({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
4
|
+
export declare function ConversationContent({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
5
|
+
export declare function ConversationScroller({ className, children, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
6
|
+
export interface ConversationEmptyProps extends ComponentProps<"div"> {
|
|
7
|
+
title: string;
|
|
8
|
+
hint?: string;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export declare function ConversationEmpty({ title, hint, className, ...props }: ConversationEmptyProps): import("react").JSX.Element;
|