@moldable-ai/ui 0.2.6 → 0.2.7

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 (32) hide show
  1. package/LICENSE +75 -71
  2. package/dist/components/chat/chat-input.d.ts +15 -3
  3. package/dist/components/chat/chat-input.d.ts.map +1 -1
  4. package/dist/components/chat/chat-input.js +19 -8
  5. package/dist/components/chat/chat-message.d.ts +6 -0
  6. package/dist/components/chat/chat-message.d.ts.map +1 -1
  7. package/dist/components/chat/chat-message.js +15 -1
  8. package/dist/components/chat/chat-panel.d.ts +26 -2
  9. package/dist/components/chat/chat-panel.d.ts.map +1 -1
  10. package/dist/components/chat/chat-panel.js +58 -30
  11. package/dist/components/chat/conversation-history.d.ts +6 -1
  12. package/dist/components/chat/conversation-history.d.ts.map +1 -1
  13. package/dist/components/chat/conversation-history.js +6 -6
  14. package/dist/components/chat/index.d.ts +2 -2
  15. package/dist/components/chat/index.d.ts.map +1 -1
  16. package/dist/components/chat/index.js +1 -1
  17. package/dist/components/chat/model-selector.d.ts +3 -1
  18. package/dist/components/chat/model-selector.d.ts.map +1 -1
  19. package/dist/components/chat/model-selector.js +2 -2
  20. package/dist/components/chat/reasoning-effort-selector.d.ts +4 -1
  21. package/dist/components/chat/reasoning-effort-selector.d.ts.map +1 -1
  22. package/dist/components/chat/reasoning-effort-selector.js +6 -6
  23. package/dist/components/chat/tool-handlers.d.ts.map +1 -1
  24. package/dist/components/chat/tool-handlers.js +62 -6
  25. package/dist/components/ui/scroll-area.d.ts +1 -1
  26. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  27. package/dist/components/ui/scroll-area.js +5 -3
  28. package/dist/lib/commands.d.ts +49 -0
  29. package/dist/lib/commands.d.ts.map +1 -1
  30. package/dist/lib/commands.js +56 -0
  31. package/package.json +15 -15
  32. package/src/styles/index.css +1 -1
package/LICENSE CHANGED
@@ -1,101 +1,105 @@
1
- Copyright (c) 2026-present Desiderata LLC
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
2
 
3
- Portions of this software are licensed as follows:
3
+ ## Abbreviation
4
4
 
5
- - All third party components incorporated into the Moldable Software are licensed
6
- under the original license provided by the owner of the applicable component.
7
- - All other content is available under the "Elastic License 2.0" license as defined below.
5
+ FSL-1.1-ALv2
8
6
 
9
- Elastic License 2.0
7
+ ## Notice
10
8
 
11
- URL: https://www.elastic.co/licensing/elastic-license
9
+ Copyright 2026 Desiderata LLC
12
10
 
13
- ## Acceptance
11
+ ## Terms and Conditions
14
12
 
15
- By using the software, you agree to all of the terms and conditions below.
13
+ ### Licensor ("We")
16
14
 
17
- ## Copyright License
15
+ The party offering the Software under these Terms and Conditions.
18
16
 
19
- The licensor grants you a non-exclusive, royalty-free, worldwide,
20
- non-sublicensable, non-transferable license to use, copy, distribute, make
21
- available, and prepare derivative works of the software, in each case subject to
22
- the limitations and conditions below.
17
+ ### The Software
23
18
 
24
- ## Limitations
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
25
22
 
26
- You may not provide the software to third parties as a hosted or managed
27
- service, where the service provides users with access to any substantial set of
28
- the features or functionality of the software.
23
+ ### License Grant
29
24
 
30
- You may not move, change, disable, or circumvent the license key functionality
31
- in the software, and you may not remove or obscure any functionality in the
32
- software that is protected by the license key.
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
33
29
 
34
- You may not alter, remove, or obscure any licensing, copyright, or other notices
35
- of the licensor in the software. Any use of the licensor's trademarks is subject
36
- to applicable law.
30
+ ### Permitted Purpose
37
31
 
38
- ## Patents
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
39
35
 
40
- The licensor grants you a license, under any patent claims the licensor can
41
- license, or becomes able to license, to make, have made, use, sell, offer for
42
- sale, import and have imported the software, in each case subject to the
43
- limitations and conditions in this license. This license does not cover any
44
- patent claims that you cause to be infringed by modifications or additions to
45
- the software. If you or your company make any written claim that the software
46
- infringes or contributes to infringement of any patent, your patent license for
47
- the software granted under these terms ends immediately. If your company makes
48
- such a claim, your patent license ends immediately for work on behalf of your
49
- company.
36
+ 1. substitutes for the Software;
50
37
 
51
- ## Notices
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
52
40
 
53
- You must ensure that anyone who gets a copy of any part of the software from you
54
- also gets a copy of these terms.
41
+ 3. offers the same or substantially similar functionality as the Software.
55
42
 
56
- If you modify the software, you must include in any modified copies of the
57
- software prominent notices stating that you have modified the software.
43
+ Permitted Purposes specifically include using the Software:
58
44
 
59
- ## No Other Rights
45
+ 1. for your internal use and access;
60
46
 
61
- These terms do not imply any licenses other than those expressly granted in
62
- these terms.
47
+ 2. for non-commercial education;
63
48
 
64
- ## Termination
49
+ 3. for non-commercial research; and
65
50
 
66
- If you use the software in violation of these terms, such use is not licensed,
67
- and your licenses will automatically terminate. If the licensor provides you
68
- with a notice of your violation, and you cease all violation of this license no
69
- later than 30 days after you receive that notice, your licenses will be
70
- reinstated retroactively. However, if you violate these terms after such
71
- reinstatement, any additional violation of these terms will cause your licenses
72
- to terminate automatically and permanently.
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
73
53
 
74
- ## No Liability
54
+ ### Patents
75
55
 
76
- _As far as the law allows, the software comes as is, without any warranty or
77
- condition, and the licensor will not be liable to you for any damages arising
78
- out of these terms or the use or nature of the software, under any kind of
79
- legal claim._
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
80
61
 
81
- ## Definitions
62
+ ### Redistribution
82
63
 
83
- The **licensor** is the entity offering these terms, and the **software** is the
84
- software the licensor makes available under these terms, including any portion
85
- of it.
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
86
66
 
87
- **you** refers to the individual or entity agreeing to these terms.
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
88
70
 
89
- **your company** is any legal entity, sole proprietorship, or other kind of
90
- organization that you work for, plus all organizations that have control over,
91
- are under the control of, or are under common control with that
92
- organization. **control** means ownership of substantially all the assets of an
93
- entity, or the power to direct its management and policies by vote, contract, or
94
- otherwise. Control can be direct or indirect.
71
+ ### Disclaimer
95
72
 
96
- **your licenses** are all the licenses granted to you for the software under
97
- these terms.
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
98
76
 
99
- **use** means anything you do with the software requiring one of your licenses.
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
100
80
 
101
- **trademark** means trademarks, service marks, and similar rights.
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
@@ -1,14 +1,26 @@
1
- import type { ChangeEvent, FormEvent, RefObject } from 'react';
1
+ import type { ChangeEvent, FormEvent, ReactNode, RefObject } from 'react';
2
+ export type ChatSubmitMode = 'send' | 'steer' | 'queue';
3
+ export type ChatImageAttachment = {
4
+ id: string;
5
+ name: string;
6
+ path: string;
7
+ mediaType: string;
8
+ previewUrl?: string;
9
+ };
2
10
  type ChatInputProps = {
3
11
  input: string;
4
12
  onInputChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
5
- onSubmit: (e: FormEvent<HTMLFormElement>) => void;
13
+ onSubmit: (e: FormEvent<HTMLFormElement> | undefined, mode?: ChatSubmitMode) => void;
6
14
  isResponding: boolean;
7
15
  placeholder?: string;
8
16
  inputRef?: RefObject<HTMLTextAreaElement | null>;
9
17
  onStop?: () => void;
10
18
  compact?: boolean;
19
+ imageAttachments?: ChatImageAttachment[];
20
+ onRemoveImageAttachment?: (id: string) => void;
21
+ footerStartControls?: ReactNode;
22
+ footerControls?: ReactNode;
11
23
  };
