@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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MonitorPlay } from "lucide-react";
|
|
3
|
+
function BarChart({ payload }) {
|
|
4
|
+
const data = Array.isArray(payload.data) ? payload.data : [];
|
|
5
|
+
if (data.length === 0)
|
|
6
|
+
return null;
|
|
7
|
+
const max = Math.max(...data.map((v) => Math.abs(v)), 1);
|
|
8
|
+
return (_jsx("div", { className: "flex items-end gap-2 pt-1", "data-cmp": "ai/display-bar", children: data.map((v, i) => (_jsxs("div", { className: "flex flex-1 flex-col items-center gap-1", children: [_jsx("span", { className: "text-micro tabular-nums text-foreground/80", children: v }), _jsx("div", { className: "w-full rounded-sm bg-[color-mix(in_srgb,var(--accent-primary)_22%,var(--surface-600))] transition-[height] duration-300", style: { height: `${Math.max(3, (Math.abs(v) / max) * 34)}px` }, title: String(v) }), _jsx("span", { className: "text-micro text-muted-foreground", children: payload.labels?.[i] ?? i + 1 })] }, i))) }));
|
|
9
|
+
}
|
|
10
|
+
export function DisplayCard({ part, renderDisplay }) {
|
|
11
|
+
const payload = part.payload;
|
|
12
|
+
const isBar = (part.view === "chart" && payload?.type === "bar") ||
|
|
13
|
+
(part.view === "bar" && Array.isArray(payload?.data));
|
|
14
|
+
return (_jsxs("div", { className: "ac-card", "data-cmp": "ai/display-card", "data-view": part.view, children: [_jsxs("div", { className: "ac-card__head", children: [_jsx(MonitorPlay, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "ac-card__title", children: part.title ?? `${part.view} 展示` })] }), _jsx("div", { className: "ac-card__body", children: renderDisplay ? (renderDisplay(part)) : isBar ? (_jsx(BarChart, { payload: payload ?? {} })) : (_jsx("pre", { className: "max-h-48 overflow-y-auto whitespace-pre-wrap break-all font-mono text-meta text-muted-foreground", children: summarizePayload(part.payload) })) })] }));
|
|
15
|
+
}
|
|
16
|
+
function summarizePayload(payload) {
|
|
17
|
+
if (payload == null)
|
|
18
|
+
return "(无载荷)";
|
|
19
|
+
if (typeof payload === "string")
|
|
20
|
+
return payload;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(payload, null, 2);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return String(payload);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Database } from "lucide-react";
|
|
3
|
+
export function MemoryPill({ part }) {
|
|
4
|
+
return (_jsxs("div", { className: "ac-memory", "data-cmp": "ac/memory-pill", children: [_jsx(Database, { "aria-hidden": "true", size: 12 }), _jsxs("span", { className: "ac-memory__count", children: ["\u8BB0\u5FC6 \u00D7", part.count] }), _jsx("span", { className: "ac-memory__excerpt", title: part.excerpt, children: part.excerpt })] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { FileText, Link2, Braces, Type } from "lucide-react";
|
|
3
|
+
const KIND_META = {
|
|
4
|
+
text: { icon: Type, label: "文本输出" },
|
|
5
|
+
file: { icon: FileText, label: "文件产物" },
|
|
6
|
+
link: { icon: Link2, label: "链接产物" },
|
|
7
|
+
json: { icon: Braces, label: "JSON 产物" },
|
|
8
|
+
};
|
|
9
|
+
export function OutputCard({ part }) {
|
|
10
|
+
const meta = KIND_META[part.outputKind] ?? KIND_META.text;
|
|
11
|
+
const Icon = meta.icon;
|
|
12
|
+
return (_jsxs("div", { className: "ac-card", "data-cmp": "ai/output-card", "data-output-kind": part.outputKind, children: [_jsxs("div", { className: "ac-card__head", children: [_jsx(Icon, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "ac-card__title", children: part.label ?? meta.label })] }), _jsx("div", { className: "ac-card__body", children: part.outputKind === "link" ? (_jsx("a", { href: part.value, target: "_blank", rel: "noreferrer", className: "break-all text-body underline", children: part.value })) : part.outputKind === "file" ? (_jsxs("span", { className: "inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-secondary/50 px-2 py-1 font-mono text-meta", children: [_jsx(FileText, { className: "size-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: part.value })] })) : (_jsx("pre", { className: "max-h-48 overflow-y-auto whitespace-pre-wrap break-all font-mono text-meta text-muted-foreground", children: part.value })) })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type PermissionRisk = "low" | "medium" | "high";
|
|
2
|
+
export type PermissionScope = "once" | "session" | "project";
|
|
3
|
+
export interface PermissionCardProps {
|
|
4
|
+
tool: string;
|
|
5
|
+
reason?: string;
|
|
6
|
+
detail?: string;
|
|
7
|
+
risk?: PermissionRisk;
|
|
8
|
+
agent?: {
|
|
9
|
+
id: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
};
|
|
12
|
+
queue?: {
|
|
13
|
+
index: number;
|
|
14
|
+
total: number;
|
|
15
|
+
};
|
|
16
|
+
onApproveRest?: () => void;
|
|
17
|
+
onRespond: (approved: boolean, remembered: boolean, scope?: PermissionScope) => void;
|
|
18
|
+
"data-cmp"?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function PermissionCard({ tool, reason, detail, risk, agent, queue, onApproveRest, onRespond, className, ...props }: PermissionCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { AlertTriangle, Check, ChevronDown, Cpu, ShieldCheck, } from "lucide-react";
|
|
4
|
+
import { cn } from "../../utils/cn";
|
|
5
|
+
export function PermissionCard({ tool, reason, detail, risk = "medium", agent, queue, onApproveRest, onRespond, className, ...props }) {
|
|
6
|
+
const [expanded, setExpanded] = useState(false);
|
|
7
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
8
|
+
const requester = agent ? agent.name?.trim() || agent.id : undefined;
|
|
9
|
+
const showQueue = queue !== undefined && queue.total > 1;
|
|
10
|
+
const showApproveRest = showQueue && queue.index === 1 && onApproveRest !== undefined;
|
|
11
|
+
const lines = (detail ?? "").split("\n").filter(Boolean);
|
|
12
|
+
const summary = lines[0] ?? "";
|
|
13
|
+
const hasMore = lines.length > 1;
|
|
14
|
+
return (_jsxs("div", { "data-cmp": props["data-cmp"] ?? "ai/permission-card", "data-id": "permission-card", "data-state": "pending", "data-risk": risk, "data-queue-index": queue?.index, "data-queue-total": queue?.total, className: cn("ac-perm", className), children: [_jsxs("div", { className: "ac-perm__head", children: [_jsx(Cpu, { className: "ac-perm__head-icon" }), _jsxs("span", { className: "ac-perm__title", children: [requester ? `${requester} ` : "", "\u8BF7\u6C42\u6267\u884C", _jsx("code", { className: "ac-perm__tool", children: tool })] }), risk === "high" ? (_jsx(AlertTriangle, { className: "ac-perm__risk-icon", "aria-label": "\u9AD8\u98CE\u9669\u64CD\u4F5C" })) : null, showQueue ? (_jsxs("span", { className: "ac-perm__queue", children: [_jsx(ShieldCheck, { className: "size-3" }), queue.index, "/", queue.total, showApproveRest ? (_jsx("button", { type: "button", "data-cmp": "ai/permission-approve-rest", className: "ac-perm__rest", onClick: onApproveRest, children: "\u5176\u4F59\u5168\u6279" })) : null] })) : null] }), detail ? (_jsxs("div", { className: "ac-perm__detail", "data-cmp": "ai/permission-detail", children: [_jsx("code", { className: cn("ac-perm__line", !expanded && "is-clamped"), children: expanded ? detail : summary }), hasMore || reason ? (_jsx("button", { type: "button", className: "ac-perm__more", onClick: () => setExpanded((v) => !v), title: reason ?? undefined, children: expanded
|
|
15
|
+
? "收起"
|
|
16
|
+
: hasMore
|
|
17
|
+
? `+${lines.length - 1} 项`
|
|
18
|
+
: "详情" })) : null] })) : null, expanded && reason ? (_jsx("p", { className: "ac-perm__reason", children: reason })) : null, _jsxs("div", { className: "ac-perm__actions", children: [_jsx("button", { type: "button", "data-cmp": "ai/permission-deny", className: "ac-perm__deny", onClick: () => onRespond(false, false), children: "\u62D2\u7EDD" }), _jsxs("div", { className: "ac-perm__primary", children: [_jsxs("button", { type: "button", "data-cmp": "ai/permission-approve", className: "ac-perm__btn ac-perm__btn--primary", onClick: () => onRespond(true, false), children: [_jsx(Check, { className: "size-3.5" }), "\u5141\u8BB8"] }), _jsx("button", { type: "button", "data-cmp": "ai/permission-scope", className: "ac-perm__btn ac-perm__btn--scope", "aria-label": "\u5141\u8BB8\u5E76\u8BB0\u4F4F", "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: () => setMenuOpen((v) => !v), children: _jsx(ChevronDown, { className: "size-3" }) }), menuOpen ? (_jsx("div", { role: "menu", className: "ac-perm__menu", children: [
|
|
19
|
+
{ v: "once", t: "仅本次", d: "" },
|
|
20
|
+
{ v: "session", t: "本会话内都允许", d: "" },
|
|
21
|
+
{ v: "project", t: "本项目内都允许", d: "" },
|
|
22
|
+
].map((opt) => (_jsx("button", { type: "button", role: "menuitem", className: "ac-perm__menu-item", onClick: () => {
|
|
23
|
+
setMenuOpen(false);
|
|
24
|
+
onRespond(true, opt.v !== "once", opt.v);
|
|
25
|
+
}, children: opt.t }, opt.v))) })) : null] })] })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SpringCheckProps {
|
|
2
|
+
checked: boolean;
|
|
3
|
+
onChange: (checked: boolean) => void;
|
|
4
|
+
label: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
doneOpacity?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function SpringCheck({ checked, onChange, label, disabled, doneOpacity, className, }: SpringCheckProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { cn } from "../../utils/cn";
|
|
4
|
+
export function SpringCheck({ checked, onChange, label, disabled = false, doneOpacity = 0.42, className, }) {
|
|
5
|
+
const [bouncing, setBouncing] = useState(false);
|
|
6
|
+
const timer = useRef(0);
|
|
7
|
+
return (_jsxs("button", { type: "button", role: "checkbox", "aria-checked": checked, disabled: disabled, "data-cmp": "ai/spring-check", "data-state": checked ? "checked" : "idle", className: cn("flex items-center gap-2 text-left", disabled && "pointer-events-none opacity-40", className), onClick: () => {
|
|
8
|
+
setBouncing(true);
|
|
9
|
+
window.clearTimeout(timer.current);
|
|
10
|
+
timer.current = window.setTimeout(() => setBouncing(false), 380);
|
|
11
|
+
onChange(!checked);
|
|
12
|
+
}, children: [_jsx("span", { className: cn("relative grid size-[18px] shrink-0 place-items-center rounded-[5px] border transition-colors duration-200", checked
|
|
13
|
+
? "border-transparent"
|
|
14
|
+
: "border-[var(--border-500)] bg-transparent", bouncing && "spring-check__bounce"), style: checked
|
|
15
|
+
? { background: "var(--accent-success)" }
|
|
16
|
+
: undefined, children: checked ? (_jsx("svg", { viewBox: "0 0 16 16", className: "spring-check__tick", width: 12, height: 12, "aria-hidden": "true", children: _jsx("path", { d: "M3.5 8.5 L6.8 11.8 L12.5 5.2", fill: "none", stroke: "var(--surface-950)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round" }) })) : null }), _jsx("span", { className: cn("relative text-body leading-snug transition-opacity duration-300", checked && "spring-check__struck"), style: checked ? { opacity: doneOpacity } : undefined, children: label })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type TodoStatus = "pending" | "in_progress" | "blocked" | "completed";
|
|
2
|
+
export interface TodoEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
content: string;
|
|
5
|
+
status: TodoStatus;
|
|
6
|
+
blocker?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface TodoState {
|
|
9
|
+
revision: number;
|
|
10
|
+
todos: TodoEntry[];
|
|
11
|
+
}
|
|
12
|
+
export declare function mergeTodoState(prev: TodoState | null, next: TodoState | null): TodoState | null;
|
|
13
|
+
export declare function blockerNote(todo: TodoEntry): string | null;
|
|
14
|
+
export interface TodoCardProps {
|
|
15
|
+
state: TodoState | null;
|
|
16
|
+
defaultOpen?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function TodoCard({ state, defaultOpen, className, }: TodoCardProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ChevronDown, ListChecks } from "lucide-react";
|
|
4
|
+
import { cn } from "../../utils/cn";
|
|
5
|
+
import { SpringCheck } from "./SpringCheck";
|
|
6
|
+
export function mergeTodoState(prev, next) {
|
|
7
|
+
if (!next)
|
|
8
|
+
return prev;
|
|
9
|
+
if (prev && next.revision < prev.revision)
|
|
10
|
+
return prev;
|
|
11
|
+
if (prev && next.revision === prev.revision)
|
|
12
|
+
return prev;
|
|
13
|
+
return next;
|
|
14
|
+
}
|
|
15
|
+
export function blockerNote(todo) {
|
|
16
|
+
return todo.blocker && todo.blocker.length > 0 ? todo.blocker : null;
|
|
17
|
+
}
|
|
18
|
+
export function TodoCard({ state, defaultOpen = false, className, }) {
|
|
19
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
20
|
+
const [, setFlash] = useState({});
|
|
21
|
+
if (!state || state.todos.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const total = state.todos.length;
|
|
24
|
+
const done = state.todos.filter((t) => t.status === "completed").length;
|
|
25
|
+
return (_jsxs("div", { className: cn("ac-todo-pill", className), "data-cmp": "ac/todo-card", "data-id": "todo-card", "data-state": open ? "open" : "collapsed", children: [_jsxs("button", { type: "button", "data-cmp": "ac/todo-toggle", "aria-expanded": open, className: "ac-todo-pill__chip", title: `任务清单 ${done}/${total} · rev ${state.revision}`, onClick: () => setOpen((v) => !v), children: [_jsx(ListChecks, { className: "size-3.5" }), _jsx("span", { className: "ac-todo-pill__label", children: "\u4EFB\u52A1\u6E05\u5355" }), _jsxs("span", { className: "ac-todo-pill__count", children: [done, "/", total] }), _jsx("span", { className: "ac-todo-pill__bar", children: _jsx("span", { className: "ac-todo-pill__fill", style: { width: `${total > 0 ? (done / total) * 100 : 0}%` } }) }), _jsx(ChevronDown, { className: cn("size-3 transition-transform duration-200", open && "rotate-180") })] }), open ? (_jsxs("div", { className: "ac-todo-pill__panel", "data-cmp": "ac/todo-list", children: [_jsx("div", { className: "ac-todo-pill__head", children: _jsxs("span", { children: ["rev ", state.revision] }) }), _jsx("ul", { className: "space-y-1", children: state.todos.map((item) => (_jsxs("li", { "data-cmp": "ac/todo-item", "data-status": item.status, children: [_jsx(SpringCheck, { checked: item.status === "completed", onChange: () => {
|
|
26
|
+
setFlash((f) => ({ ...f, [item.id]: true }));
|
|
27
|
+
}, label: item.content, disabled: item.status === "blocked" }), blockerNote(item) ? (_jsxs("div", { className: "pl-6 text-micro text-[var(--accent-danger)]", children: ["\u963B\u585E\uFF1A", blockerNote(item)] })) : item.status === "in_progress" ? (_jsx("div", { className: "pl-6 text-micro text-muted-foreground", children: "\u8FDB\u884C\u4E2D" })) : null] }, item.id))) })] })) : null] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CompactionCard, type CompactionCardProps } from "./CompactionCard";
|
|
2
|
+
export { MemoryPill, type MemoryPillProps } from "./MemoryPill";
|
|
3
|
+
export { PermissionCard, type PermissionCardProps, } from "./PermissionCard";
|
|
4
|
+
export { ContextUsage, formatTokens, type ContextUsageProps, } from "./ContextUsage";
|
|
5
|
+
export { AskCard, type AskCardProps } from "./AskCard";
|
|
6
|
+
export { isBatchAsk } from "../../domain/ask";
|
|
7
|
+
export { TodoCard, mergeTodoState, blockerNote, type TodoCardProps, type TodoEntry, type TodoState, type TodoStatus, } from "./TodoCard";
|
|
8
|
+
export { SpringCheck, type SpringCheckProps } from "./SpringCheck";
|
|
9
|
+
export { DisplayCard, type DisplayCardProps } from "./DisplayCard";
|
|
10
|
+
export { OutputCard, type OutputCardProps } from "./OutputCard";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CompactionCard } from "./CompactionCard";
|
|
2
|
+
export { MemoryPill } from "./MemoryPill";
|
|
3
|
+
export { PermissionCard, } from "./PermissionCard";
|
|
4
|
+
export { ContextUsage, formatTokens, } from "./ContextUsage";
|
|
5
|
+
export { AskCard } from "./AskCard";
|
|
6
|
+
export { isBatchAsk } from "../../domain/ask";
|
|
7
|
+
export { TodoCard, mergeTodoState, blockerNote, } from "./TodoCard";
|
|
8
|
+
export { SpringCheck } from "./SpringCheck";
|
|
9
|
+
export { DisplayCard } from "./DisplayCard";
|
|
10
|
+
export { OutputCard } from "./OutputCard";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Attachment } from "../../types/message";
|
|
3
|
+
import type { ContextUsageSegment, ContextWindowPreset } from "../../types/message";
|
|
4
|
+
import type { ConversationStatus, PendingAsk } from "../../types/conversation";
|
|
5
|
+
import type { Agent } from "../../types/agent";
|
|
6
|
+
import type { ComposerModelSwitch, ComposerPaletteLike, ComposerSubmitApi, PermissionModeSwitch, QueuedPromptsApi, SlashCommand } from "../../types/composer";
|
|
7
|
+
import { focusFieldAt } from "./ComposerInput";
|
|
8
|
+
import { type StagedAttachment } from "./ComposerAttachments";
|
|
9
|
+
export type { ComposerModelSwitch, PermissionModeSwitch };
|
|
10
|
+
export interface ComposerProps {
|
|
11
|
+
value: string;
|
|
12
|
+
onChange: (v: string) => void;
|
|
13
|
+
members: Agent[];
|
|
14
|
+
status: ConversationStatus;
|
|
15
|
+
conversationId: string;
|
|
16
|
+
onSend: (text: string, attachments?: Attachment[]) => void;
|
|
17
|
+
onStop?: () => void;
|
|
18
|
+
onNotice?: (message: string) => void;
|
|
19
|
+
onNewConversation?: () => void;
|
|
20
|
+
onOpenProfile?: () => void;
|
|
21
|
+
modelSwitch?: ComposerModelSwitch;
|
|
22
|
+
permissionMode?: PermissionModeSwitch;
|
|
23
|
+
slashExtra?: readonly SlashCommand[];
|
|
24
|
+
slashExpand?: (draft: string) => string | null;
|
|
25
|
+
contextUsage?: {
|
|
26
|
+
input: number;
|
|
27
|
+
output?: number;
|
|
28
|
+
window: number;
|
|
29
|
+
breakdown?: readonly ContextUsageSegment[];
|
|
30
|
+
windowPresets?: readonly ContextWindowPreset[];
|
|
31
|
+
onWindowChange?: (window: number) => void;
|
|
32
|
+
};
|
|
33
|
+
pendingAsk?: PendingAsk | null;
|
|
34
|
+
askQueued?: number;
|
|
35
|
+
onAnswerAsk?: (callId: string, answer: string) => void;
|
|
36
|
+
onSkipAsk?: (callId: string) => void;
|
|
37
|
+
palette: ComposerPaletteLike;
|
|
38
|
+
submission: ComposerSubmitApi;
|
|
39
|
+
queued?: QueuedPromptsApi;
|
|
40
|
+
stagedAttachments?: readonly StagedAttachment[];
|
|
41
|
+
onRemoveAttachment?: (id: string) => void;
|
|
42
|
+
attachmentPicker?: ReactNode;
|
|
43
|
+
attachmentHeader?: ReactNode;
|
|
44
|
+
dragHandlers?: {
|
|
45
|
+
onPaste?: (e: React.ClipboardEvent) => void;
|
|
46
|
+
onDragOver?: (e: React.DragEvent) => void;
|
|
47
|
+
onDrop?: (e: React.DragEvent) => void;
|
|
48
|
+
};
|
|
49
|
+
visionTag?: ReactNode;
|
|
50
|
+
onMic?: () => void;
|
|
51
|
+
micState?: "idle" | "listening";
|
|
52
|
+
toolbarTrailing?: ReactNode;
|
|
53
|
+
onArrowUp?: () => boolean;
|
|
54
|
+
onArrowDown?: () => boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function Composer(props: ComposerProps): import("react").JSX.Element;
|
|
57
|
+
export { focusFieldAt };
|
|
58
|
+
export type { ComposerPaletteLike, StagedAttachment, ComposerSubmitApi, QueuedPromptsApi };
|
|
59
|
+
export { ComposerAttachmentsStrip, VisionTag } from "./ComposerAttachments";
|
|
60
|
+
export { ComposerModelChip, ComposerPermChip, } from "./ComposerMenus";
|
|
61
|
+
export { ComposerGrip } from "./ComposerGrip";
|
|
62
|
+
export { ComposerInput } from "./ComposerInput";
|
|
63
|
+
export { ComposerToolbar } from "./ComposerToolbar";
|
|
64
|
+
export { MentionPicker } from "./MentionPicker";
|
|
65
|
+
export { PromptPop, type PromptPopProps } from "./PromptPop";
|
|
66
|
+
export { QueuedPrompts, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, type QueuedPromptsProps, } from "./QueuedPrompts";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, } from "react";
|
|
3
|
+
import { AskCard } from "../cards/AskCard";
|
|
4
|
+
import { ComposerGrip } from "./ComposerGrip";
|
|
5
|
+
import { ComposerInput, focusFieldAt } from "./ComposerInput";
|
|
6
|
+
import { ComposerToolbar } from "./ComposerToolbar";
|
|
7
|
+
import { MentionPicker } from "./MentionPicker";
|
|
8
|
+
import { QueuedPrompts } from "./QueuedPrompts";
|
|
9
|
+
import { ComposerAttachmentsStrip, } from "./ComposerAttachments";
|
|
10
|
+
function askPlaceholder(ask) {
|
|
11
|
+
if (!ask || ask.questions.length > 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
return `回答:${ask.prompt.slice(0, 24)}`;
|
|
14
|
+
}
|
|
15
|
+
export function Composer(props) {
|
|
16
|
+
const { value, onChange, status, onSend: _onSend, onStop, onNotice: _onNotice, onOpenProfile, modelSwitch, permissionMode, contextUsage, pendingAsk, askQueued = 0, onAnswerAsk, onSkipAsk, palette, submission, queued, stagedAttachments, onRemoveAttachment, attachmentPicker, attachmentHeader, dragHandlers, visionTag, onMic, micState, toolbarTrailing, onArrowUp, onArrowDown, } = props;
|
|
17
|
+
const _members = props.members;
|
|
18
|
+
const _conversationId = props.conversationId;
|
|
19
|
+
const _onNewConversation = props.onNewConversation;
|
|
20
|
+
void _members;
|
|
21
|
+
void _conversationId;
|
|
22
|
+
void _onNewConversation;
|
|
23
|
+
const fieldRef = useRef(null);
|
|
24
|
+
const draftBeforeAsk = useRef("");
|
|
25
|
+
const answering = pendingAsk != null &&
|
|
26
|
+
onAnswerAsk !== undefined &&
|
|
27
|
+
pendingAsk.questions.length === 0;
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (answering) {
|
|
30
|
+
draftBeforeAsk.current = value;
|
|
31
|
+
onChange("");
|
|
32
|
+
focusFieldAt(fieldRef, 0);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (draftBeforeAsk.current.length > 0) {
|
|
36
|
+
onChange(draftBeforeAsk.current);
|
|
37
|
+
focusFieldAt(fieldRef, draftBeforeAsk.current.length);
|
|
38
|
+
draftBeforeAsk.current = "";
|
|
39
|
+
}
|
|
40
|
+
}, [answering]);
|
|
41
|
+
const empty = value.trim().length === 0 && (stagedAttachments?.length ?? 0) === 0;
|
|
42
|
+
const canStop = status === "streaming" && onStop !== undefined;
|
|
43
|
+
const submitForInput = () => {
|
|
44
|
+
if (answering && pendingAsk && onAnswerAsk) {
|
|
45
|
+
const answer = value.trim();
|
|
46
|
+
if (answer.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
onAnswerAsk(pendingAsk.callId, answer);
|
|
49
|
+
onChange("");
|
|
50
|
+
palette.close();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
submission.submitOrQueue();
|
|
54
|
+
};
|
|
55
|
+
return (_jsxs("div", { className: `ac-composer${answering ? " is-gated" : ""}`, "data-cmp": "ac/composer", "data-id": "composer", "data-ctx": "ac.composer", "data-state": answering ? "answering" : status, onPaste: dragHandlers?.onPaste, onDragOver: dragHandlers?.onDragOver, onDrop: dragHandlers?.onDrop, children: [pendingAsk != null && onAnswerAsk !== undefined ? (_jsx(AskCard, { ask: pendingAsk, queued: askQueued, onAnswer: (text) => onAnswerAsk(pendingAsk.callId, text), onSkip: onSkipAsk ? () => onSkipAsk(pendingAsk.callId) : undefined })) : null, queued ? (_jsx(QueuedPrompts, { items: queued.items, canSendNow: onStop !== undefined, onMoveUp: queued.moveUp, onMoveDown: queued.moveDown, onSendNow: queued.sendNow, onEdit: (id) => {
|
|
56
|
+
const text = queued.edit(id);
|
|
57
|
+
if (text != null) {
|
|
58
|
+
onChange(text);
|
|
59
|
+
focusFieldAt(fieldRef, text.length);
|
|
60
|
+
}
|
|
61
|
+
}, onRemove: queued.remove, onClear: queued.items.length > 1
|
|
62
|
+
? () => queued.items.forEach((it) => queued.remove(it.id))
|
|
63
|
+
: undefined })) : null, _jsxs("div", { className: "ac-composer__card", "data-region": "ac.composer-card", children: [attachmentHeader ?? (_jsx(ComposerAttachmentsStrip, { items: stagedAttachments ?? [], onRemove: (id) => onRemoveAttachment?.(id), visionTag: visionTag })), _jsx(ComposerGrip, {}), _jsx(ComposerInput, { fieldRef: fieldRef, value: value, onChange: onChange, palette: palette, onAccept: submission.pick, onSubmit: submitForInput, answering: answering, placeholder: answering ? askPlaceholder(pendingAsk) : undefined, onArrowUp: answering ? undefined : onArrowUp, onArrowDown: answering ? undefined : onArrowDown }), _jsxs("div", { className: "ac-composer__dock", children: [_jsx(MentionPicker, { open: palette.open, trigger: palette.token?.trigger ?? null, selectedAgent: palette.selectedAgent, items: palette.items, index: palette.index, onMove: palette.move, onPick: submission.pick }), _jsx(ComposerToolbar, { picker: attachmentPicker ?? (_jsx("span", { className: "ac-attach__pick" })), permissionMode: permissionMode, modelSwitch: modelSwitch, onManageModel: onOpenProfile, streaming: status === "streaming", canStop: canStop, empty: empty, onStop: onStop, onSubmit: submitForInput, contextUsage: contextUsage, visionTag: visionTag ?? null, onMic: onMic, micState: micState, trailing: toolbarTrailing })] })] })] }));
|
|
64
|
+
}
|
|
65
|
+
export { focusFieldAt };
|
|
66
|
+
export { ComposerAttachmentsStrip, VisionTag } from "./ComposerAttachments";
|
|
67
|
+
export { ComposerModelChip, ComposerPermChip, } from "./ComposerMenus";
|
|
68
|
+
export { ComposerGrip } from "./ComposerGrip";
|
|
69
|
+
export { ComposerInput } from "./ComposerInput";
|
|
70
|
+
export { ComposerToolbar } from "./ComposerToolbar";
|
|
71
|
+
export { MentionPicker } from "./MentionPicker";
|
|
72
|
+
export { PromptPop } from "./PromptPop";
|
|
73
|
+
export { QueuedPrompts, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, } from "./QueuedPrompts";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface StagedAttachment {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
url: string;
|
|
6
|
+
kind: "image" | "video" | "file";
|
|
7
|
+
}
|
|
8
|
+
export interface ComposerAttachmentsStripProps {
|
|
9
|
+
items: readonly StagedAttachment[];
|
|
10
|
+
onRemove: (id: string) => void;
|
|
11
|
+
visionTag?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare function ComposerAttachmentsStrip({ items, onRemove, visionTag, }: ComposerAttachmentsStripProps): import("react").JSX.Element | null;
|
|
14
|
+
export declare function VisionTag({ short, full, }: {
|
|
15
|
+
short?: string;
|
|
16
|
+
full: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { FileText, ImageIcon, X } from "lucide-react";
|
|
3
|
+
export function ComposerAttachmentsStrip({ items, onRemove, visionTag, }) {
|
|
4
|
+
if (items.length === 0 && !visionTag)
|
|
5
|
+
return null;
|
|
6
|
+
return (_jsxs("div", { className: "ac-attach__strip", "data-cmp": "ac/attachments", "data-id": "attachments", children: [items.map((item) => (_jsxs("span", { className: "ac-attach__chip", "data-kind": item.kind, children: [item.kind === "image" ? (_jsx("img", { className: "ac-attach__chip-thumb", src: item.url, alt: "" })) : item.kind === "video" ? (_jsx(ImageIcon, { className: "ac-attach__chip-icon" })) : (_jsx(FileText, { className: "ac-attach__chip-icon" })), _jsx("span", { className: "ac-attach__chip-name", children: item.name }), _jsx("button", { type: "button", className: "ac-attach__remove", "aria-label": `移除 ${item.name}`, onClick: () => onRemove(item.id), children: _jsx(X, { size: 10 }) })] }, item.id))), visionTag ? (_jsx("span", { className: "ac-attach__note", "data-tone": "warn", children: visionTag })) : null] }));
|
|
7
|
+
}
|
|
8
|
+
export function VisionTag({ short = "仅限文本", full, }) {
|
|
9
|
+
return (_jsx("span", { className: "ac-vision-tag", "data-state": "no", title: full, children: short }));
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ComposerGrip(): import("react").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState, } from "react";
|
|
3
|
+
import { useDisplayStore } from "../../stores/display-store";
|
|
4
|
+
import { WIDTH_MAX, WIDTH_MIN } from "../../types/display";
|
|
5
|
+
const STEP = 24;
|
|
6
|
+
export function ComposerGrip() {
|
|
7
|
+
const [dragging, setDragging] = useState(false);
|
|
8
|
+
const startRef = useRef({ x: 0, w: 0 });
|
|
9
|
+
const setComposerWidthPx = useDisplayStore((s) => s.setComposerWidthPx);
|
|
10
|
+
const onPointerDown = useCallback((e) => {
|
|
11
|
+
const card = e.currentTarget.closest(".ac-composer__card");
|
|
12
|
+
if (!card)
|
|
13
|
+
return;
|
|
14
|
+
startRef.current = { x: e.clientX, w: card.offsetWidth };
|
|
15
|
+
setDragging(true);
|
|
16
|
+
try {
|
|
17
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}, []);
|
|
23
|
+
const onPointerMove = useCallback((e) => {
|
|
24
|
+
if (!dragging)
|
|
25
|
+
return;
|
|
26
|
+
const { x, w } = startRef.current;
|
|
27
|
+
const next = Math.max(WIDTH_MIN, Math.min(WIDTH_MAX, w + (e.clientX - x) * 2));
|
|
28
|
+
setComposerWidthPx(next);
|
|
29
|
+
}, [dragging, setComposerWidthPx]);
|
|
30
|
+
const endDrag = useCallback(() => setDragging(false), []);
|
|
31
|
+
return (_jsx("button", { type: "button", className: "ac-composer__grip", "data-cmp": "ac/composer-grip", "data-id": "composer-grip", "data-dragging": dragging ? "true" : undefined, role: "separator", "aria-orientation": "horizontal", "aria-valuemin": WIDTH_MIN, "aria-valuemax": WIDTH_MAX, "aria-label": "\u62D6\u52A8\u8C03\u6574\u8F93\u5165\u6846\u5BBD\u5EA6", title: "\u5DE6\u53F3\u62D6\u52A8\u8C03\u6574\u8F93\u5165\u6846\u5BBD\u5EA6 \u00B7 \u53CC\u51FB\u8FD8\u539F\u6863\u4F4D", onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: endDrag, onPointerCancel: endDrag, onDoubleClick: () => setComposerWidthPx(null), onKeyDown: (e) => {
|
|
32
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
33
|
+
return;
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
const card = e.currentTarget.closest(".ac-composer__card");
|
|
36
|
+
const base = card?.offsetWidth ?? WIDTH_MIN;
|
|
37
|
+
setComposerWidthPx(base + (e.key === "ArrowRight" ? STEP : -STEP));
|
|
38
|
+
} }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { PaletteItem, SlashCommand } from "../../types/composer";
|
|
3
|
+
export declare function focusFieldAt(fieldRef: RefObject<HTMLTextAreaElement | null>, pos: number): void;
|
|
4
|
+
export interface ComposerInputProps {
|
|
5
|
+
fieldRef: RefObject<HTMLTextAreaElement | null>;
|
|
6
|
+
value: string;
|
|
7
|
+
onChange: (v: string) => void;
|
|
8
|
+
palette: import("../../types/composer").ComposerPaletteLike;
|
|
9
|
+
onAccept: (item: PaletteItem) => void;
|
|
10
|
+
onSubmit: () => void;
|
|
11
|
+
answering?: boolean;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
onArrowUp?: () => boolean;
|
|
14
|
+
onArrowDown?: () => boolean;
|
|
15
|
+
onNotice?: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare const ComposerInput: import("react").ForwardRefExoticComponent<ComposerInputProps & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
18
|
+
export type { SlashCommand };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, } from "react";
|
|
3
|
+
import { Textarea } from "../../primitives/Textarea";
|
|
4
|
+
import { paletteKeyDownAction } from "../../domain/composer-palette";
|
|
5
|
+
export function focusFieldAt(fieldRef, pos) {
|
|
6
|
+
requestAnimationFrame(() => {
|
|
7
|
+
const el = fieldRef.current;
|
|
8
|
+
if (!el)
|
|
9
|
+
return;
|
|
10
|
+
el.focus();
|
|
11
|
+
el.setSelectionRange(pos, pos);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export const ComposerInput = forwardRef(function ComposerInput({ fieldRef, value, onChange, palette, onAccept, onSubmit, answering, placeholder = "输入消息,@ 唤起 Agent,/ 唤起命令…", onArrowUp, onArrowDown, }, _ref) {
|
|
15
|
+
return (_jsx(Textarea, { ref: fieldRef, className: "ac-composer__field", value: value, placeholder: placeholder, "aria-label": answering ? "回答输入框" : "消息输入框", "data-answering": answering || undefined, rows: 1, onChange: (e) => {
|
|
16
|
+
onChange(e.target.value);
|
|
17
|
+
palette.sync(e.target, e.target.value);
|
|
18
|
+
}, onKeyDown: (e) => {
|
|
19
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229)
|
|
20
|
+
return;
|
|
21
|
+
if (palette.open) {
|
|
22
|
+
const action = paletteKeyDownAction(e.key, e.shiftKey);
|
|
23
|
+
if (action === "move-next") {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
palette.move(1);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (action === "move-prev") {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
palette.move(-1);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (action === "accept") {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
const item = palette.items[palette.index];
|
|
36
|
+
if (item)
|
|
37
|
+
onAccept(item);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (action === "close") {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
palette.escape();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (e.key === "ArrowUp" && onArrowUp?.()) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (e.key === "ArrowDown" && onArrowDown?.()) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
onSubmit();
|
|
58
|
+
}
|
|
59
|
+
} }));
|
|
60
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComposerModelOption, ComposerModelSwitch, PermissionModeSwitch } from "../../types/composer";
|
|
2
|
+
export type { ComposerModelSwitch, PermissionModeSwitch };
|
|
3
|
+
export type { ComposerModelOption };
|
|
4
|
+
export declare function ComposerPermChip({ permissionMode, }: {
|
|
5
|
+
permissionMode: PermissionModeSwitch;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function ComposerModelChip({ modelSwitch, onManage, }: {
|
|
8
|
+
modelSwitch: ComposerModelSwitch;
|
|
9
|
+
onManage?: () => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { AlertTriangle, Check, ChevronDown, Cpu, Hand, Pencil, Settings, SquarePen, Zap, } from "lucide-react";
|
|
3
|
+
import { DropdownMenu, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, } from "../../primitives/DropdownMenu";
|
|
4
|
+
const PERM_MODES = [
|
|
5
|
+
{
|
|
6
|
+
value: "ask",
|
|
7
|
+
label: "询问",
|
|
8
|
+
hint: "每次询问:需确认的工具一律弹审批卡",
|
|
9
|
+
icon: Hand,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
value: "auto",
|
|
13
|
+
label: "编辑",
|
|
14
|
+
hint: "允许编辑:文件/画布/MCP 编辑类放行,命令执行仍需确认",
|
|
15
|
+
icon: SquarePen,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "yolo",
|
|
19
|
+
label: "自动",
|
|
20
|
+
hint: "全自动:除显式拒绝规则外全部放行(全权委托)",
|
|
21
|
+
icon: AlertTriangle,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
export function ComposerPermChip({ permissionMode, }) {
|
|
25
|
+
const current = PERM_MODES.find((m) => m.value === permissionMode.value) ?? PERM_MODES[0];
|
|
26
|
+
const CurrentIcon = current.icon;
|
|
27
|
+
return (_jsx(DropdownMenu, { side: "top", contentClassName: "min-w-56", trigger: () => (_jsxs("span", { className: "ac-composer__chip", "data-cmp": "ac/perm-chip", "data-id": "perm-chip", "data-mode": current.value, title: current.hint, "aria-label": `权限模式:${current.label}`, children: [_jsx(CurrentIcon, { className: "ac-composer__perm-icon" }), _jsx("span", { className: "ac-composer__chip-label", children: current.label }), _jsx(ChevronDown, { className: "ac-composer__perm-caret" })] })), children: ({ close }) => (_jsxs("div", { "data-cmp": "ac/perm-menu", children: [_jsx(DropdownMenuLabel, { children: "\u6743\u9650\u6A21\u5F0F" }), PERM_MODES.map((mode) => {
|
|
28
|
+
const active = mode.value === permissionMode.value;
|
|
29
|
+
const Icon = mode.icon;
|
|
30
|
+
return (_jsxs(DropdownMenuItem, { "data-state": active ? "selected" : "idle", "data-mode": mode.value, selected: active, className: "ac-perm-menu__row", onSelect: () => {
|
|
31
|
+
permissionMode.onChange(mode.value);
|
|
32
|
+
close();
|
|
33
|
+
}, children: [active ? (_jsx(Check, { className: "ac-perm-menu__check" })) : (_jsx("span", { className: "ac-perm-menu__check ac-menu__check-spacer" })), _jsx(Icon, { className: "ac-perm-menu__icon" }), _jsxs("span", { className: "ac-perm-menu__body", children: [_jsx("span", { className: "ac-perm-menu__title", children: mode.label }), _jsx("span", { className: "ac-perm-menu__hint", children: mode.hint })] })] }, mode.value));
|
|
34
|
+
})] })) }));
|
|
35
|
+
}
|
|
36
|
+
const BADGE_TONE = {
|
|
37
|
+
neutral: "ac-model-badge",
|
|
38
|
+
warn: "ac-model-badge",
|
|
39
|
+
accent: "ac-model-badge",
|
|
40
|
+
danger: "ac-model-badge",
|
|
41
|
+
};
|
|
42
|
+
function groupOptions(options) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const opt of options) {
|
|
45
|
+
const g = opt.group ?? null;
|
|
46
|
+
const last = out[out.length - 1];
|
|
47
|
+
if (last && last.name === g)
|
|
48
|
+
last.items.push(opt);
|
|
49
|
+
else
|
|
50
|
+
out.push({ name: g, items: [opt] });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function ModelRow({ option, active, onSelect, }) {
|
|
55
|
+
return (_jsx(DropdownMenuItem, { "data-state": active ? "selected" : "idle", selected: active, onSelect: onSelect, children: _jsxs("span", { className: "ac-model-row", children: [_jsxs("span", { className: "ac-model-tile", children: [_jsx(Zap, { className: "ac-model-menu__icon" }), active ? (_jsx("span", { className: "ac-model-tile__badge", children: _jsx(Check, { className: "size-3" }) })) : null] }), _jsxs("span", { className: "ac-model-menu__meta", children: [_jsxs("span", { className: "ac-model-menu__name-line", children: [_jsx("span", { className: "ac-model-menu__name", children: option.label }), option.badge ? (_jsx("span", { className: BADGE_TONE[option.badgeTone ?? "neutral"], "data-tone": option.badgeTone ?? "neutral", children: option.badge })) : null] }), option.hint ? (_jsx("span", { className: "ac-model-menu__hint", children: option.hint })) : null] }), option.multiplier ? (_jsx("span", { className: "ac-model-menu__multiplier", children: option.multiplier })) : null] }) }));
|
|
56
|
+
}
|
|
57
|
+
export function ComposerModelChip({ modelSwitch, onManage, }) {
|
|
58
|
+
const selected = modelSwitch.options.find((o) => o.id === modelSwitch.value);
|
|
59
|
+
const groups = groupOptions(modelSwitch.options);
|
|
60
|
+
return (_jsx(DropdownMenu, { side: "top", align: "end", contentClassName: "ac-model-menu min-w-64 max-w-80", trigger: () => (_jsxs("span", { className: "ac-composer__chip", "data-cmp": "ac/model-chip", "data-id": "model-chip", "aria-label": "\u5207\u6362\u672C\u4F1A\u8BDD\u6A21\u578B", title: "\u5207\u6362\u672C\u4F1A\u8BDD\u6A21\u578B\uFF08\u4E0D\u5F71\u54CD\u5168\u5C40\u6FC0\u6D3B\u6001\uFF09", children: [_jsx(Cpu, { className: "ac-composer__perm-icon" }), _jsx("span", { className: "ac-composer__chip-label", children: modelSwitch.value === null
|
|
61
|
+
? "跟随全局"
|
|
62
|
+
: (selected?.label ?? modelSwitch.value) }), _jsx(ChevronDown, { className: "ac-composer__perm-caret" })] })), children: ({ close }) => (_jsxs("div", { "data-cmp": "ac/model-menu", children: [_jsx(DropdownMenuLabel, { children: "\u672C\u4F1A\u8BDD\u6A21\u578B" }), _jsxs("div", { className: "ac-model-menu__list", children: [_jsx(DropdownMenuItem, { "data-state": modelSwitch.value === null ? "selected" : "idle", selected: modelSwitch.value === null, onSelect: () => {
|
|
63
|
+
modelSwitch.onPick(null);
|
|
64
|
+
close();
|
|
65
|
+
}, children: _jsxs("span", { className: "ac-model-row", children: [_jsxs("span", { className: "ac-model-tile", children: [_jsx(Cpu, { className: "ac-model-menu__icon" }), modelSwitch.value === null ? (_jsx("span", { className: "ac-model-tile__badge", children: _jsx(Check, { className: "size-3" }) })) : null] }), _jsxs("span", { className: "ac-model-menu__meta", children: [_jsx("span", { className: "ac-model-menu__name", children: "\u8DDF\u968F\u5168\u5C40\u9ED8\u8BA4" }), _jsx("span", { className: "ac-model-menu__hint", children: "\u4E0D\u5199\u89D2\u8272\u7EA7\u6A21\u578B\u503C" })] })] }) }), groups.map((group) => (_jsxs("div", { children: [_jsx(DropdownMenuSeparator, {}), group.name ? (_jsx(DropdownMenuLabel, { className: "ac-model-menu__group", children: group.name })) : null, group.items.map((option) => {
|
|
66
|
+
const active = option.id === modelSwitch.value;
|
|
67
|
+
return (_jsx(ModelRow, { option: option, active: active, onSelect: () => {
|
|
68
|
+
modelSwitch.onPick(option.id);
|
|
69
|
+
close();
|
|
70
|
+
} }, option.id));
|
|
71
|
+
})] }, group.name ?? "__default")))] }), modelSwitch.onConfigureCustom ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: () => {
|
|
72
|
+
modelSwitch.onConfigureCustom?.();
|
|
73
|
+
close();
|
|
74
|
+
}, children: [_jsx(Pencil, { className: "size-3.5" }), "\u914D\u7F6E\u81EA\u5B9A\u4E49\u6A21\u578B"] })] })) : null, onManage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: () => {
|
|
75
|
+
onManage();
|
|
76
|
+
close();
|
|
77
|
+
}, children: [_jsx(Settings, { className: "size-3.5" }), "\u6A21\u578B\u7BA1\u7406"] })] })) : null] })) }));
|
|
78
|
+
}
|