lemma-sdk 0.2.11 → 0.2.13
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/README.md +103 -0
- package/dist/react/components/AssistantChrome.d.ts +87 -0
- package/dist/react/components/AssistantChrome.js +95 -0
- package/dist/react/components/AssistantEmbedded.d.ts +8 -0
- package/dist/react/components/AssistantEmbedded.js +13 -0
- package/dist/react/components/AssistantExperience.d.ts +4 -2
- package/dist/react/components/AssistantExperience.js +39 -39
- package/dist/react/components/assistant-types.d.ts +3 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +2 -0
- package/dist/react/useAssistantController.js +7 -5
- package/dist/react/useAssistantRuntime.js +18 -11
- package/dist/react/useAssistantSession.js +26 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -218,11 +218,114 @@ function AssistantSurface() {
|
|
|
218
218
|
}
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
+
The assistant UI now ships with built-in fallback theme tokens and a sensible default shell height, so it does not need app-specific CSS variables just to look presentable. If your app already defines the same CSS custom properties (`--bg-surface`, `--brand-primary`, etc.), your values will override the defaults.
|
|
222
|
+
|
|
223
|
+
Practical expectations:
|
|
224
|
+
|
|
225
|
+
- The components are React components styled with utility class names, so your app should already compile those class names (for example with Tailwind or the same utility CSS pipeline used by the app shell).
|
|
226
|
+
- `AssistantEmbedded` and `AssistantExperienceView` render with a default minimum height of about 640px. You can still place them in a taller container when you want a full-page assistant.
|
|
227
|
+
- Host apps are still responsible for auth, `LemmaClient` setup, and any product-specific renderers for files, widgets, or navigation.
|
|
228
|
+
|
|
221
229
|
The intended split is:
|
|
222
230
|
|
|
223
231
|
- SDK: `useAssistantController`, message/tool normalization, plan parsing, tool rollups, and assistant UI primitives.
|
|
224
232
|
- App: modal shell, fullscreen behavior, route navigation, workspace/file viewers, and product-specific renderers.
|
|
225
233
|
|
|
234
|
+
Useful UI primitives exported from `lemma-sdk/react`:
|
|
235
|
+
|
|
236
|
+
- `AssistantHeader`
|
|
237
|
+
- `AssistantConversationList`
|
|
238
|
+
- `AssistantModelPicker`
|
|
239
|
+
- `AssistantShellLayout`
|
|
240
|
+
- `AssistantComposer`
|
|
241
|
+
- `AssistantMessageViewport`
|
|
242
|
+
- `AssistantAskOverlay`
|
|
243
|
+
- `AssistantPendingFileChip`
|
|
244
|
+
- `AssistantStatusPill`
|
|
245
|
+
- `MessageGroup`
|
|
246
|
+
- `PlanSummaryStrip`
|
|
247
|
+
|
|
248
|
+
For a direct plug-and-play component, `AssistantEmbedded` wires `useAssistantController` into the default assistant experience:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { AssistantEmbedded } from "lemma-sdk/react";
|
|
252
|
+
|
|
253
|
+
function SupportAssistant() {
|
|
254
|
+
return (
|
|
255
|
+
<AssistantEmbedded
|
|
256
|
+
client={client}
|
|
257
|
+
assistantId="support_assistant"
|
|
258
|
+
podId="pod_123"
|
|
259
|
+
title="Support Assistant"
|
|
260
|
+
subtitle="Ask questions about this pod."
|
|
261
|
+
placeholder="Message Support Assistant"
|
|
262
|
+
showConversationList
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
If you want the default embedded assistant to fill a page section cleanly, this is a good starting point:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
function AssistantPanel() {
|
|
272
|
+
return (
|
|
273
|
+
<section style={{ minHeight: 720 }}>
|
|
274
|
+
<AssistantEmbedded
|
|
275
|
+
client={client}
|
|
276
|
+
assistantId="support_assistant"
|
|
277
|
+
podId="pod_123"
|
|
278
|
+
title="Support Assistant"
|
|
279
|
+
subtitle="Answer questions, inspect files, and help with pod changes."
|
|
280
|
+
placeholder="Ask Support Assistant"
|
|
281
|
+
showConversationList
|
|
282
|
+
/>
|
|
283
|
+
</section>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Styling the assistant chrome directly
|
|
289
|
+
|
|
290
|
+
If you are composing your own shell from the exported primitives, you can reuse the same built-in theme values:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import {
|
|
294
|
+
AssistantComposer,
|
|
295
|
+
AssistantHeader,
|
|
296
|
+
AssistantMessageViewport,
|
|
297
|
+
assistantThemeStyles,
|
|
298
|
+
} from "lemma-sdk/react";
|
|
299
|
+
|
|
300
|
+
function CustomAssistantChrome() {
|
|
301
|
+
return (
|
|
302
|
+
<div style={{ ...assistantThemeStyles, minHeight: 640 }}>
|
|
303
|
+
<AssistantHeader
|
|
304
|
+
title="Workspace Assistant"
|
|
305
|
+
subtitle="Custom shell using SDK primitives"
|
|
306
|
+
/>
|
|
307
|
+
<AssistantMessageViewport>
|
|
308
|
+
{/* messages */}
|
|
309
|
+
</AssistantMessageViewport>
|
|
310
|
+
<AssistantComposer>
|
|
311
|
+
{/* composer */}
|
|
312
|
+
</AssistantComposer>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
You can also merge your own overrides:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { withAssistantThemeStyles } from "lemma-sdk/react";
|
|
322
|
+
|
|
323
|
+
const style = withAssistantThemeStyles({
|
|
324
|
+
"--brand-primary": "#0f766e",
|
|
325
|
+
"--brand-secondary": "#34d399",
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
226
329
|
### Assistant names (resource key)
|
|
227
330
|
|
|
228
331
|
Assistant CRUD is name-based:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { AssistantConversationListItem, AssistantConversationRenderArgs } from "./assistant-types.js";
|
|
3
|
+
export declare const assistantThemeStyles: CSSProperties;
|
|
4
|
+
export declare function withAssistantThemeStyles(style?: CSSProperties): CSSProperties;
|
|
5
|
+
export interface AssistantHeaderProps {
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
subtitle?: ReactNode;
|
|
8
|
+
badge?: ReactNode;
|
|
9
|
+
controls?: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: CSSProperties;
|
|
12
|
+
}
|
|
13
|
+
export interface AssistantMessageViewportProps extends ComponentPropsWithoutRef<"div"> {
|
|
14
|
+
innerClassName?: string;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare const AssistantMessageViewport: import("react").ForwardRefExoticComponent<AssistantMessageViewportProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
18
|
+
export interface AssistantShellLayoutProps {
|
|
19
|
+
sidebar?: ReactNode;
|
|
20
|
+
sidebarVisible?: boolean;
|
|
21
|
+
main: ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
}
|
|
25
|
+
export declare function AssistantShellLayout({ sidebar, sidebarVisible, main, className, style, }: AssistantShellLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function AssistantHeader({ title, subtitle, badge, controls, className, style, }: AssistantHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export interface AssistantConversationListProps {
|
|
28
|
+
conversations: AssistantConversationListItem[];
|
|
29
|
+
activeConversationId: string | null;
|
|
30
|
+
onSelectConversation: (conversationId: string) => void;
|
|
31
|
+
onNewConversation?: () => void;
|
|
32
|
+
renderConversationLabel?: (args: AssistantConversationRenderArgs) => ReactNode;
|
|
33
|
+
title?: ReactNode;
|
|
34
|
+
newLabel?: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
style?: CSSProperties;
|
|
37
|
+
}
|
|
38
|
+
export declare function AssistantConversationList({ conversations, activeConversationId, onSelectConversation, onNewConversation, renderConversationLabel, title, newLabel, className, style, }: AssistantConversationListProps): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export interface AssistantModelPickerProps<TValue extends string = string> {
|
|
40
|
+
value: TValue | null;
|
|
41
|
+
options: TValue[];
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
autoLabel?: ReactNode;
|
|
44
|
+
getOptionLabel?: (value: TValue) => ReactNode;
|
|
45
|
+
onChange: (value: TValue | null) => void;
|
|
46
|
+
className?: string;
|
|
47
|
+
style?: CSSProperties;
|
|
48
|
+
}
|
|
49
|
+
export declare function AssistantModelPicker<TValue extends string = string>({ value, options, disabled, autoLabel, getOptionLabel, onChange, className, style, }: AssistantModelPickerProps<TValue>): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export interface AssistantAskOverlayProps {
|
|
51
|
+
questionNumber: number;
|
|
52
|
+
totalQuestions: number;
|
|
53
|
+
question: ReactNode;
|
|
54
|
+
options: string[];
|
|
55
|
+
selectedOptions: string[];
|
|
56
|
+
canContinue: boolean;
|
|
57
|
+
continueLabel: ReactNode;
|
|
58
|
+
onSelectOption: (option: string) => void;
|
|
59
|
+
onContinue?: () => void;
|
|
60
|
+
onSkip?: () => void;
|
|
61
|
+
mode?: "single_select" | "multi_select" | "rank_priorities";
|
|
62
|
+
style?: CSSProperties;
|
|
63
|
+
}
|
|
64
|
+
export declare function AssistantAskOverlay({ questionNumber, totalQuestions, question, options, selectedOptions, canContinue, continueLabel, onSelectOption, onContinue, onSkip, mode, style, }: AssistantAskOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
65
|
+
export interface AssistantPendingFileChipProps {
|
|
66
|
+
label: ReactNode;
|
|
67
|
+
onRemove?: () => void;
|
|
68
|
+
className?: string;
|
|
69
|
+
style?: CSSProperties;
|
|
70
|
+
}
|
|
71
|
+
export interface AssistantComposerProps {
|
|
72
|
+
floating?: ReactNode;
|
|
73
|
+
status?: ReactNode;
|
|
74
|
+
pendingFiles?: ReactNode;
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
className?: string;
|
|
77
|
+
style?: CSSProperties;
|
|
78
|
+
}
|
|
79
|
+
export declare function AssistantComposer({ floating, status, pendingFiles, children, className, style, }: AssistantComposerProps): import("react/jsx-runtime").JSX.Element;
|
|
80
|
+
export declare function AssistantPendingFileChip({ label, onRemove, className, style, }: AssistantPendingFileChipProps): import("react/jsx-runtime").JSX.Element;
|
|
81
|
+
export interface AssistantStatusPillProps {
|
|
82
|
+
label: ReactNode;
|
|
83
|
+
subtle?: boolean;
|
|
84
|
+
className?: string;
|
|
85
|
+
style?: CSSProperties;
|
|
86
|
+
}
|
|
87
|
+
export declare function AssistantStatusPill({ label, subtle, className, style, }: AssistantStatusPillProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
function cx(...values) {
|
|
4
|
+
return values.filter(Boolean).join(" ");
|
|
5
|
+
}
|
|
6
|
+
function formatConversationTimestamp(value) {
|
|
7
|
+
const parsed = new Date(value);
|
|
8
|
+
if (Number.isNaN(parsed.valueOf())) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
12
|
+
month: "short",
|
|
13
|
+
day: "numeric",
|
|
14
|
+
timeZone: "UTC",
|
|
15
|
+
}).format(parsed);
|
|
16
|
+
}
|
|
17
|
+
export const assistantThemeStyles = {
|
|
18
|
+
"--bg-canvas": "#f4ede6",
|
|
19
|
+
"--bg-surface": "#fffaf4",
|
|
20
|
+
"--bg-subtle": "#efe4d8",
|
|
21
|
+
"--border-default": "#d8c4b2",
|
|
22
|
+
"--border-subtle": "#eadacc",
|
|
23
|
+
"--text-primary": "#23160f",
|
|
24
|
+
"--text-secondary": "#5f4838",
|
|
25
|
+
"--text-tertiary": "#8d715b",
|
|
26
|
+
"--text-on-brand": "#fff7f0",
|
|
27
|
+
"--text-inverse": "#fffaf4",
|
|
28
|
+
"--brand-primary": "#c45a2f",
|
|
29
|
+
"--brand-secondary": "#f2a65e",
|
|
30
|
+
"--brand-accent": "#df7a38",
|
|
31
|
+
"--brand-glow": "#f4d6b5",
|
|
32
|
+
"--state-success": "#1c8c63",
|
|
33
|
+
"--state-info": "#2e78b7",
|
|
34
|
+
"--state-error": "#be4637",
|
|
35
|
+
"--state-warning": "#a86811",
|
|
36
|
+
"--shadow-xs": "0 6px 18px -12px rgba(46, 27, 15, 0.35)",
|
|
37
|
+
"--shadow-sm": "0 12px 28px -20px rgba(46, 27, 15, 0.28)",
|
|
38
|
+
"--shadow-md": "0 20px 44px -28px rgba(46, 27, 15, 0.34)",
|
|
39
|
+
"--shadow-lg": "0 28px 64px -34px rgba(46, 27, 15, 0.4)",
|
|
40
|
+
};
|
|
41
|
+
export function withAssistantThemeStyles(style) {
|
|
42
|
+
return {
|
|
43
|
+
...assistantThemeStyles,
|
|
44
|
+
...style,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export const AssistantMessageViewport = forwardRef(function AssistantMessageViewport({ className, innerClassName, children, style, ...props }, ref) {
|
|
48
|
+
return (_jsx("div", { ref: ref, className: cx("min-h-0 flex-1 overflow-y-auto bg-[var(--bg-surface)] px-4 py-4", className), style: withAssistantThemeStyles(style), ...props, children: _jsx("div", { className: cx("mx-auto flex w-full max-w-5xl flex-col gap-3", innerClassName), children: children }) }));
|
|
49
|
+
});
|
|
50
|
+
export function AssistantShellLayout({ sidebar, sidebarVisible = false, main, className, style, }) {
|
|
51
|
+
return (_jsxs("div", { className: cx("mx-auto flex h-full w-full min-h-[640px] flex-col gap-3 rounded-[28px] border border-[color:color-mix(in_srgb,_var(--border-default)_72%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-canvas)_95%,_transparent),color-mix(in_srgb,_var(--bg-surface)_98%,_transparent))] p-3 font-sans antialiased shadow-[var(--shadow-lg)]", !!sidebar && sidebarVisible && "lg:grid lg:grid-cols-[280px_minmax(0,1fr)] lg:gap-3", className), style: withAssistantThemeStyles(style), children: [sidebar && sidebarVisible ? (_jsx("div", { className: "hidden h-full min-h-0 lg:block", children: sidebar })) : null, main] }));
|
|
52
|
+
}
|
|
53
|
+
export function AssistantHeader({ title, subtitle, badge, controls, className, style, }) {
|
|
54
|
+
return (_jsxs("div", { className: cx("flex items-center justify-between border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3.5", className), style: withAssistantThemeStyles(style), children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [badge ? (_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-2xl bg-[linear-gradient(145deg,var(--brand-primary),var(--brand-secondary))] shadow-[var(--shadow-xs)]", children: badge })) : null, _jsxs("div", { children: [_jsx("h3", { className: "text-[13px] font-semibold leading-tight tracking-[-0.01em] text-[var(--text-primary)]", children: title }), subtitle ? (_jsx("p", { className: "mt-0.5 text-[11px] text-[var(--text-tertiary)]", children: subtitle })) : null] })] }), controls ? (_jsx("div", { className: "flex items-center gap-1", children: controls })) : null] }));
|
|
55
|
+
}
|
|
56
|
+
export function AssistantConversationList({ conversations, activeConversationId, onSelectConversation, onNewConversation, renderConversationLabel, title = "Conversations", newLabel = "New", className, style, }) {
|
|
57
|
+
return (_jsxs("aside", { className: cx("flex h-full min-h-[560px] flex-col overflow-hidden rounded-[24px] border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-surface)_98%,_transparent),color-mix(in_srgb,_var(--bg-canvas)_74%,_transparent))] shadow-[var(--shadow-lg)]", className), style: withAssistantThemeStyles(style), children: [_jsx("div", { className: "border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3.5", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-[13px] font-semibold text-[var(--text-primary)]", children: title }), _jsxs("div", { className: "mt-1 text-[11px] text-[var(--text-tertiary)]", children: [conversations.length, " total"] })] }), onNewConversation ? (_jsx("button", { type: "button", onClick: onNewConversation, className: "rounded-full border border-[var(--border-default)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_92%,_white)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]", children: newLabel })) : null] }) }), _jsx("div", { className: "min-h-0 flex-1 space-y-2 overflow-y-auto p-3", children: conversations.map((conversation) => {
|
|
58
|
+
const isActive = conversation.id === activeConversationId;
|
|
59
|
+
return (_jsxs("button", { type: "button", onClick: () => onSelectConversation(conversation.id), className: cx("w-full rounded-2xl border px-3 py-3 text-left transition-all duration-200", isActive
|
|
60
|
+
? "border-[color:color-mix(in_srgb,_var(--brand-primary)_44%,_var(--border-default))] bg-[linear-gradient(135deg,color-mix(in_srgb,_var(--brand-glow)_52%,_var(--bg-surface)),color-mix(in_srgb,_var(--brand-primary)_8%,_var(--bg-surface)))] shadow-[var(--shadow-xs)]"
|
|
61
|
+
: "border-[var(--border-default)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_88%,_white)] hover:-translate-y-[1px] hover:bg-[var(--bg-subtle)] hover:shadow-[var(--shadow-xs)]"), children: [_jsx("div", { className: "truncate text-[12px] font-medium text-[var(--text-primary)]", children: renderConversationLabel
|
|
62
|
+
? renderConversationLabel({ conversation, isActive })
|
|
63
|
+
: (conversation.title || "Untitled conversation") }), _jsxs("div", { className: "mt-2 flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-[10px] uppercase tracking-[0.08em] text-[var(--text-tertiary)]", children: (conversation.status || "waiting").toLowerCase() }), conversation.updated_at ? (_jsx("div", { className: "truncate text-[10px] text-[var(--text-tertiary)]", children: formatConversationTimestamp(conversation.updated_at) })) : null] })] }, conversation.id));
|
|
64
|
+
}) })] }));
|
|
65
|
+
}
|
|
66
|
+
export function AssistantModelPicker({ value, options, disabled, autoLabel = "Auto", getOptionLabel, onChange, className, style, }) {
|
|
67
|
+
const autoValue = "__AUTO__";
|
|
68
|
+
return (_jsxs("select", { value: value ?? autoValue, onChange: (event) => onChange(event.target.value === autoValue ? null : event.target.value), disabled: disabled, className: cx("h-9 rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_88%,_white)] px-3.5 text-[11px] font-medium text-[var(--text-secondary)] shadow-[var(--shadow-xs)]", className), style: withAssistantThemeStyles(style), "aria-label": "Conversation model", title: "Conversation model", children: [_jsx("option", { value: autoValue, children: autoLabel }), options.map((option) => (_jsx("option", { value: option, children: getOptionLabel ? getOptionLabel(option) : option }, option)))] }));
|
|
69
|
+
}
|
|
70
|
+
export function AssistantAskOverlay({ questionNumber, totalQuestions, question, options, selectedOptions, canContinue, continueLabel, onSelectOption, onContinue, onSkip, mode = "single_select", style, }) {
|
|
71
|
+
return (_jsxs("div", { className: "space-y-2 rounded-[22px] border border-[color:color-mix(in_srgb,_var(--border-default)_82%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-surface)_98%,_transparent),color-mix(in_srgb,_var(--bg-canvas)_70%,_transparent))] p-3 shadow-[var(--shadow-md)]", style: withAssistantThemeStyles(style), children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("div", { className: "text-[11px] uppercase tracking-[0.12em] text-[var(--text-tertiary)]", children: ["Question ", questionNumber, " of ", totalQuestions] }), _jsx("p", { className: "mt-1 text-[14px] font-medium leading-6 text-[var(--text-primary)]", children: question })] }), onSkip ? (_jsx("button", { type: "button", onClick: onSkip, className: "rounded-md px-2 py-1 text-[12px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]", children: "Skip" })) : null] }), _jsx("div", { className: "max-h-[260px] space-y-1.5 overflow-y-auto pr-1", children: options.map((option, optionIndex) => {
|
|
72
|
+
const isSelected = selectedOptions.includes(option);
|
|
73
|
+
const rankLabel = mode === "rank_priorities" && isSelected
|
|
74
|
+
? selectedOptions.indexOf(option) + 1
|
|
75
|
+
: null;
|
|
76
|
+
return (_jsx("button", { type: "button", onClick: () => onSelectOption(option), className: cx("w-full rounded-lg border px-2.5 py-2 text-left text-[13px] transition-colors", isSelected
|
|
77
|
+
? "border-[color:color-mix(in_srgb,_var(--brand-primary)_64%,_var(--border-subtle))] bg-[color:color-mix(in_srgb,_var(--brand-primary)_14%,_transparent)] text-[var(--text-primary)]"
|
|
78
|
+
: "border-[var(--border-default)] bg-[var(--bg-canvas)] text-[var(--text-secondary)] hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]"), children: _jsxs("span", { className: "inline-flex items-center gap-2", children: [rankLabel ? (_jsx("span", { className: "inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--brand-primary)] px-1 text-[10px] font-semibold text-[var(--text-on-brand)]", children: rankLabel })) : (_jsx("span", { className: cx("inline-block h-2.5 w-2.5 rounded-full border", isSelected
|
|
79
|
+
? "border-[var(--brand-primary)] bg-[var(--brand-primary)]"
|
|
80
|
+
: "border-[var(--border-default)] bg-transparent") })), option] }) }, `${option}-${optionIndex}`));
|
|
81
|
+
}) }), onContinue ? (_jsx("div", { className: "flex justify-end", children: _jsx("button", { type: "button", onClick: onContinue, disabled: !canContinue, className: cx("rounded-md px-2.5 py-1.5 text-[12px] font-medium transition-colors", canContinue
|
|
82
|
+
? "bg-[var(--brand-primary)] text-[var(--text-on-brand)] hover:bg-[color:color-mix(in_srgb,_var(--brand-primary)_88%,_var(--text-primary))]"
|
|
83
|
+
: "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), children: continueLabel }) })) : null] }));
|
|
84
|
+
}
|
|
85
|
+
export function AssistantComposer({ floating, status, pendingFiles, children, className, style, }) {
|
|
86
|
+
return (_jsxs("div", { className: cx("relative rounded-[24px] border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-surface)_98%,_transparent),color-mix(in_srgb,_var(--bg-canvas)_82%,_transparent))] p-2.5 shadow-[var(--shadow-md)]", className), style: withAssistantThemeStyles(style), children: [floating ? (_jsx("div", { className: "absolute bottom-[calc(100%+8px)] left-0 right-0 z-20", children: floating })) : null, _jsx("div", { className: "min-h-[34px] px-2 pb-1", children: _jsx("div", { className: "flex min-h-[26px] items-center transition-opacity duration-200", children: status || _jsx("span", { "aria-hidden": "true", className: "inline-block h-[30px]" }) }) }), pendingFiles ? (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 px-1 pb-1.5", children: pendingFiles })) : null, children] }));
|
|
87
|
+
}
|
|
88
|
+
export function AssistantPendingFileChip({ label, onRemove, className, style, }) {
|
|
89
|
+
return (_jsxs("span", { className: cx("inline-flex max-w-full items-center gap-1.5 rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_72%,_transparent)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_70%,_var(--bg-subtle))] px-2.5 py-1 text-[11px] text-[var(--text-secondary)]", className), style: withAssistantThemeStyles(style), children: [_jsx("span", { className: "truncate max-w-[180px]", children: label }), onRemove ? (_jsx("button", { type: "button", onClick: onRemove, className: "inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-[var(--bg-canvas)]", title: "Remove file", children: "\u00D7" })) : null] }));
|
|
90
|
+
}
|
|
91
|
+
export function AssistantStatusPill({ label, subtle = false, className, style, }) {
|
|
92
|
+
return (_jsxs("div", { className: cx("inline-flex min-h-[32px] max-w-full items-center gap-2 rounded-full px-3 py-1.5 text-[12px] transition-all duration-200", subtle
|
|
93
|
+
? "border border-[color:color-mix(in_srgb,_var(--border-default)_72%,_transparent)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_90%,_transparent)] text-[var(--text-tertiary)]"
|
|
94
|
+
: "border border-[color:color-mix(in_srgb,_var(--brand-primary)_24%,_var(--border-default))] bg-[color:color-mix(in_srgb,_var(--brand-glow)_28%,_var(--bg-surface))] text-[var(--text-secondary)]", className), style: withAssistantThemeStyles(style), children: [_jsxs("span", { className: "relative inline-flex h-2.5 w-2.5 shrink-0", children: [_jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--brand-primary)]/45" }), _jsx("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full bg-[var(--brand-primary)]" })] }), _jsx("span", { className: "truncate", children: label })] }));
|
|
95
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LemmaClient } from "../../client.js";
|
|
2
|
+
import { type AssistantConversationScope } from "../useAssistantController.js";
|
|
3
|
+
import { type AssistantExperienceViewProps } from "./AssistantExperience.js";
|
|
4
|
+
export interface AssistantEmbeddedProps extends Omit<AssistantExperienceViewProps, "controller">, AssistantConversationScope {
|
|
5
|
+
client: LemmaClient;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled, ...props }: AssistantEmbeddedProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useAssistantController } from "../useAssistantController.js";
|
|
3
|
+
import { AssistantExperienceView } from "./AssistantExperience.js";
|
|
4
|
+
export function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled = true, ...props }) {
|
|
5
|
+
const controller = useAssistantController({
|
|
6
|
+
client,
|
|
7
|
+
podId: podId ?? undefined,
|
|
8
|
+
assistantId: assistantId ?? undefined,
|
|
9
|
+
organizationId: organizationId ?? undefined,
|
|
10
|
+
enabled,
|
|
11
|
+
});
|
|
12
|
+
return _jsx(AssistantExperienceView, { controller: controller, ...props });
|
|
13
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
2
|
import type { AssistantRenderableMessage, AssistantToolInvocation } from "../useAssistantController.js";
|
|
3
3
|
import type { AssistantControllerView, AssistantConversationRenderArgs, AssistantMessageRenderArgs, AssistantPendingFileRenderArgs, AssistantPresentedFileRenderArgs, AssistantToolRenderArgs } from "./assistant-types.js";
|
|
4
4
|
type PlanStatus = "pending" | "in_progress" | "completed";
|
|
@@ -39,6 +39,8 @@ export interface AssistantExperienceViewProps {
|
|
|
39
39
|
subtitle?: ReactNode;
|
|
40
40
|
placeholder?: string;
|
|
41
41
|
emptyState?: ReactNode;
|
|
42
|
+
className?: string;
|
|
43
|
+
style?: CSSProperties;
|
|
42
44
|
draft?: string;
|
|
43
45
|
onDraftChange?: (value: string) => void;
|
|
44
46
|
showConversationList?: boolean;
|
|
@@ -75,5 +77,5 @@ export declare function MessageGroup({ message, conversationId, onNavigateResour
|
|
|
75
77
|
renderPresentedFile?: (args: AssistantPresentedFileRenderArgs) => ReactNode;
|
|
76
78
|
renderToolInvocation?: (args: AssistantToolRenderArgs) => ReactNode;
|
|
77
79
|
}): import("react/jsx-runtime").JSX.Element;
|
|
78
|
-
export declare function AssistantExperienceView({ controller, title, subtitle, placeholder, emptyState, draft: controlledDraft, onDraftChange, showConversationList, onNavigateResource, renderConversationLabel, renderMessageContent, renderPresentedFile, renderPendingFile, renderToolInvocation, }: AssistantExperienceViewProps): import("react/jsx-runtime").JSX.Element;
|
|
80
|
+
export declare function AssistantExperienceView({ controller, title, subtitle, placeholder, emptyState, className, style, draft: controlledDraft, onDraftChange, showConversationList, onNavigateResource, renderConversationLabel, renderMessageContent, renderPresentedFile, renderPendingFile, renderToolInvocation, }: AssistantExperienceViewProps): import("react/jsx-runtime").JSX.Element;
|
|
79
81
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { AvailableModels } from "../../types.js";
|
|
4
|
+
import { AssistantComposer, AssistantConversationList, AssistantHeader, AssistantAskOverlay, AssistantMessageViewport, AssistantModelPicker, AssistantPendingFileChip, AssistantStatusPill, withAssistantThemeStyles, } from "./AssistantChrome.js";
|
|
4
5
|
function cx(...values) {
|
|
5
6
|
return values.filter(Boolean).join(" ");
|
|
6
7
|
}
|
|
@@ -506,7 +507,7 @@ function defaultPresentedFile({ filepath }) {
|
|
|
506
507
|
return (_jsxs("div", { className: "rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_78%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,var(--bg-surface)_96%,transparent),color-mix(in_srgb,var(--bg-canvas)_76%,transparent))] px-3 py-2.5", children: [_jsx("div", { className: "text-[14px] font-medium text-[var(--text-primary)]", children: fileNameFromPath(filepath) }), _jsx("div", { className: "mt-1 text-[12px] text-[var(--text-tertiary)]", children: filepath })] }));
|
|
507
508
|
}
|
|
508
509
|
function defaultPendingFile({ file, remove }) {
|
|
509
|
-
return (
|
|
510
|
+
return (_jsx(AssistantPendingFileChip, { label: file.name, onRemove: remove }));
|
|
510
511
|
}
|
|
511
512
|
export function PlanSummaryStrip({ plan, onHide }) {
|
|
512
513
|
const [showAll, setShowAll] = useState(false);
|
|
@@ -526,12 +527,28 @@ export function ThinkingIndicator() {
|
|
|
526
527
|
}
|
|
527
528
|
export function EmptyState({ onSendMessage }) {
|
|
528
529
|
const suggestions = [
|
|
529
|
-
{
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
530
|
+
{
|
|
531
|
+
label: "Summarize uploaded docs",
|
|
532
|
+
text: "Summarize the documents I upload and pull out decisions, owners, and next steps.",
|
|
533
|
+
accent: "Research",
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
label: "Build a CRM table",
|
|
537
|
+
text: "Create a table for leads with useful columns, sample views, and suggested automation hooks.",
|
|
538
|
+
accent: "Data",
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
label: "Draft a support workflow",
|
|
542
|
+
text: "Design a support triage workflow with an assistant, a queue, and follow-up tasks.",
|
|
543
|
+
accent: "Ops",
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
label: "Create a dashboard page",
|
|
547
|
+
text: "Build a React desk page for an executive dashboard with KPIs, trends, and recent activity.",
|
|
548
|
+
accent: "UI",
|
|
549
|
+
},
|
|
533
550
|
];
|
|
534
|
-
return (_jsxs("div", { className: "
|
|
551
|
+
return (_jsxs("div", { className: "px-2 py-7", children: [_jsxs("div", { className: "mx-auto max-w-2xl text-center", children: [_jsx("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-[18px] bg-[linear-gradient(145deg,var(--brand-primary),var(--brand-secondary))] shadow-[var(--shadow-sm)]", children: _jsx("span", { className: "text-[var(--text-on-brand)] text-sm font-semibold tracking-[0.18em]", children: "AI" }) }), _jsx("div", { className: "inline-flex items-center rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_78%,_transparent)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_84%,_white)] px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-[var(--text-tertiary)]", children: "Start with a concrete job" }), _jsx("h4", { className: "mt-4 text-[20px] font-semibold tracking-[-0.02em] text-[var(--text-primary)]", children: "What can I help you build?" }), _jsx("p", { className: "mx-auto mt-2 max-w-xl text-[13px] leading-relaxed text-[var(--text-tertiary)]", children: "I can shape workflows, spin up data structures, write desk UI, and turn rough ideas into working pod changes." })] }), _jsx("div", { className: "mx-auto mt-6 grid max-w-3xl grid-cols-1 gap-3 sm:grid-cols-2", children: suggestions.map((suggestion, index) => (_jsx("button", { onClick: () => onSendMessage(suggestion.text), className: "group rounded-[22px] border border-[color:color-mix(in_srgb,_var(--border-default)_78%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-surface)_94%,_white),color-mix(in_srgb,_var(--bg-canvas)_64%,_transparent))] px-4 py-4 text-left shadow-[var(--shadow-xs)] transition-all duration-200 hover:-translate-y-[2px] hover:border-[color:color-mix(in_srgb,_var(--brand-accent)_48%,_var(--border-subtle))] hover:shadow-[var(--shadow-sm)]", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "inline-flex rounded-full bg-[color:color-mix(in_srgb,_var(--brand-glow)_46%,_white)] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--brand-primary)]", children: suggestion.accent }), _jsx("div", { className: "mt-3 text-[14px] font-semibold tracking-[-0.01em] text-[var(--text-primary)]", children: suggestion.label }), _jsx("div", { className: "mt-1.5 text-[12px] leading-5 text-[var(--text-secondary)]", children: suggestion.text })] }), _jsx("span", { className: "mt-0.5 text-[var(--text-tertiary)] transition-all group-hover:translate-x-0.5 group-hover:text-[var(--brand-primary)]", children: "\u203A" })] }) }, `${suggestion.label}-${index}`))) })] }));
|
|
535
552
|
}
|
|
536
553
|
function ReasoningPartCard({ text, isStreaming, durationMs, }) {
|
|
537
554
|
return (_jsxs("details", { className: "group", open: isStreaming, children: [_jsxs("summary", { className: "list-none cursor-pointer inline-flex items-center gap-1.5 text-[12px] leading-5 text-[var(--text-tertiary)]", children: [_jsx("span", { className: "transition-transform group-open:rotate-90", children: "\u203A" }), _jsx("span", { className: cx("font-semibold", isStreaming && "text-transparent bg-clip-text bg-[linear-gradient(110deg,var(--text-secondary),35%,var(--brand-accent),50%,var(--text-secondary),65%)] bg-[length:250%_100%] animate-pulse"), children: isStreaming ? "Thinking" : `Thought${durationMs ? ` · ${Math.max(1, Math.round(durationMs / 1000))}s` : ""}` })] }), _jsx("div", { className: "mt-1 pl-4 border-l border-[var(--border-default)]", children: _jsx("pre", { className: "text-[11px] leading-5 text-[var(--text-tertiary)] whitespace-pre-wrap font-mono", children: text }) })] }));
|
|
@@ -754,7 +771,7 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
|
|
|
754
771
|
return null;
|
|
755
772
|
}), presentableFilepaths.length > 0 ? (_jsx(PresentFilesCard, { filepaths: presentableFilepaths, conversationId: conversationId, renderPresentedFile: renderPresentedFile })) : null] })] }));
|
|
756
773
|
}
|
|
757
|
-
export function AssistantExperienceView({ controller, title = "Lemma Assistant", subtitle = "Ask across your workspace and organization.", placeholder = "Message Lemma Assistant", emptyState, draft: controlledDraft, onDraftChange, showConversationList = false, onNavigateResource, renderConversationLabel = defaultConversationLabel, renderMessageContent = defaultMessageContent, renderPresentedFile, renderPendingFile = defaultPendingFile, renderToolInvocation, }) {
|
|
774
|
+
export function AssistantExperienceView({ controller, title = "Lemma Assistant", subtitle = "Ask across your workspace and organization.", placeholder = "Message Lemma Assistant", emptyState, className, style, draft: controlledDraft, onDraftChange, showConversationList = false, onNavigateResource, renderConversationLabel = defaultConversationLabel, renderMessageContent = defaultMessageContent, renderPresentedFile, renderPendingFile = defaultPendingFile, renderToolInvocation, }) {
|
|
758
775
|
const [draft, setDraft] = useControllableDraft(controlledDraft, onDraftChange);
|
|
759
776
|
const [isPlanHidden, setIsPlanHidden] = useState(false);
|
|
760
777
|
const [dismissedAskToolCallIds, setDismissedAskToolCallIds] = useState([]);
|
|
@@ -992,41 +1009,24 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
|
|
|
992
1009
|
? effectiveAskOverlayState.answers[effectiveAskOverlayState.currentQuestionIndex] || []
|
|
993
1010
|
: [];
|
|
994
1011
|
const canContinueAsk = activeAskAnswers.length > 0;
|
|
995
|
-
return (_jsxs("div", { className: cx("flex h-full min-h-
|
|
996
|
-
const isActive = conversation.id === controller.activeConversationId;
|
|
997
|
-
return (_jsxs("button", { type: "button", onClick: () => controller.selectConversation(conversation.id), className: cx("w-full rounded-xl border px-3 py-2.5 text-left transition-colors", isActive
|
|
998
|
-
? "border-[color:color-mix(in_srgb,_var(--brand-primary)_44%,_var(--border-default))] bg-[color:color-mix(in_srgb,_var(--brand-glow)_42%,_var(--bg-surface))]"
|
|
999
|
-
: "border-[var(--border-default)] bg-[var(--bg-surface)] hover:bg-[var(--bg-subtle)]"), children: [_jsx("div", { className: "text-[12px] font-medium text-[var(--text-primary)]", children: renderConversationLabel({ conversation, isActive }) }), _jsx("div", { className: "mt-1 text-[10px] uppercase tracking-[0.08em] text-[var(--text-tertiary)]", children: (conversation.status || "waiting").toLowerCase() })] }, conversation.id));
|
|
1000
|
-
}) })] })) : null, _jsxs("div", { className: "flex h-full min-h-0 flex-col gap-3", children: [_jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] shadow-[var(--shadow-lg)]", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "h-7 w-7 rounded-full bg-[linear-gradient(135deg,var(--brand-primary),var(--brand-secondary))] flex items-center justify-center shadow-[var(--shadow-xs)]", children: _jsx("span", { className: "text-[var(--text-on-brand)] text-xs", children: "\u2728" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-semibold text-[var(--text-primary)] text-[13px] leading-tight", children: title }), _jsx("p", { className: "text-[11px] text-[var(--text-tertiary)]", children: subtitle })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("select", { value: controller.conversationModel || "", onChange: (event) => { void handleModelChange(event.target.value || null); }, disabled: isConversationBusy || isUpdatingModel, className: "h-8 rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] px-3 text-[11px] text-[var(--text-secondary)]", children: [_jsx("option", { value: "", children: "Auto" }), availableModels.map((availableModel) => (_jsx("option", { value: availableModel, children: availableModel }, availableModel)))] }), _jsx("button", { type: "button", onClick: controller.clearMessages, title: "New conversation", className: "inline-flex h-8 w-8 items-center justify-center rounded-full text-[var(--text-tertiary)] hover:bg-[var(--bg-subtle)] hover:text-[var(--text-secondary)]", children: "\u21BA" })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-3 min-h-[180px] bg-[var(--bg-surface)]", ref: messagesContainerRef, onScroll: updatePinnedState, children: [controller.messages.length === 0 && !isConversationBusy ? (emptyState || _jsx(EmptyState, { onSendMessage: (message) => { void controller.sendMessage(message); } })) : null, (controller.isLoadingMessages && controller.messages.length === 0) ? (_jsx("div", { className: "flex justify-center py-6", children: _jsx("span", { className: "text-[var(--text-tertiary)] text-sm", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "flex justify-center py-1", children: _jsx("span", { className: "text-[var(--text-tertiary)] text-xs", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
|
|
1012
|
+
return (_jsxs("div", { className: cx("relative isolate flex h-full w-full min-h-[640px] flex-col gap-3 overflow-hidden rounded-[30px] border border-[color:color-mix(in_srgb,_var(--border-default)_72%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-canvas)_95%,_transparent),color-mix(in_srgb,_var(--bg-surface)_98%,_transparent))] p-3 font-sans antialiased shadow-[var(--shadow-lg)]", showConversationList && "lg:grid lg:grid-cols-[280px_minmax(0,1fr)] lg:gap-3", className), style: withAssistantThemeStyles(style), children: [_jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-x-0 top-0 h-44 bg-[radial-gradient(circle_at_top,color-mix(in_srgb,_var(--brand-glow)_78%,_transparent),transparent_72%)] opacity-80" }), showConversationList ? (_jsx(AssistantConversationList, { conversations: controller.conversations, activeConversationId: controller.activeConversationId, onSelectConversation: (conversationId) => controller.selectConversation(conversationId), onNewConversation: controller.clearMessages, renderConversationLabel: renderConversationLabel, className: "relative z-[1] hidden lg:flex lg:flex-col" })) : null, _jsxs("div", { className: "relative z-[1] flex h-full min-h-0 flex-col gap-3", children: [_jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded-[24px] border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[linear-gradient(180deg,color-mix(in_srgb,_var(--bg-surface)_98%,_transparent),color-mix(in_srgb,_var(--bg-canvas)_74%,_transparent))] shadow-[var(--shadow-lg)]", children: [_jsx(AssistantHeader, { title: title, subtitle: subtitle, badge: _jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.14em] text-[var(--text-on-brand)]", children: "AI" }), controls: (_jsxs(_Fragment, { children: [_jsx(AssistantModelPicker, { value: controller.conversationModel, options: availableModels, onChange: (nextModel) => { void handleModelChange(nextModel); }, disabled: isConversationBusy || isUpdatingModel }), _jsx("button", { type: "button", onClick: controller.clearMessages, title: "New conversation", className: "inline-flex h-9 items-center justify-center rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[color:color-mix(in_srgb,_var(--bg-surface)_88%,_white)] px-3 text-[11px] font-medium text-[var(--text-secondary)] shadow-[var(--shadow-xs)] transition-colors hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]", children: "New" })] })) }), _jsxs(AssistantMessageViewport, { className: "min-h-[260px] bg-transparent", ref: messagesContainerRef, onScroll: updatePinnedState, children: [controller.messages.length === 0 && !isConversationBusy ? (emptyState || _jsx(EmptyState, { onSendMessage: (message) => { void controller.sendMessage(message); } })) : null, (controller.isLoadingMessages && controller.messages.length === 0) ? (_jsx("div", { className: "flex justify-center py-6", children: _jsx("span", { className: "text-[var(--text-tertiary)] text-sm", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "flex justify-center py-1", children: _jsx("span", { className: "text-[var(--text-tertiary)] text-xs", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
|
|
1001
1013
|
const previousRow = index > 0 ? displayMessageRows[index - 1] : null;
|
|
1002
1014
|
const showAssistantHeader = row.message.role !== "assistant"
|
|
1003
1015
|
? false
|
|
1004
1016
|
: previousRow?.message.role !== "assistant";
|
|
1005
1017
|
const includesLastRawMessage = row.sourceIndexes.includes(controller.messages.length - 1);
|
|
1006
1018
|
return (_jsx(MessageGroup, { message: row.message, onNavigateResource: onNavigateResource, onWidgetSendPrompt: handleWidgetSendPrompt, conversationId: controller.activeConversationId, isStreaming: isConversationBusy && includesLastRawMessage && row.message.role === "assistant", showAssistantHeader: showAssistantHeader, renderMessageContent: renderMessageContent, renderPresentedFile: renderPresentedFile, renderToolInvocation: renderToolInvocation }, row.id || index));
|
|
1007
|
-
}), isConversationBusy && controller.messages.length > 0 && !activeToolBanner && !lastMessageHasContent ? (_jsx(ThinkingIndicator, {})) : null, controller.error ? (_jsx("div", { className: "bg-[color:color-mix(in_srgb,_var(--state-error)_12%,_transparent)] border border-[color:color-mix(in_srgb,_var(--state-error)_48%,_var(--border-subtle))] rounded-lg p-3 text-xs text-[var(--state-error)] flex items-start gap-2.5", children: _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Something went wrong" }), _jsx("p", { className: "text-[var(--state-error)] mt-1", children: controller.error })] }) })) : null, (controller.messages.length > 0 || isConversationBusy || !!controller.error) ? (_jsx("div", { "aria-hidden": "true", className: "h-14 shrink-0" })) : null] })] }),
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
? "bg-[var(--brand-primary)] text-[var(--text-on-brand)] hover:bg-[color:color-mix(in_srgb,_var(--brand-primary)_88%,_var(--text-primary))]"
|
|
1019
|
-
: "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"),
|
|
1020
|
-
const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
|
|
1021
|
-
return (_jsx("div", { children: renderPendingFile({
|
|
1022
|
-
file,
|
|
1023
|
-
remove: () => controller.removePendingFile(fileKey),
|
|
1024
|
-
}) }, fileKey));
|
|
1025
|
-
}) })) : null, _jsxs("div", { className: "relative flex items-end gap-2", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: cx("mb-1.5 ml-1 h-9 w-9 rounded-full flex items-center justify-center transition-colors", isConversationBusy || controller.isUploadingFiles
|
|
1026
|
-
? "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"
|
|
1027
|
-
: "bg-[var(--bg-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-canvas)] hover:text-[var(--text-primary)]"), title: "Upload files", children: controller.isUploadingFiles ? "…" : "+" }), _jsx("textarea", { ref: inputRef, value: draft, onChange: (event) => setDraft(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, className: "flex-1 resize-none border-0 bg-transparent px-3 py-2.5 text-[14px] text-[var(--text-primary)] leading-6 focus:ring-0 focus:outline-none placeholder:text-[var(--text-tertiary)] min-h-[48px] max-h-[220px]", rows: 1, disabled: isConversationBusy }), _jsx("div", { className: "pb-1.5 pr-1.5", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: cx("h-9 w-9 rounded-full flex items-center justify-center transition-all duration-200", isConversationBusy
|
|
1028
|
-
? "bg-[var(--text-primary)] text-[var(--text-inverse)] hover:bg-[color:color-mix(in_srgb,_var(--text-primary)_80%,_transparent)] hover:scale-105"
|
|
1029
|
-
: draft.trim()
|
|
1030
|
-
? "bg-[var(--brand-primary)] text-[var(--text-on-brand)] shadow-[var(--shadow-xs)] hover:bg-[color:color-mix(in_srgb,_var(--brand-primary)_88%,_var(--text-primary))]"
|
|
1031
|
-
: "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "■" : "→" }) })] })] }))] })] })] }));
|
|
1019
|
+
}), isConversationBusy && controller.messages.length > 0 && !activeToolBanner && !lastMessageHasContent ? (_jsx(ThinkingIndicator, {})) : null, controller.error ? (_jsx("div", { className: "bg-[color:color-mix(in_srgb,_var(--state-error)_12%,_transparent)] border border-[color:color-mix(in_srgb,_var(--state-error)_48%,_var(--border-subtle))] rounded-lg p-3 text-xs text-[var(--state-error)] flex items-start gap-2.5", children: _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Something went wrong" }), _jsx("p", { className: "text-[var(--state-error)] mt-1", children: controller.error })] }) })) : null, (controller.messages.length > 0 || isConversationBusy || !!controller.error) ? (_jsx("div", { "aria-hidden": "true", className: "h-14 shrink-0" })) : null] })] }), _jsx(AssistantComposer, { floating: planSummary ? (isPlanHidden ? (_jsxs("button", { type: "button", onClick: () => setIsPlanHidden(false), className: "inline-flex items-center gap-2 rounded-lg border border-[var(--border-default)] bg-[var(--bg-surface)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]", children: ["Show plan (", planSummary.completedCount, "/", planSummary.steps.length, ")"] })) : (_jsx(PlanSummaryStrip, { plan: planSummary, onHide: () => setIsPlanHidden(true) }))) : undefined, status: isConversationBusy && activeToolBanner ? (_jsx(AssistantStatusPill, { label: activeToolBanner.summary, subtle: true })) : undefined, pendingFiles: controller.pendingFiles.length > 0 ? (_jsx(_Fragment, { children: controller.pendingFiles.map((file) => {
|
|
1020
|
+
const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
|
|
1021
|
+
return (_jsx("div", { children: renderPendingFile({
|
|
1022
|
+
file,
|
|
1023
|
+
remove: () => controller.removePendingFile(fileKey),
|
|
1024
|
+
}) }, fileKey));
|
|
1025
|
+
}) })) : undefined, children: activeAskQuestion && effectiveAskOverlayState && pendingAskUserInput ? (_jsx(AssistantAskOverlay, { questionNumber: effectiveAskOverlayState.currentQuestionIndex + 1, totalQuestions: pendingAskUserInput.questions.length, question: activeAskQuestion.question, options: activeAskQuestion.options, selectedOptions: activeAskAnswers, canContinue: canContinueAsk, continueLabel: effectiveAskOverlayState.currentQuestionIndex >= pendingAskUserInput.questions.length - 1 ? "Use answers" : "Continue", onSelectOption: updateAskAnswer, onContinue: activeAskQuestion.type !== "single_select" || pendingAskUserInput.questions.length > 1 ? continueAskQuestions : undefined, onSkip: () => dismissAskOverlay(effectiveAskOverlayState.toolCallId), mode: activeAskQuestion.type })) : (_jsxs("div", { className: "relative flex items-end gap-2", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: cx("mb-1.5 ml-1 flex h-10 w-10 items-center justify-center rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_78%,_transparent)] transition-colors", isConversationBusy || controller.isUploadingFiles
|
|
1026
|
+
? "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"
|
|
1027
|
+
: "bg-[color:color-mix(in_srgb,_var(--bg-surface)_78%,_white)] text-[var(--text-secondary)] hover:bg-[var(--bg-subtle)] hover:text-[var(--text-primary)]"), title: "Upload files", children: controller.isUploadingFiles ? "..." : "+" }), _jsx("textarea", { ref: inputRef, value: draft, onChange: (event) => setDraft(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, className: "min-h-[52px] max-h-[220px] flex-1 resize-none border-0 bg-transparent px-3 py-2.5 text-[14px] leading-6 text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:ring-0", rows: 1, disabled: isConversationBusy }), _jsx("div", { className: "pb-1.5 pr-1.5", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: cx("flex h-10 w-10 items-center justify-center rounded-full transition-all duration-200", isConversationBusy
|
|
1028
|
+
? "bg-[var(--text-primary)] text-[var(--text-inverse)] hover:scale-105 hover:bg-[color:color-mix(in_srgb,_var(--text-primary)_80%,_transparent)]"
|
|
1029
|
+
: draft.trim()
|
|
1030
|
+
? "bg-[var(--brand-primary)] text-[var(--text-on-brand)] shadow-[var(--shadow-xs)] hover:bg-[color:color-mix(in_srgb,_var(--brand-primary)_88%,_var(--text-primary))]"
|
|
1031
|
+
: "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "[]" : "->" }) })] })) })] })] }));
|
|
1032
1032
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
import type { AssistantRenderableMessage, AssistantToolInvocation } from "../useAssistantController.js";
|
|
3
3
|
export interface AssistantConversationListItem {
|
|
4
4
|
id: string;
|
|
@@ -61,6 +61,8 @@ export interface AssistantExperienceCustomizationProps {
|
|
|
61
61
|
subtitle?: ReactNode;
|
|
62
62
|
placeholder?: string;
|
|
63
63
|
emptyState?: ReactNode;
|
|
64
|
+
className?: string;
|
|
65
|
+
style?: CSSProperties;
|
|
64
66
|
draft?: string;
|
|
65
67
|
onDraftChange?: (value: string) => void;
|
|
66
68
|
showConversationList?: boolean;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -13,8 +13,12 @@ export type { UseAssistantRuntimeOptions, UseAssistantRuntimeResult, } from "./u
|
|
|
13
13
|
export { useAssistantController } from "./useAssistantController.js";
|
|
14
14
|
export type { AssistantAction, AssistantConversationScope, AssistantMessagePart, AssistantRenderableMessage, AssistantToolInvocation, UseAssistantControllerOptions, UseAssistantControllerResult, } from "./useAssistantController.js";
|
|
15
15
|
export type { AssistantConversationRenderArgs, AssistantControllerView, AssistantExperienceCustomizationProps, AssistantMessageRenderArgs, AssistantPendingFileRenderArgs, AssistantPresentedFileRenderArgs, AssistantToolRenderArgs, } from "./components/assistant-types.js";
|
|
16
|
+
export { AssistantAskOverlay, AssistantComposer, AssistantConversationList, AssistantHeader, AssistantMessageViewport, AssistantModelPicker, AssistantPendingFileChip, AssistantShellLayout, AssistantStatusPill, assistantThemeStyles, withAssistantThemeStyles, } from "./components/AssistantChrome.js";
|
|
17
|
+
export type { AssistantAskOverlayProps, AssistantComposerProps, AssistantConversationListProps, AssistantHeaderProps, AssistantMessageViewportProps, AssistantModelPickerProps, AssistantPendingFileChipProps, AssistantShellLayoutProps, AssistantStatusPillProps, } from "./components/AssistantChrome.js";
|
|
16
18
|
export { AssistantExperienceView } from "./components/AssistantExperience.js";
|
|
17
19
|
export type { ActiveToolBanner, AskUserInputQuestion, AssistantExperienceViewProps, DisplayMessageRow, PendingAskUserInput, PlanStepState, PlanSummaryState, } from "./components/AssistantExperience.js";
|
|
20
|
+
export { AssistantEmbedded } from "./components/AssistantEmbedded.js";
|
|
21
|
+
export type { AssistantEmbeddedProps } from "./components/AssistantEmbedded.js";
|
|
18
22
|
export { buildDisplayMessageRows, dedupToolInvocations, EmptyState, findPendingAskUserInput, formatAskUserInputAnswers, getActiveToolBanner, extractPresentFilePathsFromInvocation, latestPlanSummary, MessageGroup, PlanSummaryStrip, ThinkingIndicator, } from "./components/AssistantExperience.js";
|
|
19
23
|
export { useTaskSession } from "./useTaskSession.js";
|
|
20
24
|
export type { CreateTaskInput, UseTaskSessionOptions, UseTaskSessionResult, } from "./useTaskSession.js";
|
package/dist/react/index.js
CHANGED
|
@@ -5,7 +5,9 @@ export { useAssistantRun } from "./useAssistantRun.js";
|
|
|
5
5
|
export { useAssistantSession } from "./useAssistantSession.js";
|
|
6
6
|
export { useAssistantRuntime } from "./useAssistantRuntime.js";
|
|
7
7
|
export { useAssistantController } from "./useAssistantController.js";
|
|
8
|
+
export { AssistantAskOverlay, AssistantComposer, AssistantConversationList, AssistantHeader, AssistantMessageViewport, AssistantModelPicker, AssistantPendingFileChip, AssistantShellLayout, AssistantStatusPill, assistantThemeStyles, withAssistantThemeStyles, } from "./components/AssistantChrome.js";
|
|
8
9
|
export { AssistantExperienceView } from "./components/AssistantExperience.js";
|
|
10
|
+
export { AssistantEmbedded } from "./components/AssistantEmbedded.js";
|
|
9
11
|
export { buildDisplayMessageRows, dedupToolInvocations, EmptyState, findPendingAskUserInput, formatAskUserInputAnswers, getActiveToolBanner, extractPresentFilePathsFromInvocation, latestPlanSummary, MessageGroup, PlanSummaryStrip, ThinkingIndicator, } from "./components/AssistantExperience.js";
|
|
10
12
|
export { useTaskSession } from "./useTaskSession.js";
|
|
11
13
|
export { useFunctionSession } from "./useFunctionSession.js";
|
|
@@ -708,14 +708,16 @@ export function useAssistantController({ client, podId, assistantId, organizatio
|
|
|
708
708
|
}, [conversations]);
|
|
709
709
|
useEffect(() => {
|
|
710
710
|
const conversationId = activeConversationIdRef.current;
|
|
711
|
-
if (!conversationId)
|
|
712
|
-
|
|
713
|
-
if (!runtimeMessages || runtimeMessages.length === 0)
|
|
711
|
+
if (!conversationId) {
|
|
712
|
+
setMessages([]);
|
|
714
713
|
return;
|
|
714
|
+
}
|
|
715
715
|
const normalized = sortMessagesByCreatedAt(runtimeMessages)
|
|
716
|
-
.filter((message) =>
|
|
717
|
-
if (normalized.length === 0)
|
|
716
|
+
.filter((message) => message.conversation_id === conversationId);
|
|
717
|
+
if (normalized.length === 0) {
|
|
718
|
+
setMessages([]);
|
|
718
719
|
return;
|
|
720
|
+
}
|
|
719
721
|
const nextMessages = mapConversationMessages(normalized);
|
|
720
722
|
const pendingText = sessionStreamingText.trim();
|
|
721
723
|
if (pendingText.length > 0) {
|
|
@@ -43,8 +43,15 @@ function upsertRuntimeMessage(previous, incoming) {
|
|
|
43
43
|
next.push(incoming);
|
|
44
44
|
return next;
|
|
45
45
|
}
|
|
46
|
-
function toRuntimeMessage(message) {
|
|
47
|
-
|
|
46
|
+
function toRuntimeMessage(message, fallbackConversationId) {
|
|
47
|
+
const runtimeMessage = message;
|
|
48
|
+
if (runtimeMessage.conversation_id || !fallbackConversationId) {
|
|
49
|
+
return runtimeMessage;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...runtimeMessage,
|
|
53
|
+
conversation_id: fallbackConversationId,
|
|
54
|
+
};
|
|
48
55
|
}
|
|
49
56
|
function buildOptimisticId() {
|
|
50
57
|
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
|
|
@@ -56,16 +63,16 @@ export function useAssistantRuntime({ conversationId = null, sessionMessages = [
|
|
|
56
63
|
const [runtimeMessages, setRuntimeMessages] = useState([]);
|
|
57
64
|
const mergeMessages = useCallback((messages) => {
|
|
58
65
|
setRuntimeMessages((previous) => {
|
|
59
|
-
const merged = messages.reduce((accumulator, message) => upsertRuntimeMessage(accumulator, toRuntimeMessage(message)), previous);
|
|
66
|
+
const merged = messages.reduce((accumulator, message) => upsertRuntimeMessage(accumulator, toRuntimeMessage(message, conversationId)), previous);
|
|
60
67
|
return [...merged].sort((a, b) => messageTime(a) - messageTime(b));
|
|
61
68
|
});
|
|
62
|
-
}, []);
|
|
69
|
+
}, [conversationId]);
|
|
63
70
|
const replaceLoadedMessages = useCallback((messages) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}, []);
|
|
71
|
+
const normalized = messages
|
|
72
|
+
.map((message) => toRuntimeMessage(message, conversationId))
|
|
73
|
+
.filter((message) => !conversationId || message.conversation_id === conversationId);
|
|
74
|
+
setRuntimeMessages([...normalized].sort((a, b) => messageTime(a) - messageTime(b)));
|
|
75
|
+
}, [conversationId]);
|
|
69
76
|
const appendOptimisticUserMessage = useCallback((content, options) => {
|
|
70
77
|
const trimmed = content.trim();
|
|
71
78
|
const optimisticConversationId = options?.conversationId ?? conversationId ?? undefined;
|
|
@@ -98,8 +105,8 @@ export function useAssistantRuntime({ conversationId = null, sessionMessages = [
|
|
|
98
105
|
if (sessionMessages.length === 0)
|
|
99
106
|
return;
|
|
100
107
|
const normalized = sessionMessages
|
|
101
|
-
.map((message) => toRuntimeMessage(message))
|
|
102
|
-
.filter((message) => !conversationId ||
|
|
108
|
+
.map((message) => toRuntimeMessage(message, conversationId))
|
|
109
|
+
.filter((message) => !conversationId || message.conversation_id === conversationId);
|
|
103
110
|
if (normalized.length === 0)
|
|
104
111
|
return;
|
|
105
112
|
mergeMessages(normalized);
|
|
@@ -57,6 +57,7 @@ export function useAssistantSession(options) {
|
|
|
57
57
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
58
58
|
const [error, setError] = useState(null);
|
|
59
59
|
const abortRef = useRef(null);
|
|
60
|
+
const conversationIdRef = useRef(externalConversationId);
|
|
60
61
|
const statusRef = useRef(undefined);
|
|
61
62
|
const streamingTextRef = useRef("");
|
|
62
63
|
const autoResumedKeyRef = useRef(null);
|
|
@@ -65,20 +66,30 @@ export function useAssistantSession(options) {
|
|
|
65
66
|
const onMessageRef = useRef(onMessage);
|
|
66
67
|
const onErrorRef = useRef(onError);
|
|
67
68
|
const setConversationId = useCallback((nextConversationId) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
abortRef.current?.abort();
|
|
70
|
+
abortRef.current = null;
|
|
71
|
+
setConversationIdState((currentConversationId) => {
|
|
72
|
+
if (currentConversationId === nextConversationId) {
|
|
73
|
+
return currentConversationId;
|
|
74
|
+
}
|
|
75
|
+
autoResumedKeyRef.current = null;
|
|
76
|
+
streamingTextRef.current = "";
|
|
77
|
+
setStreamingText("");
|
|
73
78
|
setConversation(null);
|
|
74
79
|
setStatus(undefined);
|
|
75
80
|
statusRef.current = undefined;
|
|
76
81
|
setMessages([]);
|
|
77
|
-
|
|
82
|
+
setError(null);
|
|
83
|
+
setIsStreaming(false);
|
|
84
|
+
return nextConversationId;
|
|
85
|
+
});
|
|
78
86
|
}, []);
|
|
79
87
|
useEffect(() => {
|
|
80
|
-
|
|
81
|
-
}, [externalConversationId]);
|
|
88
|
+
setConversationId(externalConversationId);
|
|
89
|
+
}, [externalConversationId, setConversationId]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
conversationIdRef.current = conversationId;
|
|
92
|
+
}, [conversationId]);
|
|
82
93
|
useEffect(() => {
|
|
83
94
|
onEventRef.current = onEvent;
|
|
84
95
|
}, [onEvent]);
|
|
@@ -206,6 +217,13 @@ export function useAssistantSession(options) {
|
|
|
206
217
|
page_token: input.pageToken,
|
|
207
218
|
});
|
|
208
219
|
const nextMessages = response.items ?? [];
|
|
220
|
+
if (conversationIdRef.current !== id) {
|
|
221
|
+
return {
|
|
222
|
+
items: nextMessages,
|
|
223
|
+
limit: response.limit ?? input.limit ?? 20,
|
|
224
|
+
next_page_token: response.next_page_token,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
209
227
|
setMessages((previous) => nextMessages.reduce((accumulator, message) => upsertConversationMessage(accumulator, message), previous));
|
|
210
228
|
return {
|
|
211
229
|
items: nextMessages,
|