12
- export declare function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder, inputRef, onStop, compact, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder, inputRef, onStop, compact, imageAttachments, onRemoveImageAttachment, footerStartControls, footerControls, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
13
25
  export {};
14
26
  //# 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,EACV,MAAM,OAAO,CAAA;AAKd,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAA;IAC5D,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAA;IACjD,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;CAClB,CAAA;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAA+B,EAC/B,QAAQ,EACR,MAAM,EACN,OAAe,GAChB,EAAE,cAAc,2CAsFhB"}
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,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowUp, Square } from 'lucide-react';
2
+ 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, }) {
6
+ export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeholder = 'Ask anything...', inputRef, onStop, compact = false, imageAttachments = [], onRemoveImageAttachment, footerStartControls, footerControls, }) {
7
7
  const hasInput = input.trim().length > 0;
8
+ const hasAttachments = imageAttachments.length > 0;
9
+ const canSubmit = hasInput || hasAttachments;
8
10
  const handleKeyDown = (e) => {
9
11
  if (e.key === 'Enter') {
10
12
  if (e.shiftKey) {
@@ -13,18 +15,23 @@ export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeh
13
15
  }
14
16
  // Submit on plain Enter (if there's input)
15
17
  e.preventDefault();
16
- if (hasInput) {
17
- // If we have input, submit (which will stop any ongoing stream)
18
- onSubmit(e);
18
+ if (canSubmit) {
19
+ onSubmit(undefined, isResponding ? 'queue' : 'send');
19
20
  }
20
21
  else if (isResponding && onStop) {
21
22
  // If no input but streaming, just stop
22
23
  onStop();
23
24
  }
24
25
  }
26
+ else if (e.key === 'Tab' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
27
+ if (!canSubmit)
28
+ return;
29
+ e.preventDefault();
30
+ onSubmit(undefined, isResponding ? 'queue' : 'send');
31
+ }
25
32
  };
26
- // Show stop button only when streaming AND no input
27
- const showStopButton = isResponding && !hasInput;
33
+ // Keep the stop control available while a response is in progress.
34
+ const showStopButton = isResponding;
28
35
  const handleButtonClick = (e) => {
29
36
  // When rendered as stop button (type="button"), always call onStop
30
37
  // Don't re-check conditions - trust the render decision to avoid race conditions
@@ -34,5 +41,9 @@ export function ChatInput({ input, onInputChange, onSubmit, isResponding, placeh
34
41
  }
35
42
  // If type="submit", form handles submission
36
43
  };
37
- 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: onSubmit, children: _jsxs("div", { className: cn('bg-background relative flex w-full flex-col rounded-3xl border', compact && 'h-14'), children: [_jsx("div", { className: cn('w-full px-5', compact ? 'py-4' : 'py-4 pr-14'), 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 && (_jsx("div", { className: "absolute bottom-3 right-3", children: _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: !hasInput && !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 ? 'Stop generating' : 'Send message' })] }) }))] }) }) }) }));
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
45
+ ? 'Stop generating'
46
+ : isResponding
47
+ ? 'Steer response'
48
+ : 'Send message' })] })] }))] }) }) }) }));
38
49
  }
@@ -4,6 +4,12 @@ export type ChatMessagePart = {
4
4
  } | {
5
5
  type: 'reasoning';
6
6
  text: string;
7
+ } | {
8
+ type: 'file';
9
+ mediaType: string;
10
+ filename?: string;
11
+ url: string;
12
+ previewUrl?: string;
7
13
  } | {
8
14
  type: 'tool-invocation';
9
15
  toolCallId?: string;
@@ -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,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;AAcD,iBAAS,WAAW,CAAC,EACnB,OAAO,EACP,MAAc,EACd,WAAmB,GACpB,EAAE,YAAY,2CA6bd;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":"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,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Brain, ChevronDown, Loader2 } from 'lucide-react';
3
+ import { Brain, ChevronDown, Image as ImageIcon, Loader2 } 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';
@@ -187,6 +187,15 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
187
187
  id: key,
188
188
  textPart: part,
189
189
  });
190
+ continue;
191
+ }
192
+ if (part.type === 'file') {
193
+ closeThinkingGroup();
194
+ items.push({
195
+ type: 'file',
196
+ id: key,
197
+ filePart: part,
198
+ });
190
199
  }
191
200
  }
192
201
  // Close any remaining thinking group
@@ -288,6 +297,11 @@ function PureMessage({ message, isLast = false, isStreaming = false, }) {
288
297
  if (item.type === 'inline-tool') {
289
298
  return (_jsx("div", { className: "w-full min-w-0", children: item.content }, item.id));
290
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
+ }
291
305
  if (item.type === 'text' && item.textPart) {
292
306
  if (message.role === 'user') {
293
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));
@@ -1,4 +1,5 @@
1
1
  import { type ChangeEvent, type FormEvent, type ReactNode } from 'react';
2
+ import { type ChatImageAttachment, type ChatSubmitMode } from './chat-input';
2
3
  import { type ChatMessage } from './chat-message';
3
4
  import { type MessageCheckpoint } from './chat-messages';
4
5
  import { type ConversationMeta } from './conversation-history';
@@ -16,17 +17,26 @@ export interface ToolProgressData {
16
17
  stderr: string;
17
18
  status: 'running' | 'complete';
18
19
  }
20
+ export interface QueuedChatMessage {
21
+ id: string;
22
+ preview: string;
23
+ attachmentCount: number;
24
+ status?: 'queued' | 'failed';
25
+ error?: string;
26
+ }
19
27
  export interface ChatPanelProps {
20
28
  /** Chat messages */
21
29
  messages: ChatMessage[];
22
30
  /** Current chat status */
23
31
  status: ChatStatus;
32
+ /** Whether chat work is active or queued, blocking conversation-level controls */
33
+ isTurnInFlight?: boolean;
24
34
  /** Input value */
25
35
  input: string;
26
36
  /** Handle input change */
27
37
  onInputChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
28
38
  /** Handle form submit */
29
- onSubmit: (e?: FormEvent<HTMLFormElement>) => void;
39
+ onSubmit: (e?: FormEvent<HTMLFormElement>, mode?: ChatSubmitMode) => void;
30
40
  /** Handle stop generation */
31
41
  onStop?: () => void;
32
42
  /** Handle new chat */
@@ -87,10 +97,24 @@ export interface ChatPanelProps {
87
97
  restoringMessageId?: string | null;
88
98
  /** Callback when restore is requested for a message */
89
99
  onRestoreCheckpoint?: (messageId: string) => void;
100
+ /** Images attached to the pending user message */
101
+ imageAttachments?: ChatImageAttachment[];
102
+ /** Remove a pending image attachment */
103
+ onRemoveImageAttachment?: (id: string) => void;
104
+ /** Whether a dragged image is currently over the chat drop target */
105
+ isImageDropActive?: boolean;
106
+ /** Follow-up messages queued to send after the active response finishes */
107
+ queuedMessages?: QueuedChatMessage[];
108
+ /** Remove a queued follow-up before it sends */
109
+ onRemoveQueuedMessage?: (id: string) => void;
110
+ /** Retry a queued follow-up after a send failure */
111
+ onRetryQueuedMessage?: (id: string) => void;
112
+ /** Send a queued follow-up into the active response immediately */
113
+ onSteerQueuedMessage?: (id: string) => void;
90
114
  }
91
115
  /**
92
116
  * Floating chat panel with model selector
93
117
  */
94
- export declare function ChatPanel({ messages, status, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder, welcomeMessage, isExpanded, onExpandedChange, isMinimized, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress, onApprovalResponse, isEditingApp, onEditingAppChange, showEditingAppToggle, checkpoints, restoringMessageId, onRestoreCheckpoint, }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
118
+ export declare function ChatPanel({ messages, status, isTurnInFlight, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder, welcomeMessage, isExpanded, onExpandedChange, isMinimized, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress, onApprovalResponse, isEditingApp, onEditingAppChange, showEditingAppToggle, checkpoints, restoringMessageId, onRestoreCheckpoint, imageAttachments, onRemoveImageAttachment, isImageDropActive, queuedMessages, onRemoveQueuedMessage, onRetryQueuedMessage, onSteerQueuedMessage, }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
95
119
  export {};
96
120
  //# sourceMappingURL=chat-panel.d.ts.map
@@ -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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"}
@@ -1,12 +1,12 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { AlertCircle, ChevronDown, Code2, MessageCircle, Plus, } from 'lucide-react';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AlertCircle, ChevronDown, Code2, CornerDownRight, Image as ImageIcon, ListPlus, MessageCircle, X, } from 'lucide-react';
4
4
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
5
5
  import { cn } from '../../lib/utils';
6
6
  import { Button } from '../ui/button';
7
7
  import { ScrollArea } from '../ui/scroll-area';
8
8
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
9
- import { ChatInput } from './chat-input';
9
+ import { ChatInput, } from './chat-input';
10
10
  import { ThinkingMessage } from './chat-message';
11
11
  import { MessageRow, computeIsThinking, } from './chat-messages';
12
12
  import { ConversationHistory, } from './conversation-history';
@@ -19,7 +19,7 @@ import { Virtualizer } from 'virtua';
19
19
  /**
20
20
  * Floating chat panel with model selector
21
21
  */
22
- export function ChatPanel({ messages, status, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder = 'Ask anything...', welcomeMessage, isExpanded, onExpandedChange, isMinimized = false, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress = {}, onApprovalResponse, isEditingApp = true, onEditingAppChange, showEditingAppToggle = false, checkpoints, restoringMessageId, onRestoreCheckpoint, }) {
22
+ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder = 'Ask anything...', welcomeMessage, isExpanded, onExpandedChange, isMinimized = false, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress = {}, onApprovalResponse, isEditingApp = true, onEditingAppChange, showEditingAppToggle = false, checkpoints, restoringMessageId, onRestoreCheckpoint, imageAttachments = [], onRemoveImageAttachment, isImageDropActive = false, queuedMessages = [], onRemoveQueuedMessage, onRetryQueuedMessage, onSteerQueuedMessage, }) {
23
23
  const inputRef = useRef(null);
24
24
  const scrollAreaRef = useRef(null);
25
25
  const viewportRef = useRef(null);
@@ -29,6 +29,10 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
29
29
  const [viewportVersion, setViewportVersion] = useState(0);
30
30
  const [isAtBottom, setIsAtBottom] = useState(true);
31
31
  const isResponding = status === 'streaming' || status === 'submitted';
32
+ const controlsDisabled = isTurnInFlight ?? isResponding;
33
+ const composerControls = (_jsxs(_Fragment, { children: [_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelChange: onModelChange, disabled: controlsDisabled, dropdownAlign: "end", dropdownSide: "top" }), reasoningEffortOptions &&
34
+ selectedReasoningEffort &&
35
+ onReasoningEffortChange && (_jsx(ReasoningEffortSelector, { options: reasoningEffortOptions, selectedEffort: selectedReasoningEffort, onEffortChange: onReasoningEffortChange, disabled: controlsDisabled, showLabel: true, dropdownAlign: "end", dropdownSide: "top" }))] }));
32
36
  // Capture the Radix viewport element so virtua can use it as the scroll container
33
37
  useEffect(() => {
34
38
  const scrollArea = scrollAreaRef.current;
@@ -67,12 +71,13 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
67
71
  return () => clearTimeout(timer);
68
72
  }
69
73
  }, [isExpanded]);
