@moldable-ai/ui 0.2.7 → 0.2.8

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.
@@ -20,7 +20,8 @@ type ChatInputProps = {
20
20
  onRemoveImageAttachment?: (id: string) => void;
21
21
  footerStartControls?: ReactNode;
22
22
  footerControls?: ReactNode;
23
+ submitDisabled?: boolean;
23
24
  };
24
- export declare function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder, inputRef, onStop, compact, imageAttachments, onRemoveImageAttachment, footerStartControls, footerControls, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder, inputRef, onStop, compact, imageAttachments, onRemoveImageAttachment, footerStartControls, footerControls, submitDisabled, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
25
26
  export {};
26
27
  //# sourceMappingURL=chat-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-input.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EAGT,SAAS,EACT,SAAS,EACV,MAAM,OAAO,CAAA;AAKd,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAA;IAC5D,QAAQ,EAAE,CACR,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,cAAc,KAClB,IAAI,CAAA;IACT,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAA;IACxC,uBAAuB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C,mBAAmB,CAAC,EAAE,SAAS,CAAA;IAC/B,cAAc,CAAC,EAAE,SAAS,CAAA;CAC3B,CAAA;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAA+B,EAC/B,QAAQ,EACR,MAAM,EACN,OAAe,EACf,gBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,cAAc,GACf,EAAE,cAAc,2CA6IhB"}
1
+ {"version":3,"file":"chat-input.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EAGT,SAAS,EACT,SAAS,EACV,MAAM,OAAO,CAAA;AAKd,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAA;IAC5D,QAAQ,EAAE,CACR,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,cAAc,KAClB,IAAI,CAAA;IACT,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,CAAA;IACxC,uBAAuB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C,mBAAmB,CAAC,EAAE,SAAS,CAAA;IAC/B,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAA+B,EAC/B,QAAQ,EACR,MAAM,EACN,OAAe,EACf,gBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,cAAc,EACd,cAAsB,GACvB,EAAE,cAAc,2CAqJhB"}
@@ -3,10 +3,10 @@ import { ArrowUp, Image as ImageIcon, Square, X } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
5
  import { Textarea } from '../ui/textarea';
