@fromsko/chat-ui 0.0.0-stage → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -2
  3. package/lib/adapters/event-bus.d.ts +80 -0
  4. package/lib/adapters/event-bus.js +73 -0
  5. package/lib/adapters/index.d.ts +3 -0
  6. package/lib/adapters/index.js +3 -0
  7. package/lib/adapters/labels.d.ts +6 -0
  8. package/lib/adapters/labels.js +17 -0
  9. package/lib/adapters/stream-bridge.d.ts +9 -0
  10. package/lib/adapters/stream-bridge.js +7 -0
  11. package/lib/blocks/cards/AskCard.d.ts +8 -0
  12. package/lib/blocks/cards/AskCard.js +97 -0
  13. package/lib/blocks/cards/CompactionCard.d.ts +5 -0
  14. package/lib/blocks/cards/CompactionCard.js +5 -0
  15. package/lib/blocks/cards/ContextUsage.d.ts +14 -0
  16. package/lib/blocks/cards/ContextUsage.js +136 -0
  17. package/lib/blocks/cards/DisplayCard.d.ts +7 -0
  18. package/lib/blocks/cards/DisplayCard.js +27 -0
  19. package/lib/blocks/cards/MemoryPill.d.ts +5 -0
  20. package/lib/blocks/cards/MemoryPill.js +5 -0
  21. package/lib/blocks/cards/OutputCard.d.ts +5 -0
  22. package/lib/blocks/cards/OutputCard.js +13 -0
  23. package/lib/blocks/cards/PermissionCard.d.ts +21 -0
  24. package/lib/blocks/cards/PermissionCard.js +26 -0
  25. package/lib/blocks/cards/SpringCheck.d.ts +9 -0
  26. package/lib/blocks/cards/SpringCheck.js +17 -0
  27. package/lib/blocks/cards/TodoCard.d.ts +19 -0
  28. package/lib/blocks/cards/TodoCard.js +28 -0
  29. package/lib/blocks/cards/index.d.ts +10 -0
  30. package/lib/blocks/cards/index.js +10 -0
  31. package/lib/blocks/composer/Composer.d.ts +66 -0
  32. package/lib/blocks/composer/Composer.js +73 -0
  33. package/lib/blocks/composer/ComposerAttachments.d.ts +17 -0
  34. package/lib/blocks/composer/ComposerAttachments.js +10 -0
  35. package/lib/blocks/composer/ComposerGrip.d.ts +1 -0
  36. package/lib/blocks/composer/ComposerGrip.js +39 -0
  37. package/lib/blocks/composer/ComposerInput.d.ts +18 -0
  38. package/lib/blocks/composer/ComposerInput.js +60 -0
  39. package/lib/blocks/composer/ComposerMenus.d.ts +10 -0
  40. package/lib/blocks/composer/ComposerMenus.js +78 -0
  41. package/lib/blocks/composer/ComposerToolbar.d.ts +27 -0
  42. package/lib/blocks/composer/ComposerToolbar.js +16 -0
  43. package/lib/blocks/composer/MentionPicker.d.ts +13 -0
  44. package/lib/blocks/composer/MentionPicker.js +71 -0
  45. package/lib/blocks/composer/PromptPop.d.ts +7 -0
  46. package/lib/blocks/composer/PromptPop.js +40 -0
  47. package/lib/blocks/composer/QueuedPrompts.d.ts +14 -0
  48. package/lib/blocks/composer/QueuedPrompts.js +21 -0
  49. package/lib/blocks/composer/SlingButton.d.ts +10 -0
  50. package/lib/blocks/composer/SlingButton.js +112 -0
  51. package/lib/blocks/composer/index.d.ts +7 -0
  52. package/lib/blocks/composer/index.js +1 -0
  53. package/lib/blocks/conversation/ChatEmptyScene.d.ts +4 -0
  54. package/lib/blocks/conversation/ChatEmptyScene.js +21 -0
  55. package/lib/blocks/conversation/ChatHeader.d.ts +13 -0
  56. package/lib/blocks/conversation/ChatHeader.js +20 -0
  57. package/lib/blocks/conversation/ChatPane.d.ts +90 -0
  58. package/lib/blocks/conversation/ChatPane.js +60 -0
  59. package/lib/blocks/conversation/Conversation.d.ts +11 -0
  60. package/lib/blocks/conversation/Conversation.js +41 -0
  61. package/lib/blocks/conversation/LineSidebar.d.ts +14 -0
  62. package/lib/blocks/conversation/LineSidebar.js +42 -0
  63. package/lib/blocks/conversation/index.d.ts +5 -0
  64. package/lib/blocks/conversation/index.js +5 -0
  65. package/lib/blocks/index.d.ts +9 -0
  66. package/lib/blocks/index.js +9 -0
  67. package/lib/blocks/message/DayDivider.d.ts +10 -0
  68. package/lib/blocks/message/DayDivider.js +29 -0
  69. package/lib/blocks/message/DiffView.d.ts +5 -0
  70. package/lib/blocks/message/DiffView.js +18 -0
  71. package/lib/blocks/message/MermaidDiagram.d.ts +4 -0
  72. package/lib/blocks/message/MermaidDiagram.js +43 -0
  73. package/lib/blocks/message/Message.d.ts +17 -0
  74. package/lib/blocks/message/Message.js +45 -0
  75. package/lib/blocks/message/MessageActionBar.d.ts +18 -0
  76. package/lib/blocks/message/MessageActionBar.js +15 -0
  77. package/lib/blocks/message/MessageArtifacts.d.ts +13 -0
  78. package/lib/blocks/message/MessageArtifacts.js +9 -0
  79. package/lib/blocks/message/MessageAttachments.d.ts +5 -0
  80. package/lib/blocks/message/MessageAttachments.js +28 -0
  81. package/lib/blocks/message/MessageBubble.d.ts +11 -0
  82. package/lib/blocks/message/MessageBubble.js +31 -0
  83. package/lib/blocks/message/MessageRow.d.ts +20 -0
  84. package/lib/blocks/message/MessageRow.js +49 -0
  85. package/lib/blocks/message/MessageStream.d.ts +23 -0
  86. package/lib/blocks/message/MessageStream.js +80 -0
  87. package/lib/blocks/message/MessageTrace.d.ts +16 -0
  88. package/lib/blocks/message/MessageTrace.js +39 -0
  89. package/lib/blocks/message/PartView.d.ts +10 -0
  90. package/lib/blocks/message/PartView.js +29 -0
  91. package/lib/blocks/message/code-text.d.ts +15 -0
  92. package/lib/blocks/message/code-text.js +17 -0
  93. package/lib/blocks/message/index.d.ts +12 -0
  94. package/lib/blocks/message/index.js +12 -0
  95. package/lib/blocks/reasoning/Reasoning.d.ts +7 -0
  96. package/lib/blocks/reasoning/Reasoning.js +26 -0
  97. package/lib/blocks/reasoning/ReasoningBlock.d.ts +6 -0
  98. package/lib/blocks/reasoning/ReasoningBlock.js +5 -0
  99. package/lib/blocks/reasoning/index.d.ts +2 -0
  100. package/lib/blocks/reasoning/index.js +2 -0
  101. package/lib/blocks/selection/SelectionActions.d.ts +12 -0
  102. package/lib/blocks/selection/SelectionActions.js +29 -0
  103. package/lib/blocks/selection/SelectionBar.d.ts +10 -0
  104. package/lib/blocks/selection/SelectionBar.js +6 -0
  105. package/lib/blocks/selection/index.d.ts +2 -0
  106. package/lib/blocks/selection/index.js +2 -0
  107. package/lib/blocks/settings/DisplaySettingsPanel.d.ts +5 -0
  108. package/lib/blocks/settings/DisplaySettingsPanel.js +33 -0
  109. package/lib/blocks/settings/SquishSwitch.d.ts +8 -0
  110. package/lib/blocks/settings/SquishSwitch.js +21 -0
  111. package/lib/blocks/settings/index.d.ts +2 -0
  112. package/lib/blocks/settings/index.js +2 -0
  113. package/lib/blocks/status/LatticeLoader.d.ts +12 -0
  114. package/lib/blocks/status/LatticeLoader.js +22 -0
  115. package/lib/blocks/status/TypingIndicator.d.ts +5 -0
  116. package/lib/blocks/status/TypingIndicator.js +5 -0
  117. package/lib/blocks/status/index.d.ts +2 -0
  118. package/lib/blocks/status/index.js +2 -0
  119. package/lib/blocks/tool/SandboxCard.d.ts +5 -0
  120. package/lib/blocks/tool/SandboxCard.js +10 -0
  121. package/lib/blocks/tool/ToolCard.d.ts +10 -0
  122. package/lib/blocks/tool/ToolCard.js +32 -0
  123. package/lib/blocks/tool/ToolGroup.d.ts +6 -0
  124. package/lib/blocks/tool/ToolGroup.js +23 -0
  125. package/lib/blocks/tool/exec-envelope.d.ts +7 -0
  126. package/lib/blocks/tool/exec-envelope.js +43 -0
  127. package/lib/blocks/tool/index.d.ts +4 -0
  128. package/lib/blocks/tool/index.js +4 -0
  129. package/lib/domain/ask.d.ts +14 -0
  130. package/lib/domain/ask.js +50 -0
  131. package/lib/domain/attachments.d.ts +16 -0
  132. package/lib/domain/attachments.js +126 -0
  133. package/lib/domain/attribution.d.ts +4 -0
  134. package/lib/domain/attribution.js +11 -0
  135. package/lib/domain/composer-palette.d.ts +15 -0
  136. package/lib/domain/composer-palette.js +131 -0
  137. package/lib/domain/index.d.ts +12 -0
  138. package/lib/domain/index.js +12 -0
  139. package/lib/domain/mention.d.ts +17 -0
  140. package/lib/domain/mention.js +95 -0
  141. package/lib/domain/message-blocks.d.ts +29 -0
  142. package/lib/domain/message-blocks.js +63 -0
  143. package/lib/domain/message-menu.d.ts +4 -0
  144. package/lib/domain/message-menu.js +46 -0
  145. package/lib/domain/queue.d.ts +23 -0
  146. package/lib/domain/queue.js +52 -0
  147. package/lib/domain/reducer.d.ts +137 -0
  148. package/lib/domain/reducer.js +442 -0
  149. package/lib/domain/selection-actions.d.ts +13 -0
  150. package/lib/domain/selection-actions.js +56 -0
  151. package/lib/domain/selection-gates.d.ts +6 -0
  152. package/lib/domain/selection-gates.js +36 -0
  153. package/lib/domain/slash-commands.d.ts +16 -0
  154. package/lib/domain/slash-commands.js +142 -0
  155. package/lib/hooks/index.d.ts +8 -0
  156. package/lib/hooks/index.js +8 -0
  157. package/lib/hooks/useComposerPalette.d.ts +35 -0
  158. package/lib/hooks/useComposerPalette.js +120 -0
  159. package/lib/hooks/useComposerSubmit.d.ts +31 -0
  160. package/lib/hooks/useComposerSubmit.js +135 -0
  161. package/lib/hooks/useInputHistory.d.ts +14 -0
  162. package/lib/hooks/useInputHistory.js +52 -0
  163. package/lib/hooks/usePendingAsk.d.ts +11 -0
  164. package/lib/hooks/usePendingAsk.js +33 -0
  165. package/lib/hooks/usePendingPermissions.d.ts +10 -0
  166. package/lib/hooks/usePendingPermissions.js +26 -0
  167. package/lib/hooks/useQueuedPrompts.d.ts +12 -0
  168. package/lib/hooks/useQueuedPrompts.js +91 -0
  169. package/lib/hooks/useSelectionPop.d.ts +13 -0
  170. package/lib/hooks/useSelectionPop.js +130 -0
  171. package/lib/hooks/useStreamBridge.d.ts +1 -0
  172. package/lib/hooks/useStreamBridge.js +23 -0
  173. package/lib/index.d.ts +10 -0
  174. package/lib/index.js +10 -0
  175. package/lib/primitives/Badge.d.ts +7 -0
  176. package/lib/primitives/Badge.js +13 -0
  177. package/lib/primitives/Button.d.ts +9 -0
  178. package/lib/primitives/Button.js +21 -0
  179. package/lib/primitives/Collapsible.d.ts +12 -0
  180. package/lib/primitives/Collapsible.js +14 -0
  181. package/lib/primitives/Dialog.d.ts +10 -0
  182. package/lib/primitives/Dialog.js +19 -0
  183. package/lib/primitives/DropdownMenu.d.ts +28 -0
  184. package/lib/primitives/DropdownMenu.js +37 -0
  185. package/lib/primitives/Empty.d.ts +8 -0
  186. package/lib/primitives/Empty.js +6 -0
  187. package/lib/primitives/Input.d.ts +3 -0
  188. package/lib/primitives/Input.js +6 -0
  189. package/lib/primitives/Kbd.d.ts +2 -0
  190. package/lib/primitives/Kbd.js +5 -0
  191. package/lib/primitives/Popover.d.ts +15 -0
  192. package/lib/primitives/Popover.js +32 -0
  193. package/lib/primitives/ScrollArea.d.ts +5 -0
  194. package/lib/primitives/ScrollArea.js +5 -0
  195. package/lib/primitives/Separator.d.ts +5 -0
  196. package/lib/primitives/Separator.js +5 -0
  197. package/lib/primitives/Spinner.d.ts +5 -0
  198. package/lib/primitives/Spinner.js +5 -0
  199. package/lib/primitives/Textarea.d.ts +3 -0
  200. package/lib/primitives/Textarea.js +6 -0
  201. package/lib/primitives/Tooltip.d.ts +8 -0
  202. package/lib/primitives/Tooltip.js +7 -0
  203. package/lib/primitives/index.d.ts +14 -0
  204. package/lib/primitives/index.js +14 -0
  205. package/lib/reactbits/Motion.d.ts +24 -0
  206. package/lib/reactbits/Motion.js +52 -0
  207. package/lib/reactbits/index.d.ts +1 -0
  208. package/lib/reactbits/index.js +1 -0
  209. package/lib/stores/chat-store.d.ts +33 -0
  210. package/lib/stores/chat-store.js +93 -0
  211. package/lib/stores/display-store.d.ts +15 -0
  212. package/lib/stores/display-store.js +116 -0
  213. package/lib/stores/index.d.ts +3 -0
  214. package/lib/stores/index.js +3 -0
  215. package/lib/stores/theme-store.d.ts +8 -0
  216. package/lib/stores/theme-store.js +29 -0
  217. package/lib/tokens/index.d.ts +21 -0
  218. package/lib/tokens/index.js +66 -0
  219. package/lib/types/agent.d.ts +62 -0
  220. package/lib/types/agent.js +1 -0
  221. package/lib/types/callbacks.d.ts +55 -0
  222. package/lib/types/callbacks.js +1 -0
  223. package/lib/types/composer.d.ts +116 -0
  224. package/lib/types/composer.js +1 -0
  225. package/lib/types/conversation.d.ts +54 -0
  226. package/lib/types/conversation.js +1 -0
  227. package/lib/types/display.d.ts +25 -0
  228. package/lib/types/display.js +40 -0
  229. package/lib/types/index.d.ts +8 -0
  230. package/lib/types/index.js +8 -0
  231. package/lib/types/message.d.ts +100 -0
  232. package/lib/types/message.js +1 -0
  233. package/lib/types/selection.d.ts +60 -0
  234. package/lib/types/selection.js +1 -0
  235. package/lib/types/theme.d.ts +100 -0
  236. package/lib/types/theme.js +1 -0
  237. package/lib/utils/clipboard.d.ts +4 -0
  238. package/lib/utils/clipboard.js +41 -0
  239. package/lib/utils/cn.d.ts +2 -0
  240. package/lib/utils/cn.js +14 -0
  241. package/lib/utils/index.d.ts +3 -0
  242. package/lib/utils/index.js +3 -0
  243. package/lib/utils/storage.d.ts +11 -0
  244. package/lib/utils/storage.js +48 -0
  245. package/package.json +93 -4
  246. package/src/styles/blocks.css +2439 -0
  247. package/src/styles/index.css +3 -0
  248. package/src/tokens/theme.css +464 -0
@@ -0,0 +1,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 type { MemoryPart } from "../../types/message";
2
+ export interface MemoryPillProps {
3
+ part: MemoryPart;
4
+ }
5
+ export declare function MemoryPill({ part }: MemoryPillProps): import("react").JSX.Element;
@@ -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,5 @@
1
+ import type { OutputPart } from "../../types/message";
2
+ export interface OutputCardProps {
3
+ part: OutputPart;
4
+ }
5
+ export declare function OutputCard({ part }: OutputCardProps): import("react").JSX.Element;
@@ -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
+ }