70
- const handleSubmit = useCallback((e) => {
74
+ const handleSubmit = useCallback((e, mode) => {
71
75
  e?.preventDefault();
72
- if (!input.trim())
76
+ if (!input.trim() && imageAttachments.length === 0)
73
77
  return;
74
- // If streaming, stop the current response first
75
- if (isResponding && onStop) {
78
+ const submitMode = mode ?? (isResponding ? 'steer' : 'send');
79
+ // Steer interrupts the current response and sends the new instruction now.
80
+ if (submitMode === 'steer' && isResponding && onStop) {
76
81
  onStop();
77
82
  }
78
83
  if (!isExpanded) {
@@ -80,26 +85,44 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
80
85
  }
81
86
  // Reset to bottom when user sends a message
82
87
  setIsAtBottom(true);
83
- onSubmit(e);
84
- }, [input, isResponding, isExpanded, onExpandedChange, onSubmit, onStop]);
88
+ onSubmit(e, submitMode);
89
+ }, [
90
+ imageAttachments.length,
91
+ input,
92
+ isResponding,
93
+ isExpanded,
94
+ onExpandedChange,
95
+ onSubmit,
96
+ onStop,
97
+ ]);
85
98
  const handleNewChat = useCallback(() => {
86
99
  onNewChat?.();
87
100
  inputRef.current?.focus();
88
101
  }, [onNewChat]);
102
+ const composerStartControls = (_jsxs(_Fragment, { children: [conversations &&
103
+ (conversations.length > 0 || onNewChat) &&
104
+ onSelectConversation && (_jsx(ConversationHistory, { conversations: conversations, currentConversationId: currentConversationId, onSelect: onSelectConversation, onDelete: onDeleteConversation, onNewChat: handleNewChat, newChatDisabled: controlsDisabled, disabled: controlsDisabled, dropdownAlign: "start", dropdownSide: "top", tooltipSide: "top" })), showEditingAppToggle && onEditingAppChange && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: (event) => {
105
+ event.preventDefault();
106
+ onEditingAppChange(!isEditingApp);
107
+ }, className: cn('cursor-pointer', isEditingApp
108
+ ? 'text-primary bg-primary/10 hover:bg-primary/20'
109
+ : 'text-muted-foreground hover:text-foreground'), children: _jsx(Code2, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "top", children: _jsx("p", { children: isEditingApp
110
+ ? 'Editing this app (click to disable)'
111
+ : 'Not editing app (click to enable)' }) })] }) }))] }));
89
112
  // Keyboard shortcut: Cmd+Shift+O for new chat
90
113
  useEffect(() => {
91
114
  const handleKeyDown = (e) => {
92
115
  if (e.key === 'o' && e.metaKey && e.shiftKey) {
93
116
  e.preventDefault();
94
- // Only create new chat if not currently streaming and panel is expanded
95
- if (!isResponding && isExpanded) {
117
+ // Only create new chat if conversation controls are currently available.
118
+ if (!controlsDisabled && isExpanded) {
96
119
  handleNewChat();
97
120
  }
98
121
  }
99
122
  };
100
123
  document.addEventListener('keydown', handleKeyDown);
101
124
  return () => document.removeEventListener('keydown', handleKeyDown);
102
- }, [isResponding, isExpanded, handleNewChat]);
125
+ }, [controlsDisabled, isExpanded, handleNewChat]);
103
126
  // Handle clicking the minimized flap
104
127
  const handleFlapClick = useCallback(() => {
105
128
  onMinimizedChange?.(false);
@@ -114,10 +137,11 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
114
137
  const isStreaming = status === 'streaming' || status === 'submitted';
115
138
  const isThinking = useMemo(() => computeIsThinking(messages, status), [messages, status]);
116
139
  const showMissingApiKeyPrompt = missingApiKey ||
117
- (error && error.message?.includes('API_KEY not configured'));
118
- const showErrorMessage = Boolean(error) &&
119
- status === 'error' &&
120
- !error?.message?.includes('API_KEY not configured');
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
+ const showErrorMessage = Boolean(error) && status === 'error' && !showMissingApiKeyPrompt;
121
145
  const lastMessageId = messages[messages.length - 1]?.id;
122
146
  const listItems = useMemo(() => {
123
147
  const items = [];
@@ -155,7 +179,7 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
155
179
  case 'thinking':
156
180
  return (_jsx("div", { className: "px-2 pb-4", children: _jsx(ThinkingMessage, {}) }));
157
181
  case 'missing-api-key':
158
- 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 start chatting with AI" })] }), onAddApiKey && (_jsx(Button, { onClick: onAddApiKey, size: "sm", className: "cursor-pointer", children: "Add API key" }))] }) }));
182
+ 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" }))] }) }));
159
183
  case 'error':
160
184
  return (_jsx("div", { className: "px-2 pb-4", children: error && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 text-destructive mx-2 flex items-start gap-2 rounded-lg border p-3 text-sm", children: [_jsx(AlertCircle, { className: "mt-0.5 size-4 shrink-0" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium", children: "Request failed" }), _jsx("p", { className: "text-destructive/80 mt-0.5 break-words", children: error.message || 'An unknown error occurred' })] })] })) }));
161
185
  }
@@ -202,11 +226,11 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
202
226
  width: isExpanded ? 'min(90vw, 640px)' : '400px',