6
- export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder = 'Ask anything...', inputRef, onStop, compact = false, imageAttachments = [], onRemoveImageAttachment, footerStartControls, footerControls, }) {
6
+ export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder = 'Ask anything...', inputRef, onStop, compact = false, imageAttachments = [], onRemoveImageAttachment, footerStartControls, footerControls, submitDisabled = false, }) {
7
7
  const hasInput = input.trim().length > 0;
8
8
  const hasAttachments = imageAttachments.length > 0;
9
- const canSubmit = hasInput || hasAttachments;
9
+ const canSubmit = !submitDisabled && (hasInput || hasAttachments);
10
10
  const handleKeyDown = (e) => {
11
11
  if (e.key === 'Enter') {
12
12
  if (e.shiftKey) {
@@ -41,7 +41,13 @@ export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeh
41
41
  }
42
42
  // If type="submit", form handles submission
43
43
  };
44
- return (_jsx("div", { className: cn('isolate w-full p-2'), "data-chat-input-wrapper": true, children: _jsx("div", { className: "mx-auto max-w-5xl", children: _jsx("form", { onSubmit: (e) => onSubmit(e, isResponding ? 'queue' : 'send'), children: _jsxs("div", { className: cn('bg-background relative flex w-full flex-col rounded-3xl border', compact && !hasAttachments && 'h-14'), children: [!compact && hasAttachments && (_jsx("div", { className: "flex max-h-32 gap-2 overflow-x-auto px-3 pt-3", children: imageAttachments.map((attachment) => (_jsxs("div", { className: "bg-muted/70 border-border flex max-w-48 shrink-0 items-center gap-2 rounded-xl border p-1.5 pr-2", children: [_jsx("div", { className: "bg-background flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-lg border", children: attachment.previewUrl ? (_jsx("img", { src: attachment.previewUrl, alt: "", className: "size-full object-cover", draggable: false })) : (_jsx(ImageIcon, { className: "text-muted-foreground size-4" })) }), _jsx("span", { className: "min-w-0 truncate text-xs font-medium", title: attachment.path, children: attachment.name }), onRemoveImageAttachment && (_jsx("button", { type: "button", onClick: () => onRemoveImageAttachment(attachment.id), className: "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors", "aria-label": `Remove ${attachment.name}`, children: _jsx(X, { className: "size-3.5" }) }))] }, attachment.id))) })), _jsx("div", { className: cn('w-full px-5', compact ? 'py-4' : 'pb-2 pt-4'), children: _jsx(Textarea, { ref: inputRef, value: input, placeholder: placeholder, onChange: onInputChange, onKeyDown: handleKeyDown, className: cn('max-h-32 min-h-[24px] resize-none rounded-none border-0 !bg-transparent p-0 text-sm shadow-none focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:!bg-transparent', compact && 'max-h-6 min-h-6') }) }), !compact && (_jsxs("div", { className: "flex min-h-11 items-center justify-between gap-3 px-3 pb-3", children: [_jsx("div", { className: "flex min-w-0 shrink-0 items-center justify-start gap-1", children: footerStartControls }), _jsx("div", { className: "min-w-0 flex-1" }), _jsx("div", { className: "flex min-w-0 shrink-0 items-center justify-end gap-1.5", children: footerControls }), _jsxs(Button, { type: showStopButton ? 'button' : 'submit', size: "icon", className: "bg-primary text-primary-foreground size-8 cursor-pointer rounded-full hover:opacity-70 disabled:opacity-30", disabled: !canSubmit && !isResponding, onClick: handleButtonClick, children: [showStopButton ? (_jsx(Square, { className: "fill-primary-foreground size-4 animate-pulse" })) : (_jsx(ArrowUp, { className: "size-4" })), _jsx("span", { className: "sr-only", children: showStopButton
44
+ return (_jsx("div", { className: cn('isolate w-full p-2'), "data-chat-input-wrapper": true, children: _jsx("div", { className: "mx-auto max-w-5xl", children: _jsx("form", { onSubmit: (e) => {
45
+ if (submitDisabled && !isResponding) {
46
+ e.preventDefault();
47
+ return;
48
+ }
49
+ onSubmit(e, isResponding ? 'queue' : 'send');
50
+ }, children: _jsxs("div", { className: cn('bg-background relative flex w-full flex-col rounded-3xl border', compact && !hasAttachments && 'h-14'), children: [!compact && hasAttachments && (_jsx("div", { className: "flex max-h-32 gap-2 overflow-x-auto px-3 pt-3", children: imageAttachments.map((attachment) => (_jsxs("div", { className: "bg-muted/70 border-border flex max-w-48 shrink-0 items-center gap-2 rounded-xl border p-1.5 pr-2", children: [_jsx("div", { className: "bg-background flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-lg border", children: attachment.previewUrl ? (_jsx("img", { src: attachment.previewUrl, alt: "", className: "size-full object-cover", draggable: false })) : (_jsx(ImageIcon, { className: "text-muted-foreground size-4" })) }), _jsx("span", { className: "min-w-0 truncate text-xs font-medium", title: attachment.path, children: attachment.name }), onRemoveImageAttachment && (_jsx("button", { type: "button", onClick: () => onRemoveImageAttachment(attachment.id), className: "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors", "aria-label": `Remove ${attachment.name}`, children: _jsx(X, { className: "size-3.5" }) }))] }, attachment.id))) })), _jsx("div", { className: cn('w-full px-5', compact ? 'py-4' : 'pb-2 pt-4'), children: _jsx(Textarea, { ref: inputRef, value: input, placeholder: placeholder, onChange: onInputChange, onKeyDown: handleKeyDown, className: cn('max-h-32 min-h-[24px] resize-none rounded-none border-0 !bg-transparent p-0 text-sm shadow-none focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:!bg-transparent', compact && 'max-h-6 min-h-6') }) }), !compact && (_jsxs("div", { className: "flex min-h-11 items-center justify-between gap-3 px-3 pb-3", children: [_jsx("div", { className: "flex min-w-0 shrink-0 items-center justify-start gap-1", children: footerStartControls }), _jsx("div", { className: "min-w-0 flex-1" }), _jsx("div", { className: "flex min-w-0 shrink-0 items-center justify-end gap-1.5", children: footerControls }), _jsxs(Button, { type: showStopButton ? 'button' : 'submit', size: "icon", className: "bg-primary text-primary-foreground size-8 cursor-pointer rounded-full hover:opacity-70 disabled:opacity-30", disabled: !canSubmit && !isResponding, onClick: handleButtonClick, children: [showStopButton ? (_jsx(Square, { className: "fill-primary-foreground size-4 animate-pulse" })) : (_jsx(ArrowUp, { className: "size-4" })), _jsx("span", { className: "sr-only", children: showStopButton
45
51
  ? 'Stop generating'
46
52
  : isResponding
47
53
  ? 'Steer response'
@@ -27,6 +27,11 @@ export type ChatMessage = {
27
27
  role: 'user' | 'assistant' | 'system';
28
28
  content?: string;
29
29
  parts?: ChatMessagePart[];
30
+ turn?: {
31
+ userMessageId?: string;
32
+ startedAtMs?: number;
33
+ completedAtMs?: number;
34
+ };
30
35
  };
31
36
  type MessageProps = {
32
37
  message: ChatMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-message.tsx"],"names":[],"mappings":"AAgCA,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAA;QACV,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;CACF,CAAA;AAEL,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B,CAAA;AAED,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAmBD,iBAAS,WAAW,CAAC,EACnB,OAAO,EACP,MAAc,EACd,WAAmB,GACpB,EAAE,YAAY,2CAyed;AAED,eAAO,MAAM,OAAO,yDAAoB,CAAA;AAExC,wBAAgB,eAAe,4CAiB9B"}
1
+ {"version":3,"file":"chat-message.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-message.tsx"],"names":[],"mappings":"AA+BA,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAA;QACV,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;CACF,CAAA;AAEL,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE;QACL,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;CACF,CAAA;AAED,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAopBD,iBAAS,WAAW,CAAC,EACnB,OAAO,EACP,MAAc,EACd,WAAmB,GACpB,EAAE,YAAY,2CAied;AAED,eAAO,MAAM,OAAO,yDAAoB,CAAA;AAExC,wBAAgB,eAAe,4CAc9B"}
@@ -1,58 +1,419 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Brain, ChevronDown, Image as ImageIcon, Loader2 } from 'lucide-react';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown, Image as ImageIcon } from 'lucide-react';
4
4
  import { memo, useEffect, useMemo, useState } from 'react';
5
5
  import { cn } from '../../lib/utils';
6
6
  import { Markdown } from '../markdown';
7
7
  import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '../ui/collapsible';
8
- import { ThinkingTimeline, ThinkingTimelineMarker, } from './thinking-timeline';
9
8
  import { ToolApproval, ToolApprovalHeader, ToolApprovalRejected, } from './tool-approval';
10
9
  import { useToolApprovalResponse } from './tool-approval-context';
11
- import { getToolHandler } from './tool-handlers';
10
+ import { getToolHandler, } from './tool-handlers';
12
11
  import { useToolProgress } from './tool-progress-context';
13
12
  import { AnimatePresence, motion } from 'framer-motion';
14
13
  const DEFAULT_ACTIONS_LABEL = 'Thinking';
15
14
  // OpenRouter sends these for encrypted reasoning msgs
16
15
  const stripRedacted = (text) => text.replace(/\[REDACTED\]/g, '').trim();
16
+ function emptyActivityCounts() {
17
+ return {
18
+ createdFileCount: 0,
19
+ editedFileCount: 0,
20
+ deletedFileCount: 0,
21
+ exploredFileCount: 0,
22
+ searchCount: 0,
23
+ listCount: 0,
24
+ commandCount: 0,
25
+ runningCommandCount: 0,
26
+ webSearchCount: 0,
27
+ apiCallCount: 0,
28
+ skillActionCount: 0,
29
+ appActionCount: 0,
30
+ toolCallCount: 0,
31
+ };
32
+ }
33
+ function incrementActivityCount(counts, key, amount = 1) {
34
+ counts[key] += amount;
35
+ }
36
+ function asRecord(value) {
37
+ return value && typeof value === 'object'
38
+ ? value
39
+ : undefined;
40
+ }
41
+ function stringValue(value) {
42
+ return typeof value === 'string' && value.trim().length > 0
43
+ ? value
44
+ : undefined;
45
+ }
46
+ function getString(record, keys) {
47
+ if (!record) {
48
+ return undefined;
49
+ }
50
+ for (const key of keys) {
51
+ const value = stringValue(record[key]);
52
+ if (value) {
53
+ return value;
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ function getCount(record, keys) {
59
+ if (!record) {
60
+ return undefined;
61
+ }
62
+ for (const key of keys) {
63
+ const value = record[key];
64
+ if (typeof value === 'number' && Number.isFinite(value)) {
65
+ return value;
66
+ }
67
+ if (Array.isArray(value)) {
68
+ return value.length;
69
+ }
70
+ }
71
+ return undefined;
72
+ }
73
+ function getFileName(path) {
74
+ const parts = path.split('/');
75
+ return parts[parts.length - 1] || path;
76
+ }
77
+ function getPathLabel(path) {
78
+ return path ? getFileName(path) : undefined;
79
+ }
80
+ function getToolPath(part) {
81
+ const args = asRecord(part.args);
82
+ const output = asRecord(part.output);
83
+ return (getString(output, ['path', 'filePath', 'targetPath']) ??
84
+ getString(args, ['path', 'filePath', 'targetPath']));
85
+ }
86
+ function getToolQuery(part) {
87
+ const args = asRecord(part.args);
88
+ const output = asRecord(part.output);
89
+ return (getString(args, ['query', 'pattern', 'glob', 'text']) ??
90
+ getString(output, ['query', 'pattern', 'glob']));
91
+ }
92
+ function getToolCommand(part, progress) {
93
+ const args = asRecord(part.args);
94
+ const output = asRecord(part.output);
95
+ return (progress?.command ??
96
+ getString(output, ['command']) ??
97
+ getString(args, ['command']));
98
+ }
99
+ function getToolTarget(part) {
100
+ const args = asRecord(part.args);
101
+ const output = asRecord(part.output);
102
+ return (getString(output, ['appName', 'appId', 'name', 'id']) ??
103
+ getString(args, ['appName', 'appId', 'name', 'id', 'repoName', 'skillName']));
104
+ }
105
+ function isToolLoading(part, progress) {
106
+ const isApprovalResponded = part.state === 'approval-responded' || part.state === 'output-denied';
107
+ const wasApproved = part.approval?.approved === true;
108
+ return (part.state === 'partial-call' ||
109
+ part.state === 'call' ||
110
+ part.state === 'pending' ||
111
+ progress?.status === 'running' ||
112
+ (isApprovalResponded && wasApproved && part.output === undefined));
113
+ }
114
+ function toolHasError(part) {
115
+ const output = asRecord(part.output);
116
+ if (!output) {
117
+ return false;
118
+ }
119
+ return (output.success === false ||
120
+ output.ok === false ||
121
+ (typeof output.exitCode === 'number' && output.exitCode !== 0) ||
122
+ Boolean(output.error));
123
+ }
124
+ function plural(count, singular, pluralForm = `${singular}s`) {
125
+ return `${count} ${count === 1 ? singular : pluralForm}`;
126
+ }
127
+ function sentenceCase(text) {
128
+ return text.length > 0 ? text.charAt(0).toUpperCase() + text.slice(1) : text;
129
+ }
130
+ function formatActivitySummary(counts, fallback, isStreaming) {
131
+ const segments = [];
132
+ const completedCommands = Math.max(0, counts.commandCount - counts.runningCommandCount);
133
+ const explorationDetails = [
134
+ counts.exploredFileCount > 0
135
+ ? plural(counts.exploredFileCount, 'file')
136
+ : null,
137
+ counts.searchCount > 0
138
+ ? plural(counts.searchCount, 'search', 'searches')
139
+ : null,
140
+ counts.listCount > 0
141
+ ? plural(counts.listCount, 'directory', 'directories')
142
+ : null,
143
+ ].filter(Boolean);
144
+ if (counts.createdFileCount > 0) {
145
+ segments.push(`created ${plural(counts.createdFileCount, 'file')}`);
146
+ }
147
+ if (counts.editedFileCount > 0) {
148
+ segments.push(`edited ${plural(counts.editedFileCount, 'file')}`);
149
+ }
150
+ if (counts.deletedFileCount > 0) {
151
+ segments.push(`deleted ${plural(counts.deletedFileCount, 'file')}`);
152
+ }
153
+ if (explorationDetails.length > 0) {
154
+ segments.push(`explored ${explorationDetails.join(', ')}`);
155
+ }
156
+ if (completedCommands > 0) {
157
+ segments.push(`ran ${plural(completedCommands, 'command')}`);
158
+ }
159
+ if (counts.runningCommandCount > 0) {
160
+ segments.push(`running ${plural(counts.runningCommandCount, 'command')}`);
161
+ }
162
+ if (counts.webSearchCount > 0) {
163
+ segments.push(`ran ${plural(counts.webSearchCount, 'web search', 'web searches')}`);
164
+ }
165
+ if (counts.apiCallCount > 0) {
166
+ segments.push(`called ${plural(counts.apiCallCount, 'app API', 'app APIs')}`);
167
+ }
168
+ if (counts.skillActionCount > 0) {
169
+ segments.push(`updated ${plural(counts.skillActionCount, 'skill setting')}`);
170
+ }
171
+ if (counts.appActionCount > 0) {
172
+ segments.push(`updated ${plural(counts.appActionCount, 'app')}`);
173
+ }
174
+ if (counts.toolCallCount > 0) {
175
+ segments.push(`used ${plural(counts.toolCallCount, 'tool')}`);
176
+ }
177
+ if (segments.length === 0) {
178
+ return isStreaming ? fallback : fallback || DEFAULT_ACTIONS_LABEL;
179
+ }
180
+ return sentenceCase(segments.join(', '));
181
+ }
182
+ function ActivityCode({ children, title, }) {
183
+ return (_jsx("code", { className: "text-foreground/80 inline max-w-full truncate align-baseline font-mono text-[0.95em]", title: title, children: children }));
184
+ }
185
+ function toolActivityText({ verb, path, detail, fallback, }) {
186
+ const label = path ?? detail;
187
+ return (_jsxs(_Fragment, { children: [_jsx("span", { children: verb }), label ? (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: path, children: label })] })) : fallback ? (_jsxs(_Fragment, { children: [" ", fallback] })) : null] }));
188
+ }
189
+ function summarizeToolActivity(part, toolHandler, progress, fallbackId) {
190
+ const counts = emptyActivityCounts();
191
+ const running = isToolLoading(part, progress);
192
+ const hasError = toolHasError(part);
193
+ const path = getToolPath(part);
194
+ const pathLabel = getPathLabel(path);
195
+ const query = getToolQuery(part);
196
+ const command = getToolCommand(part, progress);
197
+ const target = getToolTarget(part);
198
+ const output = asRecord(part.output);
199
+ const args = asRecord(part.args);
200
+ const entryId = part.toolCallId ?? fallbackId ?? `${part.toolName}-${part.state}`;
201
+ const loadingLabel = toolHandler.loadingLabel.replace(/\.\.\.$/, '');
202
+ let content = running ? loadingLabel : `Used ${part.toolName}`;
203
+ switch (part.toolName) {
204
+ case 'readFile':
205
+ case 'fileExists':
206
+ incrementActivityCount(counts, 'exploredFileCount');
207
+ content = toolActivityText({
208
+ verb: running
209
+ ? 'Reading'
210
+ : part.toolName === 'fileExists'
211
+ ? 'Checked'
212
+ : 'Read',
213
+ path: pathLabel,
214
+ fallback: 'file',
215
+ });
216
+ break;
217
+ case 'readToolOutput':
218
+ incrementActivityCount(counts, 'exploredFileCount');
219
+ content = running ? 'Reading tool output' : 'Read tool output';
220
+ break;
221
+ case 'listDirectory': {
222
+ incrementActivityCount(counts, 'listCount');
223
+ const itemCount = getCount(output, ['entries', 'items']);
224
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Listing' : 'Listed' }), ' ', _jsx(ActivityCode, { title: path, children: pathLabel ?? 'directory' }), typeof itemCount === 'number' && (_jsxs("span", { children: [" (", plural(itemCount, 'item'), ")"] }))] }));
225
+ break;
226
+ }
227
+ case 'grep':
228
+ case 'globFileSearch': {
229
+ incrementActivityCount(counts, 'searchCount');
230
+ const resultCount = getCount(output, ['matches', 'files', 'results']);
231
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Searching' : 'Searched' }), query && (_jsxs(_Fragment, { children: [' ', "for ", _jsx(ActivityCode, { title: query, children: query })] })), typeof resultCount === 'number' && (_jsxs("span", { children: [" (", plural(resultCount, 'result'), ")"] }))] }));
232
+ break;
233
+ }
234
+ case 'webSearch':
235
+ incrementActivityCount(counts, 'webSearchCount');
236
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Searching web' : 'Searched web' }), query && (_jsxs(_Fragment, { children: [' ', "for ", _jsx(ActivityCode, { title: query, children: query })] }))] }));
237
+ break;
238
+ case 'executeBashCommand':
239
+ case 'runCommand':
240
+ incrementActivityCount(counts, 'commandCount');
241
+ if (running) {
242
+ incrementActivityCount(counts, 'runningCommandCount');
243
+ }
244
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? 'Running' : 'Ran' }), ' ', _jsx(ActivityCode, { title: command, children: command ?? 'command' })] }));
245
+ break;
246
+ case 'writeFile': {
247
+ const created = output?.created === true || args?.create === true;
248
+ incrementActivityCount(counts, created ? 'createdFileCount' : 'editedFileCount');
249
+ content = toolActivityText({
250
+ verb: running ? 'Writing' : created ? 'Created' : 'Wrote',
251
+ path: pathLabel,
252
+ fallback: 'file',
253
+ });
254
+ break;
255
+ }
256
+ case 'editFile':
257
+ incrementActivityCount(counts, 'editedFileCount');
258
+ content = toolActivityText({
259
+ verb: running ? 'Editing' : 'Edited',
260
+ path: pathLabel,
261
+ fallback: 'file',
262
+ });
263
+ break;
264
+ case 'deleteFile':
265
+ incrementActivityCount(counts, 'deletedFileCount');
266
+ content = toolActivityText({
267
+ verb: running ? 'Deleting' : 'Deleted',
268
+ path: pathLabel,
269
+ fallback: 'file',
270
+ });
271
+ break;
272
+ case 'listMoldableAppApi':
273
+ case 'callMoldableAppApi':
274
+ incrementActivityCount(counts, 'apiCallCount');
275
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running
276
+ ? part.toolName === 'listMoldableAppApi'
277
+ ? 'Listing app APIs'
278
+ : 'Calling app API'
279
+ : part.toolName === 'listMoldableAppApi'
280
+ ? 'Listed app APIs'
281
+ : 'Called app API' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
282
+ break;
283
+ case 'listSkillRepos':
284
+ case 'listAvailableSkills':
285
+ case 'syncSkills':
286
+ case 'addSkillRepo':
287
+ case 'updateSkillSelection':
288
+ case 'initSkillsConfig':
289
+ incrementActivityCount(counts, 'skillActionCount');
290
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Updated skills' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
291
+ break;
292
+ case 'scaffoldApp':
293
+ case 'getAppInfo':
294
+ case 'unregisterApp':
295
+ case 'deleteAppData':
296
+ case 'deleteApp':
297
+ incrementActivityCount(counts, 'appActionCount');
298
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Updated app' }), target && (_jsxs(_Fragment, { children: [' ', _jsx(ActivityCode, { title: target, children: target })] }))] }));
299
+ break;
300
+ default:
301
+ incrementActivityCount(counts, 'toolCallCount');
302
+ content = (_jsxs(_Fragment, { children: [_jsx("span", { children: running ? loadingLabel : 'Used tool' }), ' ', _jsx(ActivityCode, { children: part.toolName })] }));
303
+ break;
304
+ }
305
+ if (hasError) {
306
+ content = (_jsxs(_Fragment, { children: [content, _jsx("span", { children: " failed" })] }));
307
+ }
308
+ return {
309
+ entry: {
310
+ id: entryId,
311
+ content,
312
+ isRunning: running,
313
+ isError: hasError,
314
+ },
315
+ counts,
316
+ };
317
+ }
318
+ function formatWorkDuration(ms) {
319
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
320
+ const hours = Math.floor(totalSeconds / 3600);
321
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
322
+ const seconds = totalSeconds % 60;
323
+ if (hours > 0) {
324
+ return `${hours}h ${minutes}m`;
325
+ }
326
+ if (minutes > 0) {
327
+ return `${minutes}m ${seconds}s`;
328
+ }
329
+ return `${seconds}s`;
330
+ }
331
+ function useNow(enabled) {
332
+ const [now, setNow] = useState(() => Date.now());
333
+ useEffect(() => {
334
+ if (!enabled) {
335
+ return;
336
+ }
337
+ const interval = window.setInterval(() => {
338
+ setNow(Date.now());
339
+ }, 1000);
340
+ return () => window.clearInterval(interval);
341
+ }, [enabled]);
342
+ return now;
343
+ }
344
+ function WorkSummary({ messageId, startedAtMs, completedAtMs, isWorking, children, }) {
345
+ const [isOpen, setIsOpen] = useState(false);
346
+ const now = useNow(isWorking && Boolean(startedAtMs));
347
+ const endAtMs = completedAtMs ?? (isWorking ? now : undefined);
348
+ const elapsedMs = typeof startedAtMs === 'number' && typeof endAtMs === 'number'
349
+ ? Math.max(0, endAtMs - startedAtMs)
350
+ : null;
351
+ const durationLabel = elapsedMs !== null && (elapsedMs >= 1000 || !isWorking)
352
+ ? formatWorkDuration(elapsedMs)
353
+ : null;
354
+ const summaryLabel = isWorking
355
+ ? durationLabel
356
+ ? `Working for ${durationLabel}`
357
+ : 'Working'
358
+ : durationLabel
359
+ ? `Worked for ${durationLabel}`
360
+ : 'Worked';
361
+ return (_jsxs(Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: "w-full", "data-work-summary": messageId, children: [_jsxs("div", { className: "text-muted-foreground/75 text-[13px] leading-5", children: [_jsxs(CollapsibleTrigger, { className: cn('group inline-flex max-w-full cursor-pointer items-center gap-1 rounded-md border border-transparent text-left transition-colors', 'hover:bg-muted/30 hover:text-foreground/80 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2'), "aria-label": `${summaryLabel} activity`, children: [_jsx("span", { className: "truncate", children: summaryLabel }), _jsx(ChevronDown, { className: cn('size-3 shrink-0 opacity-40 transition-transform duration-200 group-hover:opacity-70', isOpen ? 'rotate-0' : '-rotate-90') })] }), _jsx("div", { className: "border-border/60 mt-1 w-full border-t" })] }), _jsx(CollapsibleContent, { className: "overflow-hidden", children: _jsx("div", { className: "pt-4", children: children }) })] }));
362
+ }
17
363
  function PureMessage({ message, isLast = false, isStreaming = false, }) {
18
364
  // Get tool progress for streaming stdout/stderr
19
365
  const toolProgress = useToolProgress();
20
366
  // Get approval response handler
21
367
  const onApprovalResponse = useToolApprovalResponse();
22
- // Track open state for each thinking group independently
23
- const [thinkingGroupStates, setThinkingGroupStates] = useState(new Map());
24
- // Process parts chronologically, grouping only reasoning into thinking groups
25
- // Inline tools (file ops, commands) are rendered directly
368
+ // Track open state for each background activity group independently.
369
+ const [activityGroupStates, setActivityGroupStates] = useState(new Map());
370
+ // Process parts chronologically, grouping ordinary tool work and reasoning as
371
+ // low-emphasis background activity. Approval prompts remain prominent because
372
+ // they need direct user action.
26
373
  const { contentItems, hasFinalAssistantText } = useMemo(() => {
27
374
  const items = [];
28
375
  const state = {
29
- currentThinkingGroup: null,
30
- thinkingGroupIndex: 0,
376
+ currentActivityGroup: null,
377
+ activityGroupIndex: 0,
31
378
  hasText: false,
32
379
  };
33
- // Helper to close current thinking group
34
- const closeThinkingGroup = () => {
35
- if (state.currentThinkingGroup) {
380
+ const updateActivityTitle = (group) => {
381
+ group.title = formatActivitySummary(group.counts, group.title, group.isStreaming);
382
+ };
383
+ const addActivity = (group, entry, counts) => {
384
+ group.entries.push(entry);
385
+ group.isStreaming = group.isStreaming || Boolean(entry.isRunning);
386
+ if (counts) {
387
+ for (const key of Object.keys(counts)) {
388
+ group.counts[key] += counts[key];
389
+ }
390
+ }
391
+ updateActivityTitle(group);
392
+ };
393
+ // Helper to close current activity group
394
+ const closeActivityGroup = () => {
395
+ if (state.currentActivityGroup) {
36
396
  items.push({
37
- type: 'thinking',
38
- id: state.currentThinkingGroup.id,
39
- thinkingGroup: state.currentThinkingGroup,
397
+ type: 'activity',
398
+ id: state.currentActivityGroup.id,
399
+ activityGroup: state.currentActivityGroup,
40
400
  });
41
- state.currentThinkingGroup = null;
401
+ state.currentActivityGroup = null;
42
402
  }
43
403
  };
44
- // Helper to ensure thinking group exists
45
- const ensureThinkingGroup = () => {
46
- if (!state.currentThinkingGroup) {
47
- state.thinkingGroupIndex++;
48
- state.currentThinkingGroup = {
49
- id: `thinking-${message.id}-${state.thinkingGroupIndex}`,
50
- timeline: [],
404
+ // Helper to ensure activity group exists
405
+ const ensureActivityGroup = () => {
406
+ if (!state.currentActivityGroup) {
407
+ state.activityGroupIndex++;
408
+ state.currentActivityGroup = {
409
+ id: `activity-${message.id}-${state.activityGroupIndex}`,
410
+ entries: [],
411
+ counts: emptyActivityCounts(),
51
412
  title: DEFAULT_ACTIONS_LABEL,
52
413
  isStreaming: false,
53
414
  };
54
415
  }
55
- return state.currentThinkingGroup;
416
+ return state.currentActivityGroup;
56
417
  };
57
418
  // If no parts, use content as text
58
419
  const parts = message.parts ?? [];
@@ -74,85 +435,40 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
74
435
  // Handle tool calls
75
436
  if (part.type === 'tool-invocation') {
76
437
  const toolHandler = getToolHandler(part.toolName);
77
- // Check if this tool should be rendered inline
78
- if (toolHandler.inline) {
79
- // Close any open thinking group first
80
- closeThinkingGroup();
81
- // Check for approval states
82
- const isApprovalRequested = part.state === 'approval-requested';
83
- const isApprovalResponded = part.state === 'approval-responded' ||
84
- part.state === 'output-denied';
85
- const wasRejected = part.approval?.approved === false;
86
- const wasApproved = part.approval?.approved === true;
87
- // Render inline tool
88
- // Also treat approval-responded (when approved) as loading until output arrives
89
- const isToolLoading = part.state === 'partial-call' ||
90
- part.state === 'call' ||
91
- part.state === 'pending' ||
92
- (isApprovalResponded && wasApproved && part.output === undefined);
93
- // Check for streaming progress (stdout/stderr from command execution)
94
- const progress = part.toolCallId
95
- ? toolProgress[part.toolCallId]
96
- : undefined;
97
- const hasStreamingOutput = progress && (progress.stdout || progress.stderr);
98
- let toolContent;
99
- // Handle approval request state
100
- if (isApprovalRequested &&
101
- toolHandler.renderApproval &&
102
- part.approval?.id &&
103
- onApprovalResponse) {
104
- toolContent = toolHandler.renderApproval({
105
- approvalId: part.approval.id,
106
- toolCallId: part.toolCallId || key,
107
- args: part.args,
108
- }, onApprovalResponse);
109
- }
110
- else if (isApprovalResponded && wasRejected) {
111
- // Show rejection message
112
- toolContent = (_jsx(ToolApproval, { state: "output-denied", approved: false, children: _jsx(ToolApprovalHeader, { children: _jsx(ToolApprovalRejected, { children: "Command execution was rejected" }) }) }));
113
- }
114
- else if (hasStreamingOutput && toolHandler.renderStreaming) {
115
- // Show streaming output (live stdout/stderr)
116
- toolContent = toolHandler.renderStreaming(part.args, progress);
117
- }
118
- else if (isToolLoading) {
119
- // Show loading state (arguments streaming or waiting)
120
- toolContent = toolHandler.renderLoading?.(part.args) || (_jsx("div", { className: "text-muted-foreground text-xs", children: toolHandler.loadingLabel }));
121
- }
122
- else {
123
- // Show final output
124
- toolContent = toolHandler.renderOutput(part.output, part.toolCallId || key);
125
- }
438
+ const isApprovalRequested = part.state === 'approval-requested';
439
+ const isApprovalResponded = part.state === 'approval-responded' || part.state === 'output-denied';
440
+ const wasRejected = part.approval?.approved === false;
441
+ if (isApprovalRequested &&
442
+ toolHandler.renderApproval &&
443
+ part.approval?.id &&
444
+ onApprovalResponse) {
445
+ closeActivityGroup();
126
446
  items.push({
127
447
  type: 'inline-tool',
128
448
  id: key,
129
- content: toolContent,
130
- });
131
- continue;
132
- }
133
- // Non-inline tools go into thinking group
134
- const group = ensureThinkingGroup();
135
- // Show loading state for tools
136
- if (part.state === 'partial-call' ||
137
- part.state === 'call' ||
138
- part.state === 'pending') {
139
- const toolLabel = toolHandler.loadingLabel;
140
- group.title = toolLabel;
141
- group.isStreaming = true;
142
- group.timeline.push({
143
- marker: ThinkingTimelineMarker.Loading,
144
- content: (_jsx("div", { className: "px-2 py-1", children: _jsx("span", { className: "text-muted-foreground font-mono text-xs", children: toolLabel }) }, `${part.toolCallId || key}-loading`)),
449
+ content: toolHandler.renderApproval({
450
+ approvalId: part.approval.id,
451
+ toolCallId: part.toolCallId || key,
452
+ args: part.args,
453
+ }, onApprovalResponse),
145
454
  });
146
455
  continue;
147
456
  }
148
- // Show tool output
149
- if (part.state === 'result' || part.state === 'output-available') {
150
- group.timeline.push({
151
- marker: toolHandler.marker ?? ThinkingTimelineMarker.Default,
152
- content: toolHandler.renderOutput(part.output, part.toolCallId || key),
457
+ if (isApprovalResponded && wasRejected) {
458
+ closeActivityGroup();
459
+ items.push({
460
+ type: 'inline-tool',
461
+ id: key,
462
+ content: (_jsx(ToolApproval, { state: "output-denied", approved: false, children: _jsx(ToolApprovalHeader, { children: _jsx(ToolApprovalRejected, { children: "Command execution was rejected" }) }) })),
153
463
  });
154
464
  continue;
155
465
  }
466
+ const group = ensureActivityGroup();
467
+ const progress = part.toolCallId
468
+ ? toolProgress[part.toolCallId]
469
+ : undefined;
470
+ const activity = summarizeToolActivity(part, toolHandler, progress, key);
471
+ addActivity(group, activity.entry, activity.counts);
156
472
  continue;
157
473
  }
158
474
  if (part.type === 'reasoning') {
@@ -161,7 +477,7 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
161
477
  if (cleaned.length === 0) {
162
478
  continue;
163
479
  }
164
- const group = ensureThinkingGroup();
480
+ const group = ensureActivityGroup();
165
481
  // Extract title from markdown **title** or use first line
166
482
  const markdownTitleMatch = cleaned.match(/\*\*([^*]+)\*\*/);
167
483
  const candidateTitle = markdownTitleMatch?.[1];
@@ -172,15 +488,15 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
172
488
  const body = candidateTitle
173
489
  ? trimmed.replace(/\*\*[^*]+\*\*\s*/, '')
174
490
  : trimmed;
175
- group.timeline.push({
176
- marker: ThinkingTimelineMarker.Default,
177
- content: (_jsx("div", { className: "whitespace-pre-wrap px-2 py-1", children: body }, key)),
491
+ addActivity(group, {
492
+ id: key,
493
+ content: _jsx("span", { className: "whitespace-pre-wrap", children: body }),
178
494
  });
179
495
  continue;
180
496
  }
181
497
  if (part.type === 'text') {
182
- // Close current thinking group before text
183
- closeThinkingGroup();
498
+ // Close current background activity group before text.
499
+ closeActivityGroup();
184
500
  state.hasText = true;
185
501
  items.push({
186
502
  type: 'text',
@@ -190,7 +506,7 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
190
506
  continue;
191
507
  }
192
508
  if (part.type === 'file') {
193
- closeThinkingGroup();
509
+ closeActivityGroup();
194
510
  items.push({
195
511
  type: 'file',
196
512
  id: key,
@@ -198,8 +514,8 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
198
514
  });
199
515
  }
200
516
  }
201
- // Close any remaining thinking group
202
- closeThinkingGroup();
517
+ // Close any remaining activity group
518
+ closeActivityGroup();
203
519
  return {
204
520
  contentItems: items,
205
521
  hasFinalAssistantText: state.hasText,
@@ -211,27 +527,27 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
211
527
  toolProgress,
212
528
  onApprovalResponse,
213
529
  ]);
214
- // Determine if last thinking group is streaming
215
- const lastThinkingGroup = contentItems
216
- .filter((item) => item.type === 'thinking')
217
- .map((item) => (item.type === 'thinking' ? item.thinkingGroup : null))
530
+ // Determine if last activity group is streaming
531
+ const lastActivityGroup = contentItems
532
+ .filter((item) => item.type === 'activity')
533
+ .map((item) => (item.type === 'activity' ? item.activityGroup : null))
218
534
  .filter((g) => g !== null)
219
535
  .pop();
220
536
  // Check if we're still streaming overall and waiting for text
221
537
  const isStreamingOverall = isStreaming && isLast && !hasFinalAssistantText;
222
- // For the last thinking group, also keep it open if we just finished streaming
538
+ // For the last activity group, also keep it open if we just finished streaming
223
539
  // but text hasn't appeared yet (grace period for AI to start responding)
224
540
  const shouldKeepLastGroupOpen = isLast && !hasFinalAssistantText && message.role === 'assistant';
225
- // Auto-open/close reasoning groups based on streaming state
541
+ // Auto-open/close activity groups based on streaming state
226
542
  useEffect(() => {
227
- setThinkingGroupStates((prevStates) => {
543
+ setActivityGroupStates((prevStates) => {
228
544
  let hasChanges = false;
229
545
  const nextStates = new Map(prevStates);
230
546
  contentItems.forEach((item) => {
231
- if (item.type === 'thinking' && item.thinkingGroup) {
232
- const groupId = item.thinkingGroup.id;
233
- const group = item.thinkingGroup;
234
- const isLastGroup = lastThinkingGroup?.id === groupId;
547
+ if (item.type === 'activity' && item.activityGroup) {
548
+ const groupId = item.activityGroup.id;
549
+ const group = item.activityGroup;
550
+ const isLastGroup = lastActivityGroup?.id === groupId;
235
551
  const currentState = nextStates.get(groupId);
236
552
  const shouldBeOpen = group.isStreaming ||
237
553
  (isLastGroup && isStreamingOverall) ||
@@ -262,58 +578,73 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
262
578
  }, [
263
579
  contentItems,
264
580
  isStreamingOverall,
265
- lastThinkingGroup,
581
+ lastActivityGroup,
266
582
  shouldKeepLastGroupOpen,
267
583
  ]);
268
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { "data-testid": `message-${message.role}`, className: "group/message w-full min-w-0 px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1 }, "data-role": message.role, children: _jsx("div", { className: "flex w-full min-w-0 items-start gap-4", children: _jsx("div", { className: "mt-1 flex w-full min-w-0 flex-col gap-2", children: contentItems.map((item) => {
269
- if (item.type === 'thinking' &&
270
- item.thinkingGroup &&
271
- message.role === 'assistant') {
272
- const group = item.thinkingGroup;
273
- const groupState = thinkingGroupStates.get(group.id);
274
- const isLastGroup = lastThinkingGroup?.id === group.id;
275
- const isGroupStreaming = group.isStreaming || (isLastGroup && isStreamingOverall);
276
- const shouldBeOpenByDefault = isGroupStreaming;
277
- const isOpen = groupState?.isOpen ?? shouldBeOpenByDefault;
278
- const titleForTrigger = hasFinalAssistantText && isLastGroup && !isGroupStreaming
279
- ? DEFAULT_ACTIONS_LABEL
280
- : group.title || DEFAULT_ACTIONS_LABEL;
281
- return (_jsxs(Collapsible, { open: isOpen, onOpenChange: (open) => {
282
- setThinkingGroupStates((prev) => {
283
- const next = new Map(prev);
284
- const current = next.get(group.id) ?? {
285
- isOpen: false,
286
- userOpened: false,
287
- };
288
- next.set(group.id, {
289
- ...current,
290
- isOpen: open,
291
- userOpened: open ? true : current.userOpened,
292
- });
293
- return next;
294
- });
295
- }, className: "mb-2 w-full", "data-ai-reasoning": true, children: [_jsxs(CollapsibleTrigger, { className: cn('group flex max-w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-[11px] font-semibold uppercase tracking-wide transition-colors', 'text-muted-foreground hover:bg-accent hover:text-foreground min-w-0'), children: [_jsx(Brain, { className: cn('size-3.5 shrink-0 opacity-80', isGroupStreaming && 'animate-pulse') }), _jsx("span", { className: "flex-1 truncate", children: titleForTrigger }), _jsx(ChevronDown, { className: cn('size-3.5 shrink-0 transition-transform', 'group-data-[state=open]:rotate-180') })] }), _jsx(CollapsibleContent, { className: "text-muted-foreground min-w-0 overflow-hidden rounded-md px-[5px] py-2 font-mono text-xs leading-relaxed", children: _jsx(ThinkingTimeline, { items: group.timeline }) })] }, item.id));
296
- }
297
- if (item.type === 'inline-tool') {
298
- return (_jsx("div", { className: "w-full min-w-0", children: item.content }, item.id));
299
- }
300
- if (item.type === 'file' && item.filePart) {
301
- const isImage = item.filePart.mediaType.startsWith('image/');
302
- const label = item.filePart.filename || 'Attached image';
303
- return (_jsxs("div", { className: cn('bg-secondary text-secondary-foreground flex w-full items-center gap-3 rounded-2xl px-3 py-2.5', message.role !== 'user' && 'bg-muted text-foreground'), children: [_jsx("div", { className: "bg-background flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border", children: isImage && item.filePart.previewUrl ? (_jsx("img", { src: item.filePart.previewUrl, alt: "", className: "size-full object-cover", draggable: false })) : (_jsx(ImageIcon, { className: "text-muted-foreground size-5" })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: label }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: item.filePart.mediaType })] })] }, item.id));
304
- }
305
- if (item.type === 'text' && item.textPart) {
306
- if (message.role === 'user') {
307
- return (_jsx("div", { className: "bg-secondary text-secondary-foreground w-full rounded-2xl px-4 py-2.5 pb-4", children: _jsx("div", { className: "whitespace-pre-wrap break-words text-sm leading-relaxed", children: item.textPart.text }) }, item.id));
308
- }
309
- return (_jsx("div", { className: cn('flex min-w-0 flex-col gap-4', {
310
- 'w-full': message.role === 'assistant',
311
- }), children: _jsx(Markdown, { markdown: item.textPart.text, proseSize: "sm", className: cn('[&_.prose]:leading-relaxed [&_.prose]:text-current', '[&_.prose_pre]:bg-background/80 [&_.prose_code]:text-current', '[&_.prose_p:last-child]:mb-0 [&_.prose_p]:mb-2') }) }, item.id));
312
- }
313
- return null;
314
- }) }) }) }) }));
584
+ const renderContentItem = (item) => {
585
+ if (item.type === 'activity' &&
586
+ item.activityGroup &&
587
+ message.role === 'assistant') {
588
+ const group = item.activityGroup;
589
+ const groupState = activityGroupStates.get(group.id);
590
+ const isLastGroup = lastActivityGroup?.id === group.id;
591
+ const isGroupStreaming = group.isStreaming || (isLastGroup && isStreamingOverall);
592
+ const shouldBeOpenByDefault = isGroupStreaming;
593
+ const isOpen = groupState?.isOpen ?? shouldBeOpenByDefault;
594
+ const titleForTrigger = group.title || DEFAULT_ACTIONS_LABEL;
595
+ return (_jsxs(Collapsible, { open: isOpen, onOpenChange: (open) => {
596
+ setActivityGroupStates((prev) => {
597
+ const next = new Map(prev);
598
+ const current = next.get(group.id) ?? {
599
+ isOpen: false,
600
+ userOpened: false,
601
+ };
602
+ next.set(group.id, {
603
+ ...current,
604
+ isOpen: open,
605
+ userOpened: open ? true : current.userOpened,
606
+ });
607
+ return next;
608
+ });
609
+ }, className: "mb-1 w-full", "data-ai-reasoning": true, children: [_jsxs(CollapsibleTrigger, { className: cn('group flex max-w-full cursor-pointer items-center gap-1 py-0.5 text-[13px] font-normal leading-5 transition-colors', 'text-muted-foreground/75 hover:text-foreground/80 min-w-0'), children: [_jsx("span", { className: "flex-1 truncate", children: titleForTrigger }), _jsx(ChevronDown, { className: cn('size-3 shrink-0 opacity-0 transition-all duration-200 group-hover:opacity-70', isOpen && 'rotate-180 opacity-50') })] }), _jsx(CollapsibleContent, { className: "text-muted-foreground/75 min-w-0 overflow-hidden pb-1.5 pl-3 pt-0 text-[13px] leading-5", children: _jsx("div", { className: "max-h-28 space-y-0.5 overflow-y-auto pr-2", children: group.entries.map((entry) => (_jsx("div", { className: cn('min-w-0 overflow-hidden truncate', entry.isError && 'text-destructive'), children: entry.content }, entry.id))) }) })] }, item.id));
610
+ }
611
+ if (item.type === 'inline-tool') {
612
+ return (_jsx("div", { className: "w-full min-w-0", children: item.content }, item.id));
613
+ }
614
+ if (item.type === 'file' && item.filePart) {
615
+ const isImage = item.filePart.mediaType.startsWith('image/');
616
+ const label = item.filePart.filename || 'Attached image';
617
+ return (_jsxs("div", { className: cn('bg-secondary text-secondary-foreground flex w-full items-center gap-3 rounded-2xl px-3 py-2.5', message.role !== 'user' && 'bg-muted text-foreground'), children: [_jsx("div", { className: "bg-background flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border", children: isImage && item.filePart.previewUrl ? (_jsx("img", { src: item.filePart.previewUrl, alt: "", className: "size-full object-cover", draggable: false })) : (_jsx(ImageIcon, { className: "text-muted-foreground size-5" })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: label }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: item.filePart.mediaType })] })] }, item.id));
618
+ }
619
+ if (item.type === 'text' && item.textPart) {
620
+ if (message.role === 'user') {
621
+ return (_jsx("div", { className: "flex w-full justify-end", children: _jsx("div", { className: "bg-foreground/5 text-foreground max-w-[77%] break-words rounded-2xl px-3 py-2 text-[13px] leading-5", children: _jsx("div", { className: "whitespace-pre-wrap break-words", children: item.textPart.text }) }) }, item.id));
622
+ }
623
+ return (_jsx("div", { className: cn('flex min-w-0 flex-col gap-4', {
624
+ 'w-full': message.role === 'assistant',
625
+ }), children: _jsx(Markdown, { markdown: item.textPart.text, proseSize: "sm", className: cn('[&_.prose]:text-[13px] [&_.prose]:leading-[21px] [&_.prose]:text-current', '[&_.prose_pre]:bg-background/80 [&_.prose_code]:text-current', '[&_.prose_p:last-child]:mb-0 [&_.prose_p]:mb-2', '[&_.prose_ol:first-child]:mt-0 [&_.prose_ul:first-child]:mt-0', '[&_.prose>*:last-child]:mb-0') }) }, item.id));
626
+ }
627
+ return null;
628
+ };
629
+ const finalTextItemIndex = message.role === 'assistant'
630
+ ? contentItems.reduce((lastIndex, item, index) => item.type === 'text' ? index : lastIndex, -1)
631
+ : -1;
632
+ const shouldCollapsePreAnswerWork = message.role === 'assistant' &&
633
+ hasFinalAssistantText &&
634
+ finalTextItemIndex > 0;
635
+ const preAnswerWorkItems = shouldCollapsePreAnswerWork
636
+ ? contentItems.slice(0, finalTextItemIndex)
637
+ : [];
638
+ const finalAnswerItems = shouldCollapsePreAnswerWork
639
+ ? contentItems.slice(finalTextItemIndex)
640
+ : contentItems;
641
+ const isTurnStillWorking = isLast &&
642
+ isStreaming &&
643
+ message.role === 'assistant' &&
644
+ !message.turn?.completedAtMs;
645
+ return (_jsx(AnimatePresence, { children: _jsx(motion.div, { "data-testid": `message-${message.role}`, className: "group/message w-full min-w-0 px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1 }, "data-role": message.role, children: _jsx("div", { className: "flex w-full min-w-0 items-start gap-4", children: _jsxs("div", { className: "mt-1 flex w-full min-w-0 flex-col gap-2", children: [shouldCollapsePreAnswerWork && (_jsx(WorkSummary, { messageId: message.id, startedAtMs: message.turn?.startedAtMs, completedAtMs: message.turn?.completedAtMs, isWorking: isTurnStillWorking, children: _jsx("div", { className: "flex min-w-0 flex-col gap-2", children: preAnswerWorkItems.map(renderContentItem) }) })), finalAnswerItems.map(renderContentItem)] }) }) }) }));
315
646
  }
316
647
  export const Message = memo(PureMessage);
317
648
  export function ThinkingMessage() {
318
- return (_jsx(motion.div, { "data-testid": "message-assistant-loading", className: "group/message w-full px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 0.5 } }, "data-role": "assistant", children: _jsxs("div", { className: "text-muted-foreground flex flex-row items-center gap-2 px-2", children: [_jsx(Loader2, { className: "size-4 shrink-0 animate-spin" }), _jsx("span", { className: "font-mono text-xs font-medium uppercase", children: "Thinking..." })] }) }));
649
+ return (_jsx(motion.div, { "data-testid": "message-assistant-loading", className: "group/message w-full px-2", initial: { y: 5, opacity: 0 }, animate: { y: 0, opacity: 1, transition: { delay: 0.5 } }, "data-role": "assistant", children: _jsx("div", { className: "text-muted-foreground/75 px-2 text-[13px] leading-5", children: _jsx("span", { className: "animate-pulse select-none", children: "Thinking" }) }) }));
319
650
  }
@@ -1 +1 @@
1
- {"version":3,"file":"chat-panel.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-panel.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-panel.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-panel.tsx"],"names":[],"mappings":"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"}
@@ -71,8 +71,20 @@ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChan
71
71
  return () => clearTimeout(timer);
72
72
  }
73
73
  }, [isExpanded]);
74
+ const showMissingApiKeyPrompt = missingApiKey ||
75
+ (error &&
76
+ (error.message?.includes('API_KEY not configured') ||
77
+ error.message?.includes('credential required in Vault') ||
78
+ error.message?.includes('credential not configured in Vault')));
79
+ const submitDisabled = Boolean(showMissingApiKeyPrompt);
74
80
  const handleSubmit = useCallback((e, mode) => {
75
81
  e?.preventDefault();
82
+ if (submitDisabled) {
83
+ if (!isExpanded) {
84
+ onExpandedChange(true);
85
+ }
86
+ return;
87
+ }
76
88
  if (!input.trim() && imageAttachments.length === 0)
77
89
  return;
78
90
  const submitMode = mode ?? (isResponding ? 'steer' : 'send');
@@ -94,6 +106,7 @@ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChan
94
106
  onExpandedChange,
95
107
  onSubmit,
96
108
  onStop,
109
+ submitDisabled,
97
110
  ]);
98
111
  const handleNewChat = useCallback(() => {
99
112
  onNewChat?.();
@@ -136,11 +149,6 @@ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChan
136
149
  }, [isExpanded, onExpandedChange, onMinimizedChange]);
137
150
  const isStreaming = status === 'streaming' || status === 'submitted';
138
151
  const isThinking = useMemo(() => computeIsThinking(messages, status), [messages, status]);
139
- const showMissingApiKeyPrompt = missingApiKey ||
140
- (error &&
141
- (error.message?.includes('API_KEY not configured') ||
142
- error.message?.includes('credential required in Vault') ||
143
- error.message?.includes('credential not configured in Vault')));
144
152
  const showErrorMessage = Boolean(error) && status === 'error' && !showMissingApiKeyPrompt;
145
153
  const lastMessageId = messages[messages.length - 1]?.id;
146
154
  const listItems = useMemo(() => {
@@ -174,10 +182,10 @@ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChan
174
182
  return (_jsx("div", { className: "px-2 pb-4", children: messages.length === 0 && welcomeMessage && (_jsx("div", { className: "bg-muted/50 text-muted-foreground mx-2 rounded-2xl p-4 text-sm", children: welcomeMessage })) }));
175
183
  case 'message': {
176
184
  const checkpoint = checkpoints?.get(item.message.id);
177
- return (_jsx("div", { className: "px-2 pb-4", children: _jsx(MessageRow, { message: item.message, isLast: item.message.id === lastMessageId, isStreaming: isStreaming, checkpoint: checkpoint, restoringMessageId: restoringMessageId, onRestoreCheckpoint: onRestoreCheckpoint }) }));
185
+ return (_jsx("div", { className: "px-2 pb-3", children: _jsx(MessageRow, { message: item.message, isLast: item.message.id === lastMessageId, isStreaming: isStreaming, checkpoint: checkpoint, restoringMessageId: restoringMessageId, onRestoreCheckpoint: onRestoreCheckpoint }) }));
178
186
  }
179
187
  case 'thinking':
180
- return (_jsx("div", { className: "px-2 pb-4", children: _jsx(ThinkingMessage, {}) }));
188
+ return (_jsx("div", { className: "px-2 pb-3", children: _jsx(ThinkingMessage, {}) }));
181
189
  case 'missing-api-key':
182
190
  return (_jsx("div", { className: "px-2 pb-4", children: _jsxs("div", { className: "border-primary/30 bg-primary/5 mx-2 flex flex-col items-center gap-3 rounded-lg border p-4 text-center", children: [_jsx("div", { className: "bg-primary/10 flex size-10 items-center justify-center rounded-full", children: _jsx(AlertCircle, { className: "text-primary size-5" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-foreground font-medium", children: "API key required" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: "Add an API key to Vault to start chatting with AI" })] }), onAddApiKey && (_jsx(Button, { onClick: onAddApiKey, size: "sm", className: "cursor-pointer", children: "Add API key" }))] }) }));
183
191
  case 'error':
@@ -267,5 +275,5 @@ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChan
267
275
  }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs transition-colors", "aria-label": "Steer active response with queued follow-up", children: [_jsx(CornerDownRight, { className: "size-3.5" }), _jsx("span", { children: "Steer" })] })), onRemoveQueuedMessage && (_jsx("button", { type: "button", onClick: (event) => {
268
276
  event.stopPropagation();
269
277
  onRemoveQueuedMessage(message.id);
270
- }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors", "aria-label": "Remove queued follow-up", children: _jsx(X, { className: "size-3.5" }) }))] }, message.id))) })] })), _jsx(ChatInput, { input: input, onInputChange: onInputChange, onSubmit: handleSubmit, isResponding: isResponding, placeholder: placeholder, inputRef: inputRef, onStop: onStop, compact: !isExpanded, imageAttachments: imageAttachments, onRemoveImageAttachment: onRemoveImageAttachment, footerStartControls: isExpanded ? composerStartControls : null, footerControls: isExpanded ? composerControls : null })] })] }) })] }));
278
+ }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors", "aria-label": "Remove queued follow-up", children: _jsx(X, { className: "size-3.5" }) }))] }, message.id))) })] })), _jsx(ChatInput, { input: input, onInputChange: onInputChange, onSubmit: handleSubmit, isResponding: isResponding, placeholder: placeholder, inputRef: inputRef, onStop: onStop, compact: !isExpanded, imageAttachments: imageAttachments, onRemoveImageAttachment: onRemoveImageAttachment, footerStartControls: isExpanded ? composerStartControls : null, footerControls: isExpanded ? composerControls : null, submitDisabled: submitDisabled })] })] }) })] }));
271
279
  }
@@ -142,9 +142,9 @@ const components = {
142
142
  return _jsx(CodeBlock, { code: codeContent, language: language });
143
143
  }
144
144
  // Inline code - style nicely, remove prose backticks via before:/after:content-none
145
- return (_jsx("code", { className: "bg-muted text-foreground rounded-sm px-1.5 py-0.5 font-mono text-[0.9em] before:content-none after:content-none", ...props, children: children }));
145
+ return (_jsx("code", { className: "bg-muted/70 text-foreground rounded px-1 py-0.5 font-mono text-[0.92em] before:content-none after:content-none", ...props, children: children }));
146
146
  },
147
- a: ({ children, href, ...props }) => (_jsx("a", { className: "text-primary break-all font-bold hover:underline", href: href ?? '#', target: "_blank", rel: "noreferrer", ...props, children: children })),
147
+ a: ({ children, href, ...props }) => (_jsx("a", { className: "text-primary break-all font-medium hover:underline", href: href ?? '#', target: "_blank", rel: "noreferrer", ...props, children: children })),
148
148
  // Process @mentions in text and paragraph nodes
149
149
  p: ({ children, ...props }) => {
150
150
  const processedChildren = React.Children.map(children, (child) => {
@@ -2,11 +2,12 @@ import * as React from 'react';
2
2
  import { Dialog } from './dialog';
3
3
  import { Command as CommandPrimitive } from 'cmdk';
4
4
  declare function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>): import("react/jsx-runtime").JSX.Element;
5
- declare function CommandDialog({ title, description, children, className, showCloseButton, ...props }: React.ComponentProps<typeof Dialog> & {
5
+ declare function CommandDialog({ title, description, children, className, showCloseButton, filter, ...props }: React.ComponentProps<typeof Dialog> & {
6
6
  title?: string;
7
7
  description?: string;
8
8
  className?: string;
9
9
  showCloseButton?: boolean;
10
+ filter?: React.ComponentProps<typeof CommandPrimitive>['filter'];
10
11
  }): import("react/jsx-runtime").JSX.Element;
11
12
  declare function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>): import("react/jsx-runtime").JSX.Element;
12
13
  declare function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/components/ui/command.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,MAAM,EAKP,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,MAAM,CAAA;AAElD,iBAAS,OAAO,CAAC,EACf,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,2CAW/C;AAED,iBAAS,aAAa,CAAC,EACrB,KAAyB,EACzB,WAA8C,EAC9C,QAAQ,EACR,SAAS,EACT,eAAsB,EACtB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,GAAG;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,2CAiBA;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAiBrD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC,2CAWpD;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAQrD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAWrD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,SAAS,CAAC,2CAQzD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC,2CAWpD;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,2CAW9B;AAED,OAAO,EACL,OAAO,EACP,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,CAAA"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/components/ui/command.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EACL,MAAM,EAKP,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,MAAM,CAAA;AAElD,iBAAS,OAAO,CAAC,EACf,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,2CAW/C;AAED,iBAAS,aAAa,CAAC,EACrB,KAAyB,EACzB,WAA8C,EAC9C,QAAQ,EACR,SAAS,EACT,eAAsB,EACtB,MAAM,EACN,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,GAAG;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAA;CACjE,2CAoBA;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAiBrD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC,2CAWpD;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAQrD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,2CAWrD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,SAAS,CAAC,2CAQzD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC,2CAWpD;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,2CAW9B;AAED,OAAO,EACL,OAAO,EACP,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,CAAA"}
@@ -7,8 +7,8 @@ import { Command as CommandPrimitive } from 'cmdk';
7
7
  function Command({ className, ...props }) {
8
8
  return (_jsx(CommandPrimitive, { "data-slot": "command", className: cn('bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md', className), ...props }));
9
9
  }
10
- function CommandDialog({ title = 'Command Palette', description = 'Search for a command to run...', children, className, showCloseButton = true, ...props }) {
11
- return (_jsxs(Dialog, { ...props, children: [_jsxs(DialogHeader, { className: "sr-only", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: description })] }), _jsx(DialogContent, { className: cn('overflow-hidden p-0', className), showCloseButton: showCloseButton, children: _jsx(Command, { className: "[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5", children: children }) })] }));
10
+ function CommandDialog({ title = 'Command Palette', description = 'Search for a command to run...', children, className, showCloseButton = true, filter, ...props }) {
11
+ return (_jsxs(Dialog, { ...props, children: [_jsxs(DialogHeader, { className: "sr-only", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: description })] }), _jsx(DialogContent, { className: cn('overflow-hidden p-0', className), showCloseButton: showCloseButton, children: _jsx(Command, { filter: filter, className: "[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5", children: children }) })] }));
12
12
  }
13
13
  function CommandInput({ className, ...props }) {
14
14
  return (_jsxs("div", { "data-slot": "command-input-wrapper", className: "flex h-9 items-center gap-2 border-b px-3", children: [_jsx(SearchIcon, { className: "size-4 shrink-0 opacity-50" }), _jsx(CommandPrimitive.Input, { "data-slot": "command-input", className: cn('placeholder:text-muted-foreground outline-hidden flex h-10 w-full rounded-md bg-transparent py-3 text-sm disabled:cursor-not-allowed disabled:opacity-50', className), ...props })] }));
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { cn } from './lib/utils';
2
2
  export { ThemeProvider, useTheme, themeScript, type Theme } from './lib/theme';
3
3
  export { WorkspaceProvider, useWorkspace, WORKSPACE_HEADER, } from './lib/workspace';
4
- export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, downloadFile, type AppCommand, type CommandAction, type CommandsResponse, type CommandMessage, type DownloadFileOptions, } from './lib/commands';
4
+ export { useMoldableCommands, useMoldableCommand, useMoldableAppCommands, isInMoldable, sendToMoldable, downloadFile, type AppCommand, type CommandAction, type CommandsResponse, type CommandMessage, type DownloadFileOptions, } from './lib/commands';
5
5
  export * from './components/ui';
6
6
  export { useIsMobile } from './hooks/use-mobile';
7
7
  export { Markdown } from './components/markdown';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAA;AAG9E,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAA;AAGvB,cAAc,iBAAiB,CAAA;AAG/B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAGzD,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAA;AAG9E,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAA;AAGvB,cAAc,iBAAiB,CAAA;AAG/B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAGzD,cAAc,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { ThemeProvider, useTheme, themeScript } from './lib/theme';
5
5
  // Export workspace
6
6
  export { WorkspaceProvider, useWorkspace, WORKSPACE_HEADER, } from './lib/workspace';
7
7
  // Export commands
8
- export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, downloadFile, } from './lib/commands';
8
+ export { useMoldableCommands, useMoldableCommand, useMoldableAppCommands, isInMoldable, sendToMoldable, downloadFile, } from './lib/commands';
9
9
  // Export UI components
10
10
  export * from './components/ui';
11
11
  // Export hooks
@@ -70,6 +70,14 @@ export interface CommandMessage {
70
70
  * ```
71
71
  */
72
72
  export declare function useMoldableCommands(handlers: Record<string, (payload?: unknown) => void>): void;
73
+ /**
74
+ * Publish the commands that make sense for the app's current view.
75
+ *
76
+ * Apps should call this with a stable, memoized command list. The desktop
77
+ * command menu will prefer these commands over the app's static
78
+ * /api/moldable/commands response while the app is running.
79
+ */
80
+ export declare function useMoldableAppCommands(appId: string, commands: AppCommand[]): void;
73
81
  /**
74
82
  * Hook to register a single command handler
75
83
  * Useful when you want to register handlers in different components
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,24 @@ export function useMoldableCommands(handlers) {
33
33
  return () => window.removeEventListener('message', handleMessage);
34
34
  }, []);
35
35
  }
36
+ /**
37
+ * Publish the commands that make sense for the app's current view.
38
+ *
39
+ * Apps should call this with a stable, memoized command list. The desktop
40
+ * command menu will prefer these commands over the app's static
41
+ * /api/moldable/commands response while the app is running.
42
+ */
43
+ export function useMoldableAppCommands(appId, commands) {
44
+ useEffect(() => {
45
+ if (!isInMoldable())
46
+ return;
47
+ window.parent.postMessage({
48
+ type: 'moldable:set-app-commands',
49
+ appId,
50
+ commands,
51
+ }, '*');
52
+ }, [appId, commands]);
53
+ }
36
54
  /**
37
55
  * Hook to register a single command handler
38
56
  * Useful when you want to register handlers in different components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moldable-ai/ui",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Shared UI components for Moldable applications",
5
5
  "author": "Desiderata LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -33,6 +33,14 @@
33
33
  "src/styles",
34
34
  "LICENSE"
35
35
  ],
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "dev": "tsc --watch",
39
+ "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
40
+ "format": "prettier --write . --ignore-path ../../.prettierignore",
41
+ "check-types": "tsc --noEmit",
42
+ "test": "vitest run"
43
+ },
36
44
  "dependencies": {
37
45
  "@base-ui/react": "^1.1.0",
38
46
  "@hookform/resolvers": "^5.2.2",
@@ -89,26 +97,18 @@
89
97
  "zod": "^4.3.5"
90
98
  },
91
99
  "devDependencies": {
100
+ "@moldable-ai/eslint-config": "workspace:*",
101
+ "@moldable-ai/prettier-config": "workspace:*",
102
+ "@moldable-ai/typescript-config": "workspace:*",
92
103
  "@types/node": "^24.0.3",
93
104
  "@types/react": "^19.1.6",
94
105
  "@types/react-dom": "^19.1.6",
95
106
  "jsdom": "^26.1.0",
96
107
  "typescript": "^5.8.3",
97
- "vitest": "^3.2.4",
98
- "@moldable-ai/typescript-config": "0.1.3",
99
- "@moldable-ai/eslint-config": "0.1.4",
100
- "@moldable-ai/prettier-config": "0.1.3"
108
+ "vitest": "^3.2.4"
101
109
  },
102
110
  "peerDependencies": {
103
111
  "react": "^19.0.0",
104
112
  "react-dom": "^19.0.0"
105
- },
106
- "scripts": {
107
- "build": "tsc",
108
- "dev": "tsc --watch",
109
- "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
110
- "format": "prettier --write . --ignore-path ../../.prettierignore",
111
- "check-types": "tsc --noEmit",
112
- "test": "vitest run"
113
113
  }
114
- }
114
+ }