@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.
- package/LICENSE +75 -71
- package/dist/components/chat/chat-input.d.ts +15 -3
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-input.js +19 -8
- package/dist/components/chat/chat-message.d.ts +6 -0
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +15 -1
- package/dist/components/chat/chat-panel.d.ts +26 -2
- package/dist/components/chat/chat-panel.d.ts.map +1 -1
- package/dist/components/chat/chat-panel.js +58 -30
- package/dist/components/chat/conversation-history.d.ts +6 -1
- package/dist/components/chat/conversation-history.d.ts.map +1 -1
- package/dist/components/chat/conversation-history.js +6 -6
- package/dist/components/chat/index.d.ts +2 -2
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/model-selector.d.ts +3 -1
- package/dist/components/chat/model-selector.d.ts.map +1 -1
- package/dist/components/chat/model-selector.js +2 -2
- package/dist/components/chat/reasoning-effort-selector.d.ts +4 -1
- package/dist/components/chat/reasoning-effort-selector.d.ts.map +1 -1
- package/dist/components/chat/reasoning-effort-selector.js +6 -6
- package/dist/components/chat/tool-handlers.d.ts.map +1 -1
- package/dist/components/chat/tool-handlers.js +62 -6
- package/dist/components/ui/scroll-area.d.ts +1 -1
- package/dist/components/ui/scroll-area.d.ts.map +1 -1
- package/dist/components/ui/scroll-area.js +5 -3
- package/dist/lib/commands.d.ts +49 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +56 -0
- package/package.json +15 -15
- package/src/styles/index.css +1 -1
package/LICENSE
CHANGED
|
@@ -1,101 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Abbreviation
|
|
4
4
|
|
|
5
|
-
-
|
|
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
|
-
|
|
7
|
+
## Notice
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Copyright 2026 Desiderata LLC
|
|
12
10
|
|
|
13
|
-
##
|
|
11
|
+
## Terms and Conditions
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
### Licensor ("We")
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
18
16
|
|
|
19
|
-
The
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
also gets a copy of these terms.
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
software prominent notices stating that you have modified the software.
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
58
44
|
|
|
59
|
-
|
|
45
|
+
1. for your internal use and access;
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
these terms.
|
|
47
|
+
2. for non-commercial education;
|
|
63
48
|
|
|
64
|
-
|
|
49
|
+
3. for non-commercial research; and
|
|
65
50
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
54
|
+
### Patents
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
62
|
+
### Redistribution
|
|
82
63
|
|
|
83
|
-
The
|
|
84
|
-
|
|
85
|
-
of it.
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
86
66
|
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
17
|
-
|
|
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
|
-
//
|
|
27
|
-
const showStopButton = isResponding
|
|
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' : '
|
|
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
|
}
|
|
@@ -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;
|
|
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
|
|
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":"
|
|
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,
|
|
3
|
-
import { AlertCircle, ChevronDown, Code2, MessageCircle,
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
}, [
|
|
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
|
|
95
|
-
if (!
|
|
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
|
-
}, [
|
|
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 &&
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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: [
|
|
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:
|
|
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":"
|
|
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:
|
|
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,
|
|
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"}
|
|
@@ -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;
|
|
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-
|
|
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":"
|
|
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
|
-
|
|
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 (
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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' &&
|
package/dist/lib/commands.d.ts
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/commands.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Shared UI components for Moldable applications",
|
|
5
5
|
"author": "Desiderata LLC",
|
|
6
|
-
"license": "
|
|
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
|
+
}
|
package/src/styles/index.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
@custom-variant dark (&:is(.dark *));
|
|
7
7
|
|
|
8
8
|
/* Ensure Tailwind scans UI component class strings */
|
|
9
|
-
@source "
|
|
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}";
|