203
227
  y: isMinimized ? 'calc(100% + 24px)' : 0,
204
228
  opacity: isMinimized ? 0 : 1,
205
- }, transition: { type: 'tween', duration: 0.25, ease: 'easeOut' }, className: cn('pointer-events-none fixed bottom-6 left-[calc(50%+var(--sidebar-width,0px)/2)] z-50 flex -translate-x-1/2 justify-center', isMinimized && 'pointer-events-none', className), children: _jsxs("div", { className: cn('bg-background/80 pointer-events-auto relative w-full shadow-[0_8px_40px_-12px_rgba(0,0,0,0.15)]', isExpanded
229
+ }, transition: { type: 'tween', duration: 0.25, ease: 'easeOut' }, className: cn('pointer-events-none fixed bottom-6 left-[calc(50%+var(--sidebar-width,0px)/2)] z-50 flex -translate-x-1/2 justify-center', isMinimized && 'pointer-events-none', className), children: _jsxs("div", { "data-chat-drop-target": true, className: cn('bg-background/80 pointer-events-auto relative w-full shadow-[0_8px_40px_-12px_rgba(0,0,0,0.15)]', isExpanded
206
230
  ? 'bg-background'
207
231
  : 'bg-background/80 supports-[backdrop-filter]:backdrop-blur-xl', 'rounded-[28px]', isExpanded ? 'overflow-hidden' : 'overflow-visible'), children: [!isExpanded && (_jsx("div", { className: "pointer-events-none absolute inset-0 z-0 rounded-[inherit]", style: {
208
232
  background: 'linear-gradient(134deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 50%, transparent 55%)',
209
- } })), _jsx("div", { className: "pointer-events-none absolute inset-0 z-50 rounded-[inherit]", style: {
233
+ } })), isImageDropActive && (_jsxs("div", { className: "bg-background/90 border-primary/50 text-foreground absolute inset-0 z-[70] flex flex-col items-center justify-center gap-2 rounded-[inherit] border-2 border-dashed backdrop-blur-sm", children: [_jsx(ImageIcon, { className: "text-primary size-7" }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-sm font-medium", children: "Drop images to attach" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "They stay on disk and are read when sent" })] })] })), _jsx("div", { className: "pointer-events-none absolute inset-0 z-50 rounded-[inherit]", style: {
210
234
  padding: '1px',
211
235
  background: 'linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.1) 20%, rgba(255,255,255,0.05) 45%, rgba(255,255,255,0.05) 55%, rgba(255,255,255,0.1) 80%, rgba(255,255,255,0.25) 100%)',
212
236
  mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
@@ -227,17 +251,21 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
227
251
  } })), _jsxs(motion.div, { initial: false, animate: {
228
252
  height: isExpanded ? 'min(60vh, 480px)' : '0px',
229
253
  opacity: isExpanded ? 1 : 0,
230
- }, transition: { type: 'tween', duration: 0.2, ease: 'easeOut' }, className: "relative z-10 flex flex-col overflow-hidden", style: { pointerEvents: isExpanded ? 'auto' : 'none' }, children: [_jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelChange: onModelChange, disabled: isResponding }), reasoningEffortOptions &&
231
- selectedReasoningEffort &&
232
- onReasoningEffortChange && (_jsx(ReasoningEffortSelector, { options: reasoningEffortOptions, selectedEffort: selectedReasoningEffort, onEffortChange: onReasoningEffortChange, disabled: isResponding })), showEditingAppToggle && onEditingAppChange && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => onEditingAppChange(!isEditingApp), className: cn('ml-1', isEditingApp
233
- ? 'text-primary bg-primary/10 hover:bg-primary/20'
234
- : 'text-muted-foreground hover:text-foreground'), children: _jsx(Code2, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: isEditingApp
235
- ? 'Editing this app (click to disable)'
236
- : 'Not editing app (click to enable)' }) })] }) }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [conversations &&
237
- conversations.length > 0 &&
238
- onSelectConversation && (_jsx(ConversationHistory, { conversations: conversations, currentConversationId: currentConversationId, onSelect: onSelectConversation, onDelete: onDeleteConversation, disabled: isResponding })), _jsxs(TooltipProvider, { children: [messages.length > 0 && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleNewChat, disabled: isResponding, className: "text-muted-foreground hover:text-foreground", children: _jsx(Plus, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "New chat" }) })] })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleMinimize, className: "text-muted-foreground hover:text-foreground", children: _jsx(ChevronDown, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Minimize" }) })] })] })] })] }), _jsx(ScrollArea, { ref: scrollAreaRef, className: "min-w-0 flex-1 px-2", children: _jsx("div", { className: "min-w-0 max-w-full overflow-hidden py-4", children: _jsx(ToolProgressProvider, { value: toolProgress, children: _jsx(ToolApprovalProvider, { onApprovalResponse: onApprovalResponse ?? null, children: viewportVersion > 0 ? (_jsx(Virtualizer, { ref: virtualizerRef, data: listItems, scrollRef: viewportRef, bufferSize: 300, children: (item) => renderVirtualItem(item) }, viewportVersion)) : (listItems.map((item) => (_jsx("div", { children: renderVirtualItem(item) }, item.key)))) }) }) }) })] }), _jsx("div", { className: "relative z-10", onClick: () => {
254
+ }, transition: { type: 'tween', duration: 0.2, ease: 'easeOut' }, className: "relative z-10 flex flex-col overflow-hidden", style: { pointerEvents: isExpanded ? 'auto' : 'none' }, children: [_jsx("div", { className: "absolute right-3 top-3 z-20", children: _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleMinimize, className: "text-muted-foreground hover:text-foreground cursor-pointer", children: _jsx(ChevronDown, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Minimize" }) })] }) }) }), _jsx(ScrollArea, { ref: scrollAreaRef, className: "min-h-0 min-w-0 flex-1 px-2", children: _jsx("div", { className: "min-w-0 max-w-full overflow-hidden py-4 pr-10", children: _jsx(ToolProgressProvider, { value: toolProgress, children: _jsx(ToolApprovalProvider, { onApprovalResponse: onApprovalResponse ?? null, children: viewportVersion > 0 ? (_jsx(Virtualizer, { ref: virtualizerRef, data: listItems, scrollRef: viewportRef, bufferSize: 300, children: (item) => renderVirtualItem(item) }, viewportVersion)) : (listItems.map((item) => (_jsx("div", { children: renderVirtualItem(item) }, item.key)))) }) }) }) })] }), _jsxs("div", { className: "relative z-10", onClick: () => {
239
255
  if (!isExpanded) {
240
256
  onExpandedChange(true);
241
257
  }
242
- }, children: _jsx(ChatInput, { input: input, onInputChange: onInputChange, onSubmit: handleSubmit, isResponding: isResponding, placeholder: placeholder, inputRef: inputRef, onStop: onStop, compact: !isExpanded }) })] }) })] }));
258
+ }, children: [queuedMessages.length > 0 && (_jsxs("div", { className: "border-border/70 bg-background/95 border-t px-4 py-2", children: [_jsxs("div", { className: "text-muted-foreground mb-2 flex items-center gap-2 text-xs font-medium", children: [_jsx(ListPlus, { className: "size-3.5" }), _jsx("span", { children: "Queued follow-ups" })] }), _jsx("div", { className: "space-y-1.5", children: queuedMessages.map((message) => (_jsxs("div", { className: "bg-muted/60 text-foreground flex min-h-9 items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs", children: [_jsxs("span", { className: "min-w-0 flex-1 truncate", children: [message.preview, message.attachmentCount > 0 &&
259
+ ` (${message.attachmentCount} image${message.attachmentCount === 1 ? '' : 's'})`] }), message.status === 'failed' &&
260
+ message.error &&
261
+ onRetryQueuedMessage && (_jsxs("button", { type: "button", onClick: (event) => {
262
+ event.stopPropagation();
263
+ onRetryQueuedMessage(message.id);
264
+ }, 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": "Retry queued follow-up", children: [_jsx(CornerDownRight, { className: "size-3.5" }), _jsx("span", { children: "Retry" })] })), isResponding && onSteerQueuedMessage && (_jsxs("button", { type: "button", onClick: (event) => {
265
+ event.stopPropagation();
266
+ onSteerQueuedMessage(message.id);
267
+ }, 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
+ event.stopPropagation();
269
+ 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 })] })] }) })] }));
243
271
  }
@@ -10,9 +10,14 @@ interface ConversationHistoryProps {
10
10
  currentConversationId?: string | null;
11
11
  onSelect: (id: string) => void;
12
12
  onDelete?: (id: string) => void;
13
+ onNewChat?: () => void;
14
+ newChatDisabled?: boolean;
13
15
  disabled?: boolean;
14
16
  className?: string;
17
+ dropdownAlign?: 'start' | 'center' | 'end';
18
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
19
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
15
20
  }
16
- export declare function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, disabled, className, }: ConversationHistoryProps): import("react/jsx-runtime").JSX.Element | null;
21
+ export declare function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, onNewChat, newChatDisabled, disabled, className, dropdownAlign, dropdownSide, tooltipSide, }: ConversationHistoryProps): import("react/jsx-runtime").JSX.Element | null;
17
22
  export {};
18
23
  //# sourceMappingURL=conversation-history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../../../src/components/chat/conversation-history.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAiBD,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,QAAgB,EAChB,SAAS,GACV,EAAE,wBAAwB,kDAsE1B"}
1
+ {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../../../src/components/chat/conversation-history.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAClD,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CAClD;AAiBD,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,eAAuB,EACvB,QAAgB,EAChB,SAAS,EACT,aAAuB,EACvB,YAAuB,EACvB,WAAsB,GACvB,EAAE,wBAAwB,kDA6F1B"}
@@ -1,8 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Clock, Trash2 } from 'lucide-react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Clock, Plus, Trash2 } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../ui/dropdown-menu';
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
6
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
7
7
  function formatRelativeTime(dateStr) {
8
8
  const date = new Date(dateStr);
@@ -21,11 +21,11 @@ function formatRelativeTime(dateStr) {
21
21
  return `${diffDays}d ago`;
22
22
  return date.toLocaleDateString();
23
23
  }
24
- export function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, disabled = false, className, }) {
25
- if (conversations.length === 0) {
24
+ export function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, onNewChat, newChatDisabled = false, disabled = false, className, dropdownAlign = 'start', dropdownSide = 'bottom', tooltipSide = 'bottom', }) {
25
+ if (conversations.length === 0 && !onNewChat) {
26
26
  return null;
27
27
  }
28
- return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground', className), children: _jsx(Clock, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Conversation history" }) })] }), _jsxs(DropdownMenuContent, { align: "start", className: "w-64", children: [_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs font-medium", children: "Recent conversations" }), _jsx(DropdownMenuSeparator, {}), _jsx("div", { className: "max-h-64 overflow-y-auto", children: conversations.slice(0, 20).map((conv) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(conv.id), className: cn('group flex cursor-pointer items-start gap-2 py-2', conv.id === currentConversationId && 'bg-accent'), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm", children: conv.title }), _jsxs("p", { className: "text-muted-foreground text-xs", children: [formatRelativeTime(conv.updatedAt), " \u00B7 ", conv.messageCount, ' ', "messages"] })] }), onDelete && (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: (e) => {
28
+ return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground cursor-pointer', className), children: _jsx(Clock, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: tooltipSide, children: _jsx("p", { children: "Conversation history" }) })] }), _jsxs(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "w-64", children: [onNewChat && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onClick: onNewChat, disabled: newChatDisabled, title: "New conversation (Cmd+Shift+O)", className: "flex cursor-pointer items-center gap-2", children: [_jsx(Plus, { className: "size-4" }), _jsx("span", { children: "New conversation" }), _jsx(DropdownMenuShortcut, { children: "\u2318\u21E7O" })] }), conversations.length > 0 && _jsx(DropdownMenuSeparator, {})] })), conversations.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs font-medium", children: "Recent conversations" }), _jsx(DropdownMenuSeparator, {})] })), _jsx("div", { className: "max-h-64 overflow-y-auto", children: conversations.slice(0, 20).map((conv) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(conv.id), className: cn('group flex cursor-pointer items-start gap-2 py-2', conv.id === currentConversationId && 'bg-accent'), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm", children: conv.title }), _jsxs("p", { className: "text-muted-foreground text-xs", children: [formatRelativeTime(conv.updatedAt), " \u00B7 ", conv.messageCount, ' ', "messages"] })] }), onDelete && (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: (e) => {
29
29
  e.stopPropagation();
30
30
  onDelete(conv.id);
31
31
  }, className: "text-muted-foreground hover:text-destructive size-6 shrink-0 opacity-0 group-hover:opacity-100", children: _jsx(Trash2, { className: "size-3" }) }))] }, conv.id))) })] })] }) }));
@@ -1,4 +1,4 @@
1
- export { ChatInput } from './chat-input';
1
+ export { ChatInput, type ChatImageAttachment, type ChatSubmitMode, } from './chat-input';
2
2
  export { Message, ThinkingMessage, type ChatMessage, type ChatMessagePart, } from './chat-message';
3
3
  export { Messages, type MessageCheckpoint } from './chat-messages';
4
4
  export { CheckpointBadge, type CheckpointBadgeProps } from './checkpoint-badge';
@@ -6,7 +6,7 @@ export { RestoreDialog, type CheckpointInfo, type RestoreDialogProps, } from './
6
6
  export { ModelSelector, type ModelOption } from './model-selector';
7
7
  export { ReasoningEffortSelector, type ReasoningEffortOption, } from './reasoning-effort-selector';
8
8
  export { ConversationHistory, type ConversationMeta, } from './conversation-history';
9
- export { ChatPanel, type ChatPanelProps, type ToolProgressData, } from './chat-panel';
9
+ export { ChatPanel, type ChatPanelProps, type QueuedChatMessage, type ToolProgressData, } from './chat-panel';
10
10
  export { ThinkingTimeline, ThinkingTimelineMarker, type ThinkingTimelineItem, type ThinkingTimelineProps, } from './thinking-timeline';
11
11
  export { getToolHandler, DEFAULT_TOOL_HANDLERS, type ToolHandler, } from './tool-handlers';
12
12
  export { ToolProgressProvider, useToolProgress, useToolCallProgress, } from './tool-progress-context';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/chat/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EACL,OAAO,EACP,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACL,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EACL,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,GACpB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/chat/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,OAAO,EACP,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACL,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EACL,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,GACpB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAA"}
@@ -1,4 +1,4 @@
1
- export { ChatInput } from './chat-input';
1
+ export { ChatInput, } from './chat-input';
2
2
  export { Message, ThinkingMessage, } from './chat-message';
3
3
  export { Messages } from './chat-messages';
4
4
  export { CheckpointBadge } from './checkpoint-badge';
@@ -10,7 +10,9 @@ type ModelSelectorProps = {
10
10
  onModelChange: (modelId: string) => void;
11
11
  disabled?: boolean;
12
12
  className?: string;
13
+ dropdownAlign?: 'start' | 'center' | 'end';
14
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
13
15
  };
14
- export declare function ModelSelector({ models, selectedModel, onModelChange, disabled, className, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function ModelSelector({ models, selectedModel, onModelChange, disabled, className, dropdownAlign, dropdownSide, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
15
17
  export {};
16
18
  //# sourceMappingURL=model-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/model-selector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAUtC,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,aAAa,EACb,aAAa,EACb,QAAgB,EAChB,SAAS,GACV,EAAE,kBAAkB,2CAqCpB"}
1
+ {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/model-selector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAUtC,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CACnD,CAAA;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,aAAa,EACb,aAAa,EACb,QAAgB,EAChB,SAAS,EACT,aAAuB,EACvB,YAAuB,GACxB,EAAE,kBAAkB,2CAyCpB"}
@@ -3,7 +3,7 @@ import { Check, ChevronDown } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
- export function ModelSelector({ models, selectedModel, onModelChange, disabled = false, className, }) {
6
+ export function ModelSelector({ models, selectedModel, onModelChange, disabled = false, className, dropdownAlign = 'start', dropdownSide = 'bottom', }) {
7
7
  const currentModel = models.find((m) => m.id === selectedModel) ?? models[0];
8
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground h-7 gap-1.5 rounded-full px-2.5 text-xs font-medium', className), children: [_jsx("span", { children: currentModel?.icon }), _jsx("span", { children: currentModel?.name }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] }) }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[160px]", children: models.map((model) => (_jsxs(DropdownMenuItem, { onClick: () => onModelChange(model.id), className: "flex items-center gap-2", children: [_jsx("span", { children: model.icon }), _jsx("span", { className: "flex-1", children: model.name }), model.id === selectedModel && (_jsx(Check, { className: "text-primary size-4" }))] }, model.id))) })] }));
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground h-8 min-w-0 max-w-[180px] cursor-pointer gap-1.5 rounded-full px-2.5 text-xs font-medium', className), children: [_jsx("span", { children: currentModel?.icon }), _jsx("span", { className: "min-w-0 truncate", children: currentModel?.name }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] }) }), _jsx(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "min-w-[160px]", children: models.map((model) => (_jsxs(DropdownMenuItem, { onClick: () => onModelChange(model.id), className: "flex cursor-pointer items-center gap-2", children: [_jsx("span", { children: model.icon }), _jsx("span", { className: "flex-1", children: model.name }), model.id === selectedModel && (_jsx(Check, { className: "text-primary size-4" }))] }, model.id))) })] }));
9
9
  }
@@ -8,7 +8,10 @@ type ReasoningEffortSelectorProps = {
8
8
  onEffortChange: (effort: string) => void;
9
9
  disabled?: boolean;
10
10
  className?: string;
11
+ showLabel?: boolean;
12
+ dropdownAlign?: 'start' | 'center' | 'end';
13
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
11
14
  };
12
- export declare function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled, className, }: ReasoningEffortSelectorProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled, className, showLabel, dropdownAlign, dropdownSide, }: ReasoningEffortSelectorProps): import("react/jsx-runtime").JSX.Element;
13
16
  export {};
14
17
  //# sourceMappingURL=reasoning-effort-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-effort-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/reasoning-effort-selector.tsx"],"names":[],"mappings":"AAgBA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,cAAc,EACd,cAAc,EACd,QAAgB,EAChB,SAAS,GACV,EAAE,4BAA4B,2CA6C9B"}
1
+ {"version":3,"file":"reasoning-effort-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/reasoning-effort-selector.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CACnD,CAAA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,cAAc,EACd,cAAc,EACd,QAAgB,EAChB,SAAS,EACT,SAAiB,EACjB,aAAuB,EACvB,YAAuB,GACxB,EAAE,4BAA4B,2CAoD9B"}
@@ -1,10 +1,10 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Brain, Check } from 'lucide-react';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Brain, Check, ChevronDown } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
7
- export function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled = false, className, }) {
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
+ export function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled = false, className, showLabel = false, dropdownAlign = 'start', dropdownSide = 'bottom', }) {
8
7
  const currentOption = options.find((o) => o.value === selectedEffort) ?? options[1]; // Default to medium
9
- return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground', className), children: _jsx(Brain, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsxs("p", { children: ["Reasoning: ", currentOption?.label] }) })] }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[140px]", children: options.map((option) => (_jsxs(DropdownMenuItem, { onClick: () => onEffortChange(option.value), className: "flex items-center gap-2", children: [_jsx(Brain, { className: "text-muted-foreground size-3.5" }), _jsx("span", { className: "flex-1", children: option.label }), option.value === selectedEffort && (_jsx(Check, { className: "text-primary size-4" }))] }, option.value))) })] }) }));
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground cursor-pointer', showLabel &&
9
+ 'h-8 w-auto gap-1.5 rounded-full px-3 text-xs font-medium', className), children: showLabel ? (_jsxs(_Fragment, { children: [_jsx("span", { children: currentOption?.label }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] })) : (_jsx(Brain, { className: "size-4" })) }) }), _jsxs(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "min-w-[140px]", children: [_jsx(DropdownMenuLabel, { className: "text-muted-foreground font-normal", children: "Reasoning" }), options.map((option) => (_jsxs(DropdownMenuItem, { onClick: () => onEffortChange(option.value), className: "flex cursor-pointer items-center gap-2", children: [!showLabel && _jsx(Brain, { className: "text-muted-foreground size-3.5" }), _jsx("span", { className: "flex-1", children: option.label }), option.value === selectedEffort && (_jsx(Check, { className: "text-primary size-4" }))] }, option.value)))] })] }));
10
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-handlers.d.ts","sourceRoot":"","sources":["../../../src/components/chat/tool-handlers.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAW5D,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAEtE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IAExB,YAAY,EAAE,MAAM,CAAA;IAEpB,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAE/B,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,SAAS,CAAA;IAGhE,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;IAG7C,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,qBAAqB,KAC5B,SAAS,CAAA;IAGd,cAAc,CAAC,EAAE,CACf,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,uBAAuB,KAC/B,SAAS,CAAA;CACf,CAAA;AAkOD;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CA+0D7D,CAAA;AAoBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAmC5D"}
1
+ {"version":3,"file":"tool-handlers.d.ts","sourceRoot":"","sources":["../../../src/components/chat/tool-handlers.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAW5D,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAEtE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IAExB,YAAY,EAAE,MAAM,CAAA;IAEpB,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAE/B,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,SAAS,CAAA;IAGhE,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;IAG7C,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,qBAAqB,KAC5B,SAAS,CAAA;IAGd,cAAc,CAAC,EAAE,CACf,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,uBAAuB,KAC/B,SAAS,CAAA;CACf,CAAA;AAoSD;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAg/D7D,CAAA;AAoBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAmC5D"}
@@ -85,10 +85,66 @@ function getFileName(path) {
85
85
  const parts = path.split('/');
86
86
  return parts[parts.length - 1] || path;
87
87
  }
88
+ function appApiScopes(output) {
89
+ return (output.apps?.flatMap((app) => app.api?.capabilities?.flatMap((capability) => capability.scopes ?? []) ?? []) ?? []);
90
+ }
91
+ function appApiItemCount(result) {
92
+ if (Array.isArray(result)) {
93
+ return result.length;
94
+ }
95
+ if (result && typeof result === 'object') {
96
+ const record = result;
97
+ for (const key of ['items', 'records', 'meetings', 'results', 'data']) {
98
+ const value = record[key];
99
+ if (Array.isArray(value)) {
100
+ return value.length;
101
+ }
102
+ }
103
+ return 1;
104
+ }
105
+ return result == null ? 0 : 1;
106
+ }
88
107
  /**
89
108
  * Default tool handlers for Moldable tools
90
109
  */
91
110
  export const DEFAULT_TOOL_HANDLERS = {
111
+ listMoldableAppApi: {
112
+ loadingLabel: 'Listing app APIs...',
113
+ marker: ThinkingTimelineMarker.Default,
114
+ inline: true,
115
+ renderLoading: () => (_jsx(LoadingIndicator, { icon: Database, children: "Listing app APIs..." })),
116
+ renderOutput: (output, toolCallId) => {
117
+ if (output === undefined || output === null) {
118
+ return (_jsx(LoadingIndicator, { icon: Database, children: "Listing app APIs..." }, toolCallId));
119
+ }
120
+ const result = output;
121
+ const apps = result.apps ?? [];
122
+ const scopes = appApiScopes(result);
123
+ const hasError = result.success === false;
124
+ return (_jsx("div", { className: "my-1 min-w-0 max-w-xl", children: _jsxs("div", { className: cn('inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs', hasError
125
+ ? 'bg-destructive/10 text-destructive'
126
+ : 'bg-muted text-muted-foreground'), children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "shrink-0 font-medium", children: hasError ? 'App API list failed' : 'Listing app APIs' }), !hasError && (_jsxs("span", { className: "truncate", children: [apps.length, " app", apps.length === 1 ? '' : 's', ", ", scopes.length, ' ', "function", scopes.length === 1 ? '' : 's'] })), hasError ? (_jsx(X, { className: "size-3 shrink-0" })) : (_jsx(Check, { className: "size-3 shrink-0 text-green-600" }))] }) }, toolCallId));
127
+ },
128
+ },
129
+ callMoldableAppApi: {
130
+ loadingLabel: 'Calling app API...',
131
+ marker: ThinkingTimelineMarker.Default,
132
+ inline: true,
133
+ renderLoading: () => (_jsx(LoadingIndicator, { icon: Database, children: "Fetching app data..." })),
134
+ renderOutput: (output, toolCallId) => {
135
+ if (output === undefined || output === null) {
136
+ return (_jsx(LoadingIndicator, { icon: Database, children: "Fetching app data..." }, toolCallId));
137
+ }
138
+ const response = output;
139
+ const ok = response.ok !== false && !response.error;
140
+ const itemCount = ok ? appApiItemCount(response.result) : 0;
141
+ return (_jsx("div", { className: "my-1 min-w-0 max-w-xl", children: _jsxs("div", { className: cn('inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs', ok
142
+ ? 'bg-muted text-muted-foreground'
143
+ : 'bg-destructive/10 text-destructive'), children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "shrink-0 font-medium", children: ok ? 'Fetched app data' : 'App API failed' }), _jsx("span", { className: "min-w-0 truncate", children: ok
144
+ ? `${itemCount} item${itemCount === 1 ? '' : 's'}`
145
+ : response.error?.message || response.error?.code || 'Error' }), ok ? (_jsx(Check, { className: "size-3 shrink-0 text-green-600" })) : (_jsx(X, { className: "size-3 shrink-0" }))] }) }, toolCallId));
146
+ },
147
+ },
92
148
  readFile: {
93
149
  loadingLabel: 'Reading file...',
94
150
  marker: ThinkingTimelineMarker.File,
@@ -563,8 +619,8 @@ export const DEFAULT_TOOL_HANDLERS = {
563
619
  marker: ThinkingTimelineMarker.Default,
564
620
  inline: true,
565
621
  renderLoading: (args) => {
566
- const { appId } = (args ?? {});
567
- return (_jsxs("div", { className: "bg-muted text-muted-foreground inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0 animate-pulse" }), _jsxs("span", { className: "truncate", children: ["Deleting data for ", appId || 'app', "..."] })] }));
622
+ const { appId, workspaceId } = (args ?? {});
623
+ return (_jsxs("div", { className: "bg-muted text-muted-foreground inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0 animate-pulse" }), _jsxs("span", { className: "truncate", children: ["Deleting data for ", appId || 'app', workspaceId ? ` in ${workspaceId}` : '', "..."] })] }));
568
624
  },
569
625
  renderOutput: (output, toolCallId) => {
570
626
  const result = (output ?? {});
@@ -572,13 +628,13 @@ export const DEFAULT_TOOL_HANDLERS = {
572
628
  return (_jsxs("div", { className: "bg-muted text-muted-foreground my-1 inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0 animate-pulse" }), _jsx("span", { className: "truncate", children: "Deleting app data..." })] }, toolCallId));
573
629
  }
574
630
  if (result.success === false) {
575
- return (_jsxs("div", { className: "bg-destructive/10 text-destructive my-1 inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: result.error || 'Failed to delete app data' })] }, toolCallId));
631
+ return (_jsxs("div", { className: "bg-destructive/10 text-destructive my-1 min-w-0 rounded-md", children: [_jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: result.error || 'Failed to delete app data' })] }), (result.workspaceName || result.workspaceId) && (_jsxs("div", { className: "border-destructive/20 border-t px-2 py-1 text-[10px]", children: ["Workspace:", ' ', _jsx("span", { className: "font-medium", children: result.workspaceName || result.workspaceId }), result.workspaceName && result.workspaceId && (_jsxs("span", { className: "text-destructive/70", children: [' ', "(", result.workspaceId, ")"] }))] }))] }, toolCallId));
576
632
  }
577
- return (_jsxs("div", { className: "bg-muted text-muted-foreground my-1 inline-flex max-w-full items-center gap-2 rounded-md px-2 py-1 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsxs("span", { className: "font-medium", children: ["Data deleted for ", result.appId] }), _jsx(Check, { className: "size-3 shrink-0 text-green-600" })] }, toolCallId));
633
+ return (_jsxs("div", { className: "bg-muted text-muted-foreground my-1 min-w-0 rounded-md", children: [_jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5 text-xs", children: [_jsx(Database, { className: "size-3.5 shrink-0" }), _jsxs("span", { className: "font-medium", children: ["Data deleted for ", result.appId] }), _jsx(Check, { className: "size-3 shrink-0 text-green-600" })] }), (result.workspaceName || result.workspaceId) && (_jsxs("div", { className: "border-border/50 border-t px-2 py-1 text-[10px]", children: ["Workspace:", ' ', _jsx("span", { className: "font-medium", children: result.workspaceName || result.workspaceId }), result.workspaceName && result.workspaceId && (_jsxs("span", { className: "text-muted-foreground/70", children: [' ', "(", result.workspaceId, ")"] }))] }))] }, toolCallId));
578
634
  },
579
635
  renderApproval: (approval, onRespond) => {
580
- const { appId } = (approval.args ?? {});
581
- return (_jsxs(ToolApproval, { state: "approval-requested", children: [_jsx(ToolApprovalHeader, { children: _jsxs(ToolApprovalRequest, { children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-amber-600", children: [_jsx(AlertTriangle, { className: "size-3.5" }), "Delete app data"] }), _jsxs("div", { className: "text-muted-foreground mb-2 text-[10px]", children: ["Delete all data for ", _jsx("strong", { children: appId }), " in this workspace?"] }), _jsx("div", { className: "rounded bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-700", children: "This will permanently delete the app's database, files, and cache. The app will remain installed but start fresh." })] }) }), _jsxs(ToolApprovalActions, { children: [_jsx(ToolApprovalAction, { variant: "outline", onClick: () => onRespond({
636
+ const { appId, workspaceId } = (approval.args ?? {});
637
+ return (_jsxs(ToolApproval, { state: "approval-requested", children: [_jsx(ToolApprovalHeader, { children: _jsxs(ToolApprovalRequest, { children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-amber-600", children: [_jsx(AlertTriangle, { className: "size-3.5" }), "Delete app data"] }), _jsxs("div", { className: "text-muted-foreground mb-2 text-[10px]", children: ["Delete all data for ", _jsx("strong", { children: appId }), " in workspace ID", ' ', _jsx("strong", { children: workspaceId || 'unknown' }), "?"] }), _jsxs("div", { className: "bg-muted/50 mb-2 grid gap-1 rounded px-2 py-1.5 text-[10px]", children: [_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "App" }), _jsx("code", { className: "bg-background/70 truncate rounded px-1 font-mono", children: appId || 'unknown' })] }), _jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Target workspace ID" }), _jsx("code", { className: "bg-background/70 truncate rounded px-1 font-mono", children: workspaceId || 'unknown' })] })] }), _jsx("div", { className: "rounded bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-700", children: "This permanently deletes this app's database, files, and cache only in the target workspace. The app will remain installed and start fresh there." })] }) }), _jsxs(ToolApprovalActions, { children: [_jsx(ToolApprovalAction, { variant: "outline", onClick: () => onRespond({
582
638
  approvalId: approval.approvalId,
583
639
  approved: false,
584
640
  reason: 'User cancelled',
@@ -1,6 +1,6 @@
1
1
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
2
2
  import * as React from 'react';
3
- declare function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
3
+ declare const ScrollArea: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
4
4
  declare function ScrollBar({ className, orientation, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>): import("react/jsx-runtime").JSX.Element;
5
5
  export { ScrollArea, ScrollBar };
6
6
  //# sourceMappingURL=scroll-area.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-area.d.ts","sourceRoot":"","sources":["../../../src/components/ui/scroll-area.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAClE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC,2CAiBvD;AAED,iBAAS,SAAS,CAAC,EACjB,SAAS,EACT,WAAwB,EACxB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,mBAAmB,CAAC,mBAAmB,CAAC,2CAqBtE;AAED,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"scroll-area.d.ts","sourceRoot":"","sources":["../../../src/components/ui/scroll-area.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAClE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,QAAA,MAAM,UAAU,+JAqBd,CAAA;AAGF,iBAAS,SAAS,CAAC,EACjB,SAAS,EACT,WAAwB,EACxB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,mBAAmB,CAAC,mBAAmB,CAAC,2CAqBtE;AAED,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA"}
@@ -1,10 +1,12 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
4
+ import * as React from 'react';
4
5
  import { cn } from '../../lib/utils';
5
- function ScrollArea({ className, children, ...props }) {
6
- return (_jsxs(ScrollAreaPrimitive.Root, { "data-slot": "scroll-area", className: cn('relative', className), ...props, children: [_jsx(ScrollAreaPrimitive.Viewport, { "data-slot": "scroll-area-viewport", className: "focus-visible:ring-ring/50 size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px]", children: children }), _jsx(ScrollBar, {}), _jsx(ScrollAreaPrimitive.Corner, {})] }));
7
- }
6
+ const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => {
7
+ return (_jsxs(ScrollAreaPrimitive.Root, { ref: ref, "data-slot": "scroll-area", className: cn('relative', className), ...props, children: [_jsx(ScrollAreaPrimitive.Viewport, { "data-slot": "scroll-area-viewport", className: "focus-visible:ring-ring/50 size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px]", children: children }), _jsx(ScrollBar, {}), _jsx(ScrollAreaPrimitive.Corner, {})] }));
8
+ });
9
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
8
10
  function ScrollBar({ className, orientation = 'vertical', ...props }) {
9
11
  return (_jsx(ScrollAreaPrimitive.ScrollAreaScrollbar, { "data-slot": "scroll-area-scrollbar", orientation: orientation, className: cn('flex touch-none select-none p-px transition-colors', orientation === 'vertical' &&
10
12
  'h-full w-2.5 border-l border-l-transparent', orientation === 'horizontal' &&
@@ -7,10 +7,22 @@ export type CommandAction = {
7
7
  } | {
8
8
  type: 'message';
9
9
  payload: unknown;
10
+ command?: string;
10
11
  } | {
11
12
  type: 'focus';
12
13
  target: string;
13
14
  };
15
+ /**
16
+ * Optional visual marker apps can attach to command labels.
17
+ */
18
+ export interface AppCommandIndicator {
19
+ /** Shape to render. Defaults to a small dot. */
20
+ type?: 'dot';
21
+ /** Accessible label and hover title for the indicator. */
22
+ label: string;
23
+ /** CSS color value for the indicator. Defaults to the desktop primary color. */
24
+ color?: string;
25
+ }
14
26
  /**
15
27
  * Command definition that apps expose via /_moldable/commands
16
28
  */
@@ -21,8 +33,12 @@ export interface AppCommand {
21
33
  label: string;
22
34
  /** Optional keyboard shortcut (single key, shown as hint) */
23
35
  shortcut?: string;
36
+ /** Optional supporting text shown below the label */
37
+ description?: string;
24
38
  /** Optional icon (emoji or lucide icon name) */
25
39
  icon?: string;
40
+ /** Optional app-defined visual marker shown next to the command label */
41
+ indicator?: AppCommandIndicator;
26
42
  /** Optional group/category for organizing commands */
27
43
  group?: string;
28
44
  /** What happens when the command is executed */
@@ -84,6 +100,39 @@ export interface DownloadFileOptions {
84
100
  /** If true, data is base64-encoded binary; if false, data is plain text */
85
101
  isBase64?: boolean;
86
102
  }
103
+ export interface MoldableAppCallOptions {
104
+ scopes?: string[];
105
+ timeoutMs?: number;
106
+ requestAccess?: boolean;
107
+ }
108
+ export interface MoldableAppCallMessage {
109
+ type: 'moldable:app-call';
110
+ requestId: string;
111
+ targetAppId: string;
112
+ method: string;
113
+ params?: unknown;
114
+ scopes?: string[];
115
+ requestAccess?: boolean;
116
+ }
117
+ export interface MoldableAppCallResultMessage<T = unknown> {
118
+ type: 'moldable:app-call-result';
119
+ requestId: string;
120
+ ok: boolean;
121
+ result?: T;
122
+ error?: {
123
+ code?: string;
124
+ message?: string;
125
+ detail?: unknown;
126
+ };
127
+ }
128
+ /**
129
+ * Call another Moldable app through the desktop broker.
130
+ *
131
+ * The target app owns its data and provider auth. The desktop broker handles
132
+ * workspace-scoped access grants and forwards the request to the target app's
133
+ * private /api/moldable/rpc endpoint.
134
+ */
135
+ export declare function callMoldableApp<T = unknown>(targetAppId: string, method: string, params?: unknown, options?: MoldableAppCallOptions): Promise<T>;
87
136
  /**
88
137
  * Trigger a file download via Moldable's native save dialog.
89
138
  * Works inside Moldable's iframe environment where browser downloads don't work.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,MAAM,EAAE,aAAa,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC,QAwBtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,QAgBrC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,QAMA;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqExE"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"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"}
@@ -71,6 +71,62 @@ export function sendToMoldable(message) {
71
71
  }
72
72
  window.parent.postMessage(message, '*');
73
73
  }
74
+ function makeRequestId(prefix) {
75
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
76
+ return `${prefix}-${crypto.randomUUID()}`;
77
+ }
78
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
79
+ }
80
+ /**
81
+ * Call another Moldable app through the desktop broker.
82
+ *
83
+ * The target app owns its data and provider auth. The desktop broker handles
84
+ * workspace-scoped access grants and forwards the request to the target app's
85
+ * private /api/moldable/rpc endpoint.
86
+ */
87
+ export function callMoldableApp(targetAppId, method, params, options = {}) {
88
+ return new Promise((resolve, reject) => {
89
+ if (!isInMoldable()) {
90
+ reject(new Error('App-to-app calls are only available inside Moldable.'));
91
+ return;
92
+ }
93
+ const requestId = makeRequestId('app-call');
94
+ const timeoutMs = options.timeoutMs ?? 30_000;
95
+ const timeout = window.setTimeout(() => {
96
+ window.removeEventListener('message', handleResponse);
97
+ reject(new Error('App-to-app call timed out.'));
98
+ }, timeoutMs);
99
+ function handleResponse(event) {
100
+ if (event.data?.type !== 'moldable:app-call-result')
101
+ return;
102
+ if (event.data?.requestId !== requestId)
103
+ return;
104
+ window.clearTimeout(timeout);
105
+ window.removeEventListener('message', handleResponse);
106
+ const response = event.data;
107
+ if (response.ok) {
108
+ resolve(response.result);
109
+ return;
110
+ }
111
+ const error = new Error(response.error?.message ||
112
+ response.error?.code ||
113
+ 'App-to-app call failed.');
114
+ error.code = response.error?.code;
115
+ error.detail = response.error?.detail;
116
+ reject(error);
117
+ }
118
+ window.addEventListener('message', handleResponse);
119
+ sendToMoldable({
120
+ type: 'moldable:app-call',
121
+ requestId,
122
+ targetAppId,
123
+ method,
124
+ params,
125
+ scopes: options.scopes ?? [method],
126
+ requestAccess: options.requestAccess ?? true,
127
+ });
128
+ });
129
+ }
74
130
  /**
75
131
  * Trigger a file download via Moldable's native save dialog.
76
132
  * Works inside Moldable's iframe environment where browser downloads don't work.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@moldable-ai/ui",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Shared UI components for Moldable applications",
5
5
  "author": "Desiderata LLC",
6
- "license": "Elastic-2.0",
6
+ "license": "SEE LICENSE IN LICENSE",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/moldable-ai/moldable.git",
@@ -33,14 +33,6 @@
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
- },
44
36
  "dependencies": {
45
37
  "@base-ui/react": "^1.1.0",
46
38
  "@hookform/resolvers": "^5.2.2",
@@ -97,18 +89,26 @@
97
89
  "zod": "^4.3.5"
98
90
  },
99
91
  "devDependencies": {
100
- "@moldable-ai/eslint-config": "workspace:*",
101
- "@moldable-ai/prettier-config": "workspace:*",
102
- "@moldable-ai/typescript-config": "workspace:*",
103
92
  "@types/node": "^24.0.3",
104
93
  "@types/react": "^19.1.6",
105
94
  "@types/react-dom": "^19.1.6",
106
95
  "jsdom": "^26.1.0",
107
96
  "typescript": "^5.8.3",
108
- "vitest": "^3.2.4"
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"
109
101
  },
110
102
  "peerDependencies": {
111
103
  "react": "^19.0.0",
112
104
  "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
+ }
@@ -6,7 +6,7 @@
6
6
  @custom-variant dark (&:is(.dark *));
7
7
 
8
8
  /* Ensure Tailwind scans UI component class strings */
9
- @source "../dist/**/*.{js,jsx,ts,tsx}";
9
+ @source "../../dist/**/*.{js,jsx,ts,tsx}";
10
10
  @source "../components/**/*.{js,jsx,ts,tsx}";
11
11
  @source "../lib/**/*.{js,jsx,ts,tsx}";
12
12
  @source "../hooks/**/*.{js,jsx,ts,tsx}";