lemma-sdk 0.2.16 → 0.2.18

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 CHANGED
@@ -169,106 +169,107 @@ Notes:
169
169
 
170
170
  ## Assistants + Agent Runs
171
171
 
172
- ### React assistant controller + primitives
172
+ ### React assistant UI
173
173
 
174
- `lemma-sdk/react` now exposes the assistant controller plus the reusable UI primitives used by the app shell. A simple integration looks like this:
174
+ `lemma-sdk/react` ships the assistant controller, the default assistant experience, and the lower-level UI primitives used to build custom shells.
175
175
 
176
- If you want the SDK UI to look correct outside the Lemma app, import the bundled stylesheet once:
176
+ Import the bundled stylesheet once anywhere in your app:
177
177
 
178
178
  ```tsx
179
179
  import "lemma-sdk/react/styles.css";
180
180
  ```
181
181
 
182
- The stylesheet provides the default Lemma assistant theme tokens used by the SDK UI. Without it, components may render with missing colors, borders, and spacing in apps that do not already define the same CSS variables.
183
- The SDK UI now ships its own semantic assistant classes and default styling, so consumers should not need the Lemma app's internal theme setup or Tailwind config just to render the assistant correctly.
182
+ The stylesheet includes the SDK theme tokens and semantic assistant classes. You do not need the Lemma app's internal Tailwind setup just to render the assistant correctly.
184
183
 
185
- ```tsx
186
- import {
187
- MessageGroup,
188
- PlanSummaryStrip,
189
- ThinkingIndicator,
190
- buildDisplayMessageRows,
191
- getActiveToolBanner,
192
- latestPlanSummary,
193
- useAssistantController,
194
- } from "lemma-sdk/react";
184
+ The assistant UI renders markdown by default:
195
185
 
196
- function AssistantSurface() {
197
- const assistant = useAssistantController({
198
- client,
199
- assistantId: "uuid",
200
- podId: "pod_123",
201
- });
186
+ - GitHub-flavored markdown is enabled for assistant and user messages
187
+ - raw HTML is not rendered
188
+ - links open safely in a new tab by default
189
+ - lists, tables, blockquotes, inline code, and fenced code blocks are styled out of the box
202
190
 
203
- const rows = buildDisplayMessageRows(assistant.messages);
204
- const plan = latestPlanSummary(assistant.messages);
205
- const activeToolBanner = getActiveToolBanner(assistant.messages);
191
+ #### Choose an integration level
192
+
193
+ ##### 1. `AssistantEmbedded` for the fastest setup
194
+
195
+ Use `AssistantEmbedded` when you want a ready-made assistant surface with the SDK defaults.
206
196
 
197
+ ```tsx
198
+ import "lemma-sdk/react/styles.css";
199
+ import { AssistantEmbedded } from "lemma-sdk/react";
200
+
201
+ function SupportAssistant() {
207
202
  return (
208
- <div>
209
- {plan ? <PlanSummaryStrip plan={plan} onHide={() => {}} /> : null}
210
- {activeToolBanner ? <div>{activeToolBanner.summary}</div> : null}
211
-
212
- {rows.map((row, index) => (
213
- <MessageGroup
214
- key={row.id}
215
- message={row.message}
216
- conversationId={assistant.activeConversationId}
217
- onWidgetSendPrompt={(text) => assistant.sendMessage(text)}
218
- isStreaming={assistant.isActiveConversationRunning && row.sourceIndexes.includes(assistant.messages.length - 1)}
219
- showAssistantHeader={index === 0 || rows[index - 1]?.message.role !== "assistant"}
220
- renderMessageContent={({ message }) => <div>{message.content}</div>}
221
- />
222
- ))}
223
-
224
- {assistant.isActiveConversationRunning ? <ThinkingIndicator /> : null}
203
+ <div style={{ height: 720, minHeight: 0 }}>
204
+ <AssistantEmbedded
205
+ client={client}
206
+ podId="pod_123"
207
+ assistantId="uuid"
208
+ title="Support Assistant"
209
+ subtitle="Ask questions about this pod."
210
+ placeholder="Message Support Assistant"
211
+ showConversationList
212
+ theme="auto"
213
+ />
225
214
  </div>
226
215
  );
227
216
  }
228
217
  ```
229
218
 
230
- The intended split is:
231
-
232
- - SDK: `useAssistantController`, message/tool normalization, plan parsing, tool rollups, and assistant UI primitives.
233
- - App: modal shell, fullscreen behavior, route navigation, workspace/file viewers, and product-specific renderers.
219
+ Important notes:
234
220
 
235
- Useful UI primitives exported from `lemma-sdk/react`:
221
+ - `theme` accepts `"auto" | "light" | "dark"`
222
+ - `theme="auto"` follows the host app when it uses common selectors like `.dark`, `[data-theme="dark"]`, `[data-mode="dark"]`, and also falls back to `prefers-color-scheme`
223
+ - the parent container must have a real height; if it lives inside flex/grid, `min-height: 0` is usually needed too
224
+ - attachments are queued into the composer and sent with the next message by default
236
225
 
237
- - `AssistantHeader`
238
- - `AssistantConversationList`
239
- - `AssistantModelPicker`
240
- - `AssistantShellLayout`
241
- - `AssistantComposer`
242
- - `AssistantMessageViewport`
243
- - `AssistantAskOverlay`
244
- - `AssistantPendingFileChip`
245
- - `AssistantStatusPill`
246
- - `AssistantThemeScope`
247
- - `MessageGroup`
248
- - `PlanSummaryStrip`
226
+ ##### 2. `AssistantExperienceView` for the default UI with your own controller
249
227
 
250
- For a direct plug-and-play component, `AssistantEmbedded` wires `useAssistantController` into the default assistant experience:
228
+ Use `AssistantExperienceView` when you want the built-in assistant layout, but you need to own the controller lifecycle yourself.
251
229
 
252
230
  ```tsx
253
231
  import "lemma-sdk/react/styles.css";
254
- import { AssistantEmbedded } from "lemma-sdk/react";
232
+ import {
233
+ AssistantExperienceView,
234
+ AssistantThemeScope,
235
+ useAssistantController,
236
+ } from "lemma-sdk/react";
237
+
238
+ function ControlledAssistant() {
239
+ const assistant = useAssistantController({
240
+ client,
241
+ podId: "pod_123",
242
+ assistantId: "uuid",
243
+ });
255
244
 
256
- function SupportAssistant() {
257
245
  return (
258
- <AssistantEmbedded
259
- client={client}
260
- assistantId="uuid"
261
- podId="pod_123"
262
- title="Support Assistant"
263
- subtitle="Ask questions about this pod."
264
- placeholder="Message Support Assistant"
265
- showConversationList
266
- />
246
+ <AssistantThemeScope theme="dark" style={{ height: 720 }}>
247
+ <AssistantExperienceView
248
+ controller={assistant}
249
+ title="Support Assistant"
250
+ subtitle="Direct use of the default assistant experience."
251
+ placeholder="Message Support Assistant"
252
+ showConversationList
253
+ chromeStyle="subtle"
254
+ statusPlacement="inline"
255
+ />
256
+ </AssistantThemeScope>
267
257
  );
268
258
  }
269
259
  ```
270
260
 
271
- If you are composing your own shell with the SDK primitives, wrap it in `AssistantThemeScope` unless your app already provides matching theme tokens:
261
+ Useful props on `AssistantExperienceView`:
262
+
263
+ - `showConversationList`: show the built-in conversation sidebar
264
+ - `chromeStyle`: `"elevated" | "subtle" | "flat"`
265
+ - `statusPlacement`: `"inline" | "composer" | "none"`
266
+ - `renderMessageContent`: override markdown rendering for custom message content
267
+ - `renderToolInvocation`: replace the default tool activity renderer
268
+ - `renderPresentedFile` and `renderPendingFile`: customize attachment rendering
269
+
270
+ ##### 3. `useAssistantController` + primitives for a custom shell
271
+
272
+ Use the primitives when you want full control over layout and app chrome.
272
273
 
273
274
  ```tsx
274
275
  import "lemma-sdk/react/styles.css";
@@ -278,29 +279,109 @@ import {
278
279
  AssistantMessageViewport,
279
280
  AssistantShellLayout,
280
281
  AssistantThemeScope,
282
+ MessageGroup,
283
+ PlanSummaryStrip,
284
+ ThinkingIndicator,
285
+ buildDisplayMessageRows,
286
+ getActiveToolBanner,
287
+ latestPlanSummary,
288
+ useAssistantController,
281
289
  } from "lemma-sdk/react";
282
290
 
283
291
  function CustomAssistantShell() {
292
+ const assistant = useAssistantController({
293
+ client,
294
+ podId: "pod_123",
295
+ assistantId: "uuid",
296
+ });
297
+
298
+ const rows = buildDisplayMessageRows(assistant.messages);
299
+ const plan = latestPlanSummary(assistant.messages);
300
+ const activeToolBanner = getActiveToolBanner(assistant.messages);
301
+
284
302
  return (
285
- <AssistantThemeScope>
303
+ <AssistantThemeScope theme="auto" style={{ height: 720 }}>
286
304
  <AssistantShellLayout
287
- main={
305
+ main={(
288
306
  <div className="flex min-h-0 flex-1 flex-col gap-3">
289
- <AssistantHeader title="Lemma Assistant" subtitle="Ask anything" />
307
+ <AssistantHeader
308
+ title="Lemma Assistant"
309
+ subtitle="Ask anything"
310
+ />
311
+
312
+ {plan ? <PlanSummaryStrip plan={plan} onHide={() => {}} /> : null}
313
+ {activeToolBanner ? <div>{activeToolBanner.summary}</div> : null}
314
+
290
315
  <AssistantMessageViewport>
291
- <div>Messages go here</div>
316
+ {rows.map((row, index) => (
317
+ <MessageGroup
318
+ key={row.id}
319
+ message={row.message}
320
+ conversationId={assistant.activeConversationId}
321
+ onWidgetSendPrompt={(text) => assistant.sendMessage(text)}
322
+ isStreaming={assistant.isActiveConversationRunning && row.sourceIndexes.includes(assistant.messages.length - 1)}
323
+ showAssistantHeader={index === 0 || rows[index - 1]?.message.role !== "assistant"}
324
+ renderMessageContent={({ message }) => <div>{message.content}</div>}
325
+ />
326
+ ))}
327
+
328
+ {assistant.isActiveConversationRunning ? <ThinkingIndicator /> : null}
292
329
  </AssistantMessageViewport>
330
+
293
331
  <AssistantComposer>
294
332
  <textarea placeholder="Message Lemma Assistant" />
295
333
  </AssistantComposer>
296
334
  </div>
297
- }
335
+ )}
298
336
  />
299
337
  </AssistantThemeScope>
300
338
  );
301
339
  }
302
340
  ```
303
341
 
342
+ Useful primitives exported from `lemma-sdk/react`:
343
+
344
+ - `AssistantThemeScope`
345
+ - `AssistantHeader`
346
+ - `AssistantConversationList`
347
+ - `AssistantModelPicker`
348
+ - `AssistantShellLayout`
349
+ - `AssistantComposer`
350
+ - `AssistantMessageViewport`
351
+ - `AssistantAskOverlay`
352
+ - `AssistantPendingFileChip`
353
+ - `AssistantStatusPill`
354
+ - `MessageGroup`
355
+ - `PlanSummaryStrip`
356
+ - `ThinkingIndicator`
357
+
358
+ #### Theming
359
+
360
+ Use `AssistantThemeScope` around custom assistant layouts:
361
+
362
+ ```tsx
363
+ import { AssistantThemeScope } from "lemma-sdk/react";
364
+
365
+ <AssistantThemeScope theme="light">
366
+ <YourAssistant />
367
+ </AssistantThemeScope>
368
+ ```
369
+
370
+ Theme behavior:
371
+
372
+ - `theme="auto"`: follows host dark-mode selectors and system color scheme
373
+ - `theme="light"`: forces the light SDK palette
374
+ - `theme="dark"`: forces the dark SDK palette
375
+
376
+ If you use `AssistantEmbedded`, pass `theme` directly on that component instead of wrapping it again.
377
+
378
+ #### What belongs in the SDK vs your app
379
+
380
+ The intended split is:
381
+
382
+ - SDK: `useAssistantController`, message/tool normalization, markdown rendering, plan parsing, tool rollups, and reusable assistant UI primitives
383
+ - App: modal shell, fullscreen/window controls, route navigation, workspace/file viewers, and product-specific renderers
384
+
304
385
  ### Assistant names (resource key)
305
386
 
306
387
  Assistant CRUD is name-based:
@@ -8,8 +8,10 @@ export type AssistantResponse = {
8
8
  accessible_applications: Array<ApplicationAccessConfig>;
9
9
  accessible_folders: Array<string>;
10
10
  accessible_tables: Array<TableAccessEntry>;
11
+ agent_names: Array<string>;
11
12
  created_at: any;
12
13
  description: (string | null);
14
+ function_names: Array<string>;
13
15
  icon_url: (string | null);
14
16
  id: string;
15
17
  instruction: string;
@@ -8,7 +8,9 @@ export type CreateAssistantRequest = {
8
8
  accessible_applications?: Array<ApplicationAccessConfig>;
9
9
  accessible_folders?: Array<string>;
10
10
  accessible_tables?: Array<TableAccessEntry>;
11
+ agent_names?: Array<string>;
11
12
  description?: (string | null);
13
+ function_names?: Array<string>;
12
14
  icon_url?: (string | null);
13
15
  instruction: string;
14
16
  name: string;
@@ -10,5 +10,6 @@ export type DeskResponse = {
10
10
  source_archive_path?: (string | null);
11
11
  status: DeskStatus;
12
12
  updated_at: any;
13
+ readonly url: string;
13
14
  user_id: string;
14
15
  };
@@ -8,7 +8,9 @@ export type UpdateAssistantRequest = {
8
8
  accessible_applications?: (Array<ApplicationAccessConfig> | null);
9
9
  accessible_folders?: (Array<string> | null);
10
10
  accessible_tables?: (Array<TableAccessEntry> | null);
11
+ agent_names?: (Array<string> | null);
11
12
  description?: (string | null);
13
+ function_names?: (Array<string> | null);
12
14
  icon_url?: (string | null);
13
15
  instruction?: (string | null);
14
16
  tool_sets?: (Array<ToolSet> | null);
@@ -45,13 +45,12 @@ export declare class ConversationsService {
45
45
  * List Messages
46
46
  * List messages in a conversation with token pagination. Use `page_token` to fetch older messages.
47
47
  * @param conversationId
48
- * @param podId
49
48
  * @param pageToken
50
49
  * @param limit
51
50
  * @returns ConversationMessageListResponse Successful Response
52
51
  * @throws ApiError
53
52
  */
54
- static conversationMessageList(conversationId: string, podId?: (string | null), pageToken?: (string | null), limit?: number): CancelablePromise<ConversationMessageListResponse>;
53
+ static conversationMessageList(conversationId: string, pageToken?: (string | null), limit?: number): CancelablePromise<ConversationMessageListResponse>;
55
54
  /**
56
55
  * Send Message (Stream)
57
56
  * @param conversationId
@@ -95,13 +95,12 @@ export class ConversationsService {
95
95
  * List Messages
96
96
  * List messages in a conversation with token pagination. Use `page_token` to fetch older messages.
97
97
  * @param conversationId
98
- * @param podId
99
98
  * @param pageToken
100
99
  * @param limit
101
100
  * @returns ConversationMessageListResponse Successful Response
102
101
  * @throws ApiError
103
102
  */
104
- static conversationMessageList(conversationId, podId, pageToken, limit = 20) {
103
+ static conversationMessageList(conversationId, pageToken, limit = 20) {
105
104
  return __request(OpenAPI, {
106
105
  method: 'GET',
107
106
  url: '/conversations/{conversation_id}/messages',
@@ -109,7 +108,6 @@ export class ConversationsService {
109
108
  'conversation_id': conversationId,
110
109
  },
111
110
  query: {
112
- 'pod_id': podId,
113
111
  'page_token': pageToken,
114
112
  'limit': limit,
115
113
  },
@@ -1,14 +1,18 @@
1
1
  import { type ComponentPropsWithoutRef, type ReactNode } from "react";
2
2
  import type { AssistantConversationListItem, AssistantConversationRenderArgs } from "./assistant-types.js";
3
+ export type AssistantSurfaceTone = "default" | "subtle" | "flat";
4
+ export type AssistantThemeMode = "auto" | "light" | "dark";
3
5
  export interface AssistantThemeScopeProps extends ComponentPropsWithoutRef<"div"> {
4
6
  children: ReactNode;
7
+ theme?: AssistantThemeMode;
5
8
  }
6
- export declare function AssistantThemeScope({ className, children, ...props }: AssistantThemeScopeProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function AssistantThemeScope({ className, children, theme, ...props }: AssistantThemeScopeProps): import("react/jsx-runtime").JSX.Element;
7
10
  export interface AssistantHeaderProps {
8
11
  title: ReactNode;
9
12
  subtitle?: ReactNode;
10
13
  badge?: ReactNode;
11
14
  controls?: ReactNode;
15
+ tone?: AssistantSurfaceTone;
12
16
  className?: string;
13
17
  }
14
18
  export interface AssistantMessageViewportProps extends ComponentPropsWithoutRef<"div"> {
@@ -23,7 +27,7 @@ export interface AssistantShellLayoutProps {
23
27
  className?: string;
24
28
  }
25
29
  export declare function AssistantShellLayout({ sidebar, sidebarVisible, main, className, }: AssistantShellLayoutProps): import("react/jsx-runtime").JSX.Element;
26
- export declare function AssistantHeader({ title, subtitle, badge, controls, className, }: AssistantHeaderProps): import("react/jsx-runtime").JSX.Element;
30
+ export declare function AssistantHeader({ title, subtitle, badge, controls, tone, className, }: AssistantHeaderProps): import("react/jsx-runtime").JSX.Element;
27
31
  export interface AssistantConversationListProps {
28
32
  conversations: AssistantConversationListItem[];
29
33
  activeConversationId: string | null;
@@ -69,9 +73,10 @@ export interface AssistantComposerProps {
69
73
  status?: ReactNode;
70
74
  pendingFiles?: ReactNode;
71
75
  children: ReactNode;
76
+ tone?: AssistantSurfaceTone;
72
77
  className?: string;
73
78
  }
74
- export declare function AssistantComposer({ floating, status, pendingFiles, children, className, }: AssistantComposerProps): import("react/jsx-runtime").JSX.Element;
79
+ export declare function AssistantComposer({ floating, status, pendingFiles, children, tone, className, }: AssistantComposerProps): import("react/jsx-runtime").JSX.Element;
75
80
  export declare function AssistantPendingFileChip({ label, onRemove, className, }: AssistantPendingFileChipProps): import("react/jsx-runtime").JSX.Element;
76
81
  export interface AssistantStatusPillProps {
77
82
  label: ReactNode;
@@ -3,8 +3,8 @@ import { forwardRef } from "react";
3
3
  function cx(...values) {
4
4
  return values.filter(Boolean).join(" ");
5
5
  }
6
- export function AssistantThemeScope({ className, children, ...props }) {
7
- return (_jsx("div", { className: cx("lemma-assistant-theme", className), ...props, children: children }));
6
+ export function AssistantThemeScope({ className, children, theme = "auto", ...props }) {
7
+ return (_jsx("div", { "data-lemma-theme": theme, className: cx("lemma-assistant-theme", className), ...props, children: children }));
8
8
  }
9
9
  export const AssistantMessageViewport = forwardRef(function AssistantMessageViewport({ className, innerClassName, children, ...props }, ref) {
10
10
  return (_jsx("div", { ref: ref, className: cx("lemma-assistant-viewport", "min-h-0 flex-1 overflow-y-auto bg-[var(--bg-surface)] px-4 py-4", className), ...props, children: _jsx("div", { className: cx("lemma-assistant-viewport-inner", "mx-auto flex w-full max-w-5xl flex-col gap-3", innerClassName), children: children }) }));
@@ -13,8 +13,8 @@ export function AssistantShellLayout({ sidebar, sidebarVisible = false, main, cl
13
13
  const hasSidebar = !!sidebar;
14
14
  return (_jsxs("div", { className: cx("lemma-assistant-shell", hasSidebar && "lemma-assistant-shell--with-sidebar", hasSidebar && sidebarVisible && "lemma-assistant-shell--sidebar-visible", "mx-auto h-full w-full min-h-0 font-sans antialiased", className), children: [sidebar && sidebarVisible ? (_jsx("div", { className: "lemma-assistant-shell-sidebar", children: sidebar })) : null, main] }));
15
15
  }
16
- export function AssistantHeader({ title, subtitle, badge, controls, className, }) {
17
- return (_jsxs("div", { className: cx("lemma-assistant-header", "flex items-center justify-between border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", className), children: [_jsxs("div", { className: "lemma-assistant-header-copy flex items-center gap-2.5", children: [badge ? (_jsx("div", { className: "lemma-assistant-header-badge flex h-7 w-7 items-center justify-center rounded-full bg-[linear-gradient(135deg,var(--brand-primary),var(--brand-secondary))] shadow-[var(--shadow-xs)]", children: badge })) : null, _jsxs("div", { className: "lemma-assistant-header-titles", children: [_jsx("h3", { className: "lemma-assistant-header-title text-[13px] font-semibold leading-tight text-[var(--text-primary)]", children: title }), subtitle ? (_jsx("p", { className: "lemma-assistant-header-subtitle text-[11px] text-[var(--text-tertiary)]", children: subtitle })) : null] })] }), controls ? (_jsx("div", { className: "lemma-assistant-header-controls flex items-center gap-1", children: controls })) : null] }));
16
+ export function AssistantHeader({ title, subtitle, badge, controls, tone = "subtle", className, }) {
17
+ return (_jsxs("div", { "data-tone": tone, className: cx("lemma-assistant-header", "flex items-center justify-between border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", className), children: [_jsxs("div", { className: "lemma-assistant-header-copy flex items-center gap-2.5", children: [badge ? (_jsx("div", { className: "lemma-assistant-header-badge flex h-7 w-7 items-center justify-center rounded-full bg-[linear-gradient(135deg,var(--brand-primary),var(--brand-secondary))] shadow-[var(--shadow-xs)]", children: badge })) : null, _jsxs("div", { className: "lemma-assistant-header-titles", children: [_jsx("h3", { className: "lemma-assistant-header-title text-[13px] font-semibold leading-tight text-[var(--text-primary)]", children: title }), subtitle ? (_jsx("p", { className: "lemma-assistant-header-subtitle text-[11px] text-[var(--text-tertiary)]", children: subtitle })) : null] })] }), controls ? (_jsx("div", { className: "lemma-assistant-header-controls flex items-center gap-1", children: controls })) : null] }));
18
18
  }
19
19
  export function AssistantConversationList({ conversations, activeConversationId, onSelectConversation, onNewConversation, renderConversationLabel, title = "Conversations", newLabel = "New", className, }) {
20
20
  return (_jsxs("aside", { className: cx("lemma-assistant-conversation-list", "flex h-full min-h-0 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)]", className), children: [_jsx("div", { className: "lemma-assistant-conversation-list-header border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", children: _jsxs("div", { className: "lemma-assistant-conversation-list-header-row flex items-center justify-between gap-3", children: [_jsxs("div", { className: "lemma-assistant-conversation-list-copy", children: [_jsx("div", { className: "lemma-assistant-conversation-list-title text-[13px] font-semibold text-[var(--text-primary)]", children: title }), _jsxs("div", { className: "lemma-assistant-conversation-list-meta mt-1 text-[11px] text-[var(--text-tertiary)]", children: [conversations.length, " total"] })] }), onNewConversation ? (_jsx("button", { type: "button", onClick: onNewConversation, className: "lemma-assistant-conversation-list-new rounded-full border border-[var(--border-default)] bg-[var(--bg-surface)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] hover:text-[var(--text-primary)]", children: newLabel })) : null] }) }), _jsx("div", { className: "lemma-assistant-conversation-list-items min-h-0 flex-1 overflow-y-auto p-3 space-y-2", children: conversations.map((conversation) => {
@@ -45,8 +45,8 @@ export function AssistantAskOverlay({ questionNumber, totalQuestions, question,
45
45
  ? "bg-[var(--brand-primary)] text-[var(--text-on-brand)] hover:bg-[color:color-mix(in_srgb,_var(--brand-primary)_88%,_var(--text-primary))]"
46
46
  : "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), children: continueLabel }) })) : null] }));
47
47
  }
48
- export function AssistantComposer({ floating, status, pendingFiles, children, className, }) {
49
- return (_jsxs("div", { className: cx("lemma-assistant-composer", "relative rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] p-2 shadow-[var(--shadow-md)]", className), children: [floating ? (_jsx("div", { className: "lemma-assistant-composer-floating absolute bottom-[calc(100%+8px)] left-0 right-0 z-20", children: floating })) : null, _jsx("div", { className: "lemma-assistant-composer-status-rail min-h-[34px] px-2 pb-1", children: _jsx("div", { className: "lemma-assistant-composer-status 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: "lemma-assistant-composer-pending flex flex-wrap items-center gap-1.5 px-1 pb-1.5", children: pendingFiles })) : null, _jsx("div", { className: "lemma-assistant-composer-body", children: children })] }));
48
+ export function AssistantComposer({ floating, status, pendingFiles, children, tone = "default", className, }) {
49
+ return (_jsxs("div", { "data-tone": tone, "data-has-status": status ? "true" : "false", "data-has-pending-files": pendingFiles ? "true" : "false", "data-has-floating": floating ? "true" : "false", className: cx("lemma-assistant-composer", "relative rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] p-2 shadow-[var(--shadow-md)]", className), children: [floating ? (_jsx("div", { className: "lemma-assistant-composer-floating absolute bottom-[calc(100%+8px)] left-0 right-0 z-20", children: floating })) : null, status ? (_jsx("div", { className: "lemma-assistant-composer-status-rail min-h-[34px] px-2 pb-1", children: _jsx("div", { className: "lemma-assistant-composer-status flex min-h-[26px] items-center transition-opacity duration-200", children: status }) })) : null, pendingFiles ? (_jsx("div", { className: "lemma-assistant-composer-pending flex flex-wrap items-center gap-1.5 px-1 pb-1.5", children: pendingFiles })) : null, _jsx("div", { className: "lemma-assistant-composer-body", children: children })] }));
50
50
  }
51
51
  export function AssistantPendingFileChip({ label, onRemove, className, }) {
52
52
  return (_jsxs("span", { className: cx("lemma-assistant-pending-file-chip", "inline-flex max-w-full items-center gap-1.5 rounded-full bg-[var(--bg-subtle)] px-2 py-1 text-[11px] text-[var(--text-secondary)]", className), children: [_jsx("span", { className: "lemma-assistant-pending-file-chip-label truncate max-w-[180px]", children: label }), onRemove ? (_jsx("button", { type: "button", onClick: onRemove, className: "lemma-assistant-pending-file-chip-remove inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-[var(--bg-canvas)]", title: "Remove file", children: "\u00D7" })) : null] }));
@@ -1,8 +1,10 @@
1
1
  import type { LemmaClient } from "../../client.js";
2
2
  import { type AssistantConversationScope } from "../useAssistantController.js";
3
+ import { type AssistantThemeMode } from "./AssistantChrome.js";
3
4
  import { type AssistantExperienceViewProps } from "./AssistantExperience.js";
4
5
  export interface AssistantEmbeddedProps extends Omit<AssistantExperienceViewProps, "controller">, AssistantConversationScope {
5
6
  client: LemmaClient;
6
7
  enabled?: boolean;
8
+ theme?: AssistantThemeMode;
7
9
  }
8
- export declare function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled, ...props }: AssistantEmbeddedProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled, theme, ...props }: AssistantEmbeddedProps): import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useAssistantController } from "../useAssistantController.js";
3
3
  import { AssistantThemeScope } from "./AssistantChrome.js";
4
4
  import { AssistantExperienceView } from "./AssistantExperience.js";
5
- export function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled = true, ...props }) {
5
+ export function AssistantEmbedded({ client, podId, assistantId, organizationId, enabled = true, theme = "auto", ...props }) {
6
6
  const controller = useAssistantController({
7
7
  client,
8
8
  podId: podId ?? undefined,
@@ -10,5 +10,5 @@ export function AssistantEmbedded({ client, podId, assistantId, organizationId,
10
10
  organizationId: organizationId ?? undefined,
11
11
  enabled,
12
12
  });
13
- return (_jsx(AssistantThemeScope, { children: _jsx(AssistantExperienceView, { controller: controller, ...props }) }));
13
+ return (_jsx(AssistantThemeScope, { className: "lemma-assistant-embedded", theme: theme, children: _jsx(AssistantExperienceView, { controller: controller, ...props }) }));
14
14
  }
@@ -33,6 +33,8 @@ export interface ActiveToolBanner {
33
33
  summary: string;
34
34
  activeCount: number;
35
35
  }
36
+ export type AssistantChromeStyle = "elevated" | "subtle" | "flat";
37
+ export type AssistantStatusPlacement = "inline" | "composer" | "none";
36
38
  export interface AssistantExperienceViewProps {
37
39
  controller: AssistantControllerView;
38
40
  title?: ReactNode;
@@ -42,6 +44,8 @@ export interface AssistantExperienceViewProps {
42
44
  draft?: string;
43
45
  onDraftChange?: (value: string) => void;
44
46
  showConversationList?: boolean;
47
+ chromeStyle?: AssistantChromeStyle;
48
+ statusPlacement?: AssistantStatusPlacement;
45
49
  onNavigateResource?: (resourceType: string, resourceId: string, meta?: Record<string, unknown>) => void;
46
50
  renderConversationLabel?: (args: AssistantConversationRenderArgs) => ReactNode;
47
51
  renderMessageContent?: (args: AssistantMessageRenderArgs) => ReactNode;
@@ -75,5 +79,5 @@ export declare function MessageGroup({ message, conversationId, onNavigateResour
75
79
  renderPresentedFile?: (args: AssistantPresentedFileRenderArgs) => ReactNode;
76
80
  renderToolInvocation?: (args: AssistantToolRenderArgs) => ReactNode;
77
81
  }): 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;
82
+ export declare function AssistantExperienceView({ controller, title, subtitle, placeholder, emptyState, draft: controlledDraft, onDraftChange, showConversationList, chromeStyle, statusPlacement, onNavigateResource, renderConversationLabel, renderMessageContent, renderPresentedFile, renderPendingFile, renderToolInvocation, }: AssistantExperienceViewProps): import("react/jsx-runtime").JSX.Element;
79
83
  export {};
@@ -1,7 +1,9 @@
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
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
3
5
  import { AvailableModels } from "../../types.js";
4
- import { AssistantAskOverlay, AssistantMessageViewport, } from "./AssistantChrome.js";
6
+ import { AssistantAskOverlay, AssistantComposer, AssistantHeader, AssistantMessageViewport, AssistantModelPicker, AssistantStatusPill, } from "./AssistantChrome.js";
5
7
  function cx(...values) {
6
8
  return values.filter(Boolean).join(" ");
7
9
  }
@@ -500,8 +502,11 @@ function useControllableDraft(controlledValue, onChange) {
500
502
  function defaultConversationLabel({ conversation }) {
501
503
  return conversation.title || "Untitled conversation";
502
504
  }
505
+ const markdownComponents = {
506
+ a: ({ node: _node, ...props }) => (_jsx("a", { ...props, target: props.target || "_blank", rel: props.rel || "noreferrer noopener" })),
507
+ };
503
508
  function defaultMessageContent({ message }) {
504
- return _jsx("div", { className: "whitespace-pre-wrap", children: message.content });
509
+ return (_jsx("div", { className: "lemma-assistant-markdown", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], skipHtml: true, components: markdownComponents, children: message.content }) }));
505
510
  }
506
511
  function defaultPresentedFile({ filepath }) {
507
512
  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 })] }));
@@ -513,7 +518,7 @@ export function PlanSummaryStrip({ plan, onHide }) {
513
518
  const [showAll, setShowAll] = useState(false);
514
519
  const visibleSteps = showAll ? plan.steps : plan.steps.slice(0, 5);
515
520
  const hiddenCount = Math.max(0, plan.steps.length - visibleSteps.length);
516
- return (_jsxs("div", { className: "lemma-assistant-plan-strip rounded-xl border border-[color:color-mix(in_srgb,_var(--brand-primary)_24%,_var(--border-subtle))] bg-[color:color-mix(in_srgb,_var(--brand-glow)_18%,_var(--bg-surface))] px-3 py-2.5 shadow-[var(--shadow-sm)]", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-header flex items-center justify-between gap-2", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-summary inline-flex items-center gap-2", children: [_jsx("span", { className: "lemma-assistant-plan-strip-title text-[12px] font-semibold text-[var(--text-primary)]", children: "Task plan" }), _jsxs("span", { className: "lemma-assistant-plan-strip-count text-[11px] text-[var(--text-tertiary)]", children: [plan.completedCount, "/", plan.steps.length, " complete"] }), plan.inProgressCount > 0 ? (_jsxs("span", { className: "lemma-assistant-plan-strip-active rounded-full bg-[color:color-mix(in_srgb,_var(--brand-primary)_16%,_transparent)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--brand-primary)]", children: [plan.inProgressCount, " active"] })) : null] }), _jsx("button", { type: "button", onClick: onHide, className: "text-[11px] font-medium text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors", children: "Hide" })] }), plan.activeStep ? (_jsxs("div", { className: "mt-1.5 truncate text-[11px] text-[var(--text-secondary)]", title: plan.activeStep, children: [plan.running ? "Running:" : "Current:", " ", plan.activeStep] })) : null, _jsxs("div", { className: "lemma-assistant-plan-strip-steps mt-2 space-y-1", children: [visibleSteps.map((step, index) => (_jsxs("div", { className: "lemma-assistant-plan-strip-step flex items-start gap-2 text-[11px]", children: [_jsx("span", { className: cx("mt-1 inline-block h-2 w-2 shrink-0 rounded-full", step.status === "completed" && "bg-[var(--state-success)]", step.status === "in_progress" && "bg-[var(--brand-primary)]", step.status === "pending" && "bg-[var(--border-default)]") }), _jsx("span", { className: cx("leading-5", step.status === "completed" && "text-[var(--text-tertiary)] line-through", step.status === "in_progress" && "text-[var(--brand-primary)] font-medium", step.status === "pending" && "text-[var(--text-secondary)]"), children: step.step })] }, `${step.step}-${index}`))), plan.steps.length > 5 ? (_jsxs("div", { className: "flex items-center gap-2 pt-0.5", children: [_jsx("button", { type: "button", onClick: () => setShowAll((prev) => !prev), className: "text-[10px] font-medium text-[var(--brand-primary)] hover:text-[var(--text-primary)] transition-colors", children: showAll ? "Show less" : `See all ${plan.steps.length} steps` }), !showAll && hiddenCount > 0 ? (_jsxs("span", { className: "text-[10px] text-[var(--text-tertiary)]", children: ["+", hiddenCount, " more"] })) : null] })) : null] })] }));
521
+ return (_jsxs("div", { className: "lemma-assistant-plan-strip rounded-xl border border-[color:color-mix(in_srgb,_var(--border-default)_88%,_transparent)] bg-[var(--bg-surface)] px-3 py-2.5 shadow-[var(--shadow-sm)]", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-header flex items-center justify-between gap-2", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-summary inline-flex items-center gap-2", children: [_jsx("span", { className: "lemma-assistant-plan-strip-title text-[12px] font-semibold text-[var(--text-primary)]", children: "Task plan" }), _jsxs("span", { className: "lemma-assistant-plan-strip-count text-[11px] text-[var(--text-tertiary)]", children: [plan.completedCount, "/", plan.steps.length, " complete"] }), plan.inProgressCount > 0 ? (_jsxs("span", { className: "lemma-assistant-plan-strip-active rounded-full bg-[color:color-mix(in_srgb,_var(--brand-primary)_16%,_transparent)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--brand-primary)]", children: [plan.inProgressCount, " active"] })) : null] }), _jsx("button", { type: "button", onClick: onHide, className: "text-[11px] font-medium text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors", children: "Hide" })] }), plan.activeStep ? (_jsxs("div", { className: "mt-1.5 truncate text-[11px] text-[var(--text-secondary)]", title: plan.activeStep, children: [plan.running ? "Running:" : "Current:", " ", plan.activeStep] })) : null, _jsxs("div", { className: "lemma-assistant-plan-strip-steps mt-2 space-y-1", children: [visibleSteps.map((step, index) => (_jsxs("div", { className: "lemma-assistant-plan-strip-step flex items-start gap-2 text-[11px]", children: [_jsx("span", { className: cx("mt-1 inline-block h-2 w-2 shrink-0 rounded-full", step.status === "completed" && "bg-[var(--state-success)]", step.status === "in_progress" && "bg-[var(--brand-primary)]", step.status === "pending" && "bg-[var(--border-default)]") }), _jsx("span", { className: cx("leading-5", step.status === "completed" && "text-[var(--text-tertiary)] line-through", step.status === "in_progress" && "text-[var(--brand-primary)] font-medium", step.status === "pending" && "text-[var(--text-secondary)]"), children: step.step })] }, `${step.step}-${index}`))), plan.steps.length > 5 ? (_jsxs("div", { className: "flex items-center gap-2 pt-0.5", children: [_jsx("button", { type: "button", onClick: () => setShowAll((prev) => !prev), className: "text-[10px] font-medium text-[var(--brand-primary)] hover:text-[var(--text-primary)] transition-colors", children: showAll ? "Show less" : `See all ${plan.steps.length} steps` }), !showAll && hiddenCount > 0 ? (_jsxs("span", { className: "text-[10px] text-[var(--text-tertiary)]", children: ["+", hiddenCount, " more"] })) : null] })) : null] })] }));
517
522
  }
518
523
  export function ThinkingIndicator() {
519
524
  const [show, setShow] = useState(false);
@@ -575,7 +580,7 @@ function ToolDetailsPanel({ toolName, args, state, result, onNavigateResource, r
575
580
  activeConversationId,
576
581
  }) }));
577
582
  }
578
- return (_jsxs("div", { className: "pl-4 border-l border-[var(--border-default)] space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: formatToolDisplayName(toolName) }), canNavigate && onNavigateResource ? (_jsx("button", { type: "button", onClick: () => onNavigateResource(resultData.resourceType, resultData.resourceId, resultData), className: "inline-flex items-center gap-1 text-[10px] font-medium text-[var(--state-success)] hover:text-[var(--state-success)] transition-colors", children: "Open \u203A" })) : null] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.1em] text-[var(--text-tertiary)] mb-1", children: "Input" }), _jsx("div", { className: "p-2 rounded bg-[color:color-mix(in_srgb,_var(--bg-canvas)_70%,_transparent)] font-mono text-[11px] max-h-24 overflow-auto", children: _jsx("pre", { className: "text-[var(--text-secondary)] whitespace-pre-wrap", children: JSON.stringify(args, null, 2) }) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.1em] text-[var(--text-tertiary)] mb-1", children: "Output" }), _jsx("div", { className: "p-2 rounded bg-[color:color-mix(in_srgb,_var(--bg-canvas)_70%,_transparent)] font-mono text-[11px] max-h-24 overflow-auto", children: _jsx("pre", { className: "text-[var(--text-secondary)] whitespace-pre-wrap", children: Object.keys(resultData).length > 0 ? JSON.stringify(resultData, null, 2) : "No output yet" }) })] })] })] }));
583
+ return (_jsxs("div", { className: "pl-4 border-l border-[var(--border-default)] space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.04em] text-[var(--text-tertiary)]", children: formatToolDisplayName(toolName) }), canNavigate && onNavigateResource ? (_jsx("button", { type: "button", onClick: () => onNavigateResource(resultData.resourceType, resultData.resourceId, resultData), className: "inline-flex items-center gap-1 text-[10px] font-medium text-[var(--state-success)] hover:text-[var(--state-success)] transition-colors", children: "Open \u203A" })) : null] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.1em] text-[var(--text-tertiary)] mb-1", children: "Input" }), _jsx("div", { className: "p-2 rounded bg-[color:color-mix(in_srgb,_var(--bg-canvas)_70%,_transparent)] font-mono text-[11px] max-h-24 overflow-auto", children: _jsx("pre", { className: "text-[var(--text-secondary)] whitespace-pre-wrap", children: JSON.stringify(args, null, 2) }) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-[0.1em] text-[var(--text-tertiary)] mb-1", children: "Output" }), _jsx("div", { className: "p-2 rounded bg-[color:color-mix(in_srgb,_var(--bg-canvas)_70%,_transparent)] font-mono text-[11px] max-h-24 overflow-auto", children: _jsx("pre", { className: "text-[var(--text-secondary)] whitespace-pre-wrap", children: Object.keys(resultData).length > 0 ? JSON.stringify(resultData, null, 2) : "No output yet" }) })] })] })] }));
579
584
  }
580
585
  function InlineToolCall({ invocation, isSelected, onClick, }) {
581
586
  const resultData = (invocation.result || {});
@@ -587,7 +592,7 @@ function InlineToolCall({ invocation, isSelected, onClick, }) {
587
592
  : isFailed
588
593
  ? (typeof resultData.error === "string" ? resultData.error : "Tool failed")
589
594
  : (formatToolResultSummary(invocation.toolName, invocation.args, resultData) || "Completed");
590
- return (_jsxs("button", { type: "button", onClick: onClick, className: cx("w-full text-left inline-flex items-center gap-1.5 text-[12px] leading-5 transition-colors hover:text-[var(--text-primary)]", isExecuting && "text-[var(--state-info)]", isComplete && "text-[var(--state-success)]", isFailed && "text-[var(--state-error)]", !isExecuting && !isComplete && !isFailed && "text-[var(--text-secondary)]"), children: [_jsx("span", { className: "font-medium whitespace-nowrap", children: formatToolDisplayName(invocation.toolName) }), _jsx("span", { className: "text-current/80 truncate", children: summary }), _jsx("span", { className: "ml-auto transition-transform", children: isSelected ? "⌄" : "›" })] }));
595
+ return (_jsxs("button", { type: "button", onClick: onClick, className: cx("w-full text-left inline-flex items-center gap-1.5 text-[11px] leading-5 transition-colors hover:text-[var(--text-primary)]", isExecuting && "text-[var(--state-info)]", isComplete && "text-[var(--state-success)]", isFailed && "text-[var(--state-error)]", !isExecuting && !isComplete && !isFailed && "text-[var(--text-secondary)]"), children: [_jsx("span", { className: "font-medium whitespace-nowrap", children: formatToolDisplayName(invocation.toolName) }), _jsx("span", { className: "text-current/80 truncate", children: summary }), _jsx("span", { className: "ml-auto transition-transform", children: isSelected ? "⌄" : "›" })] }));
591
596
  }
592
597
  function ToolActivityRollup({ detailParts, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
593
598
  const [activeToolCallId, setActiveToolCallId] = useState(null);
@@ -603,7 +608,7 @@ function ToolActivityRollup({ detailParts, onNavigateResource, renderToolInvocat
603
608
  const summary = activeInvocation
604
609
  ? formatActiveToolSummary(activeInvocation.toolName, activeInvocation.args)
605
610
  : `Worked across ${toolParts.length} tool${toolParts.length === 1 ? "" : "s"}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
606
- return (_jsxs("div", { className: "lemma-assistant-tool-rollup space-y-1", children: [_jsxs("button", { type: "button", onClick: () => setIsExpanded((prev) => !prev), className: "lemma-assistant-tool-rollup-toggle inline-flex items-center gap-1.5 text-[12px] leading-5 text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors", children: [_jsx("span", { className: cx("transition-transform", isExpanded && "rotate-90"), children: "\u203A" }), isWorking ? _jsx("span", { className: "inline-flex h-2 w-2 rounded-full bg-[var(--brand-accent)]" }) : null, _jsx("span", { className: cx("text-[var(--text-secondary)]", isWorking && "font-medium"), children: summary })] }), isExpanded ? (_jsx("div", { className: "lemma-assistant-tool-rollup-details pl-4 border-l border-[var(--border-default)] space-y-1.5", children: detailParts.map((part) => {
611
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup space-y-1", children: [_jsxs("button", { type: "button", onClick: () => setIsExpanded((prev) => !prev), className: "lemma-assistant-tool-rollup-toggle inline-flex items-center gap-1.5 text-[11px] leading-5 text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors", children: [_jsx("span", { className: cx("transition-transform", isExpanded && "rotate-90"), children: "\u203A" }), isWorking ? _jsx("span", { className: "inline-flex h-2 w-2 rounded-full bg-[var(--brand-accent)]" }) : null, _jsx("span", { className: cx("text-[var(--text-secondary)]", isWorking && "font-medium"), children: summary })] }), isExpanded ? (_jsx("div", { className: "lemma-assistant-tool-rollup-details pl-4 border-l border-[var(--border-default)] space-y-1.5", children: detailParts.map((part) => {
607
612
  if (part.type === "reasoning") {
608
613
  return (_jsxs("div", { className: "lemma-assistant-tool-rollup-thinking rounded-md bg-[var(--bg-canvas)] px-2.5 py-2", children: [_jsx("div", { className: "mb-1 text-[10px] font-medium uppercase tracking-[0.1em] text-[var(--text-tertiary)]", children: part.state === "streaming" ? "Thinking" : "Thought" }), _jsx("pre", { className: "text-[11px] leading-5 text-[var(--text-secondary)] whitespace-pre-wrap font-mono max-h-40 overflow-auto", children: part.text })] }, `thinking-${part.id}`));
609
614
  }
@@ -755,7 +760,7 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
755
760
  return null;
756
761
  }), presentableFilepaths.length > 0 ? (_jsx(PresentFilesCard, { filepaths: presentableFilepaths, conversationId: conversationId, renderPresentedFile: renderPresentedFile })) : null] })] }));
757
762
  }
758
- 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, }) {
763
+ export function AssistantExperienceView({ controller, title = "Lemma Assistant", subtitle = "Ask across your workspace and organization.", placeholder = "Message Lemma Assistant", emptyState, draft: controlledDraft, onDraftChange, showConversationList = false, chromeStyle = "subtle", statusPlacement = "inline", onNavigateResource, renderConversationLabel = defaultConversationLabel, renderMessageContent = defaultMessageContent, renderPresentedFile, renderPendingFile = defaultPendingFile, renderToolInvocation, }) {
759
764
  const [draft, setDraft] = useControllableDraft(controlledDraft, onDraftChange);
760
765
  const [isPlanHidden, setIsPlanHidden] = useState(false);
761
766
  const [dismissedAskToolCallIds, setDismissedAskToolCallIds] = useState([]);
@@ -764,6 +769,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
764
769
  const messagesContainerRef = useRef(null);
765
770
  const inputRef = useRef(null);
766
771
  const fileInputRef = useRef(null);
772
+ const bottomAnchorRef = useRef(null);
767
773
  const isPinnedToBottomRef = useRef(true);
768
774
  const loadingOlderFromScrollRef = useRef(false);
769
775
  const isConversationBusy = controller.isLoading || controller.isActiveConversationRunning;
@@ -780,6 +786,15 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
780
786
  textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
781
787
  }, []);
782
788
  const scrollToLatest = useCallback((behavior = "auto") => {
789
+ const anchor = bottomAnchorRef.current;
790
+ if (anchor) {
791
+ anchor.scrollIntoView({
792
+ block: "end",
793
+ behavior,
794
+ });
795
+ isPinnedToBottomRef.current = true;
796
+ return;
797
+ }
783
798
  const el = messagesContainerRef.current;
784
799
  if (!el)
785
800
  return;
@@ -825,10 +840,18 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
825
840
  return;
826
841
  if (isPinnedToBottomRef.current) {
827
842
  requestAnimationFrame(() => {
828
- scrollToLatest(isConversationBusy ? "auto" : "smooth");
843
+ requestAnimationFrame(() => {
844
+ scrollToLatest(isConversationBusy ? "auto" : "smooth");
845
+ });
829
846
  });
830
847
  }
831
848
  }, [controller.messages, isConversationBusy, scrollToLatest]);
849
+ useEffect(() => {
850
+ isPinnedToBottomRef.current = true;
851
+ requestAnimationFrame(() => {
852
+ scrollToLatest("auto");
853
+ });
854
+ }, [controller.activeConversationId, scrollToLatest]);
832
855
  useEffect(() => {
833
856
  resizeComposer();
834
857
  }, [draft, resizeComposer]);
@@ -959,7 +982,10 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
959
982
  if (selectedFiles.length === 0)
960
983
  return;
961
984
  try {
962
- await controller.uploadFiles(selectedFiles);
985
+ await controller.uploadFiles(selectedFiles, { deferUntilSend: true });
986
+ requestAnimationFrame(() => {
987
+ inputRef.current?.focus();
988
+ });
963
989
  }
964
990
  finally {
965
991
  if (fileInputRef.current) {
@@ -993,29 +1019,34 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
993
1019
  ? effectiveAskOverlayState.answers[effectiveAskOverlayState.currentQuestionIndex] || []
994
1020
  : [];
995
1021
  const canContinueAsk = activeAskAnswers.length > 0;
996
- return (_jsxs("div", { className: cx("lemma-assistant-experience", "flex h-full min-h-0 flex-col gap-3 font-sans antialiased", showConversationList && "lg:grid lg:grid-cols-[280px_minmax(0,1fr)] lg:gap-3"), children: [showConversationList ? (_jsxs("aside", { className: "lemma-assistant-experience-sidebar hidden min-h-0 overflow-hidden rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] shadow-[var(--shadow-lg)] lg:flex lg:flex-col", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-header border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", children: _jsxs("div", { className: "lemma-assistant-experience-sidebar-header-row flex items-center justify-between gap-3", children: [_jsxs("div", { className: "lemma-assistant-experience-sidebar-copy", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-title text-[13px] font-semibold text-[var(--text-primary)]", children: "Conversations" }), _jsxs("div", { className: "lemma-assistant-experience-sidebar-meta mt-1 text-[11px] text-[var(--text-tertiary)]", children: [controller.conversations.length, " total"] })] }), _jsx("button", { type: "button", onClick: controller.clearMessages, className: "lemma-assistant-experience-sidebar-new rounded-full border border-[var(--border-default)] bg-[var(--bg-surface)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] hover:text-[var(--text-primary)]", children: "New" })] }) }), _jsx("div", { className: "lemma-assistant-experience-sidebar-items min-h-0 flex-1 overflow-y-auto p-3 space-y-2", children: controller.conversations.map((conversation) => {
1022
+ const liveStatusLabel = activeToolBanner?.summary || "Thinking through this...";
1023
+ const headerTone = chromeStyle === "elevated" ? "default" : chromeStyle === "flat" ? "flat" : "subtle";
1024
+ const composerTone = chromeStyle === "flat" ? "flat" : chromeStyle === "subtle" ? "subtle" : "default";
1025
+ const showInlineStatus = statusPlacement === "inline" && isConversationBusy;
1026
+ const showComposerStatus = statusPlacement === "composer" && isConversationBusy;
1027
+ return (_jsxs("div", { className: cx("lemma-assistant-experience", "flex h-full min-h-0 flex-col gap-3 font-sans antialiased", showConversationList && "lg:grid lg:grid-cols-[280px_minmax(0,1fr)] lg:gap-3"), "data-chrome-style": chromeStyle, "data-status-placement": statusPlacement, "data-busy": isConversationBusy ? "true" : "false", "data-has-plan": planSummary ? "true" : "false", "data-has-pending-files": controller.pendingFiles.length > 0 ? "true" : "false", "data-show-conversation-list": showConversationList ? "true" : "false", children: [showConversationList ? (_jsxs("aside", { className: "lemma-assistant-experience-sidebar hidden min-h-0 overflow-hidden rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] shadow-[var(--shadow-lg)] lg:flex lg:flex-col", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-header border-b border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] px-4 py-3", children: _jsxs("div", { className: "lemma-assistant-experience-sidebar-header-row flex items-center justify-between gap-3", children: [_jsxs("div", { className: "lemma-assistant-experience-sidebar-copy", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-title text-[13px] font-semibold text-[var(--text-primary)]", children: "Conversations" }), _jsxs("div", { className: "lemma-assistant-experience-sidebar-meta mt-1 text-[11px] text-[var(--text-tertiary)]", children: [controller.conversations.length, " total"] })] }), _jsx("button", { type: "button", onClick: controller.clearMessages, className: "lemma-assistant-experience-sidebar-new rounded-full border border-[var(--border-default)] bg-[var(--bg-surface)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] hover:text-[var(--text-primary)]", children: "New" })] }) }), _jsx("div", { className: "lemma-assistant-experience-sidebar-items min-h-0 flex-1 overflow-y-auto p-3 space-y-2", children: controller.conversations.map((conversation) => {
997
1028
  const isActive = conversation.id === controller.activeConversationId;
998
1029
  return (_jsxs("button", { type: "button", onClick: () => controller.selectConversation(conversation.id), className: cx("lemma-assistant-experience-sidebar-item", "w-full rounded-xl border px-3 py-2.5 text-left transition-colors", isActive
999
1030
  ? "lemma-assistant-experience-sidebar-item-active 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))]"
1000
1031
  : "border-[var(--border-default)] bg-[var(--bg-surface)] hover:bg-[var(--bg-subtle)]"), children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-item-title text-[12px] font-medium text-[var(--text-primary)]", children: renderConversationLabel({ conversation, isActive }) }), _jsx("div", { className: "lemma-assistant-experience-sidebar-item-status mt-1 text-[10px] uppercase tracking-[0.08em] text-[var(--text-tertiary)]", children: (conversation.status || "waiting").toLowerCase() })] }, conversation.id));
1001
- }) })] })) : null, _jsxs("div", { className: "lemma-assistant-experience-main flex h-full min-h-0 flex-col gap-3", children: [_jsxs("div", { className: "lemma-assistant-experience-card 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: "lemma-assistant-experience-header 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: "lemma-assistant-experience-header-copy flex items-center gap-2.5", children: [_jsx("div", { className: "lemma-assistant-experience-header-badge 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", { className: "lemma-assistant-experience-header-titles", children: [_jsx("h3", { className: "lemma-assistant-experience-header-title font-semibold text-[var(--text-primary)] text-[13px] leading-tight", children: title }), _jsx("p", { className: "lemma-assistant-experience-header-subtitle text-[11px] text-[var(--text-tertiary)]", children: subtitle })] })] }), _jsxs("div", { className: "lemma-assistant-experience-header-controls flex items-center gap-1", children: [_jsxs("select", { value: controller.conversationModel || "", onChange: (event) => { void handleModelChange(event.target.value || null); }, disabled: isConversationBusy || isUpdatingModel, className: "lemma-assistant-experience-model-picker 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: "lemma-assistant-experience-new 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(AssistantMessageViewport, { className: "lemma-assistant-experience-viewport min-h-[180px]", 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: "lemma-assistant-experience-loading flex justify-center py-6", children: _jsx("span", { className: "lemma-assistant-experience-loading-text text-[var(--text-tertiary)] text-sm", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading-older flex justify-center py-1", children: _jsx("span", { className: "lemma-assistant-experience-loading-older-text text-[var(--text-tertiary)] text-xs", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
1032
+ }) })] })) : null, _jsxs("div", { className: "lemma-assistant-experience-main flex h-full min-h-0 flex-col gap-3", children: [_jsxs("div", { className: "lemma-assistant-experience-card 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: [_jsx(AssistantHeader, { className: "lemma-assistant-experience-header", tone: headerTone, title: title, subtitle: subtitle, badge: _jsx("span", { className: "text-[var(--text-on-brand)] text-xs", children: "\u2728" }), controls: (_jsxs(_Fragment, { children: [_jsx(AssistantModelPicker, { value: controller.conversationModel, options: availableModels, onChange: (nextModel) => { void handleModelChange(nextModel); }, disabled: isConversationBusy || isUpdatingModel, autoLabel: "Auto", className: "lemma-assistant-experience-model-picker" }), _jsx("button", { type: "button", onClick: controller.clearMessages, title: "New conversation", className: "lemma-assistant-experience-new 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(AssistantMessageViewport, { className: "lemma-assistant-experience-viewport min-h-[180px]", 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: "lemma-assistant-experience-loading flex justify-center py-6", children: _jsx("span", { className: "lemma-assistant-experience-loading-text text-[var(--text-tertiary)] text-sm", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading-older flex justify-center py-1", children: _jsx("span", { className: "lemma-assistant-experience-loading-older-text text-[var(--text-tertiary)] text-xs", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
1002
1033
  const previousRow = index > 0 ? displayMessageRows[index - 1] : null;
1003
1034
  const showAssistantHeader = row.message.role !== "assistant"
1004
1035
  ? false
1005
1036
  : previousRow?.message.role !== "assistant";
1006
1037
  const includesLastRawMessage = row.sourceIndexes.includes(controller.messages.length - 1);
1007
1038
  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));
1008
- }), isConversationBusy && controller.messages.length > 0 && !activeToolBanner && !lastMessageHasContent ? (_jsx(ThinkingIndicator, {})) : null, controller.error ? (_jsx("div", { className: "lemma-assistant-experience-error 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] })] }), _jsxs("div", { className: "lemma-assistant-experience-composer relative rounded-2xl border border-[color:color-mix(in_srgb,_var(--border-default)_80%,_transparent)] bg-[var(--bg-surface)] p-2 shadow-[var(--shadow-md)]", children: [planSummary ? (_jsx("div", { className: "lemma-assistant-experience-plan absolute bottom-[calc(100%+8px)] left-0 right-0 z-20", children: 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)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-subtle)] transition-colors", children: ["Show plan (", planSummary.completedCount, "/", planSummary.steps.length, ")"] })) : (_jsx(PlanSummaryStrip, { plan: planSummary, onHide: () => setIsPlanHidden(true) })) })) : null, 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: "lemma-assistant-experience-composer-body space-y-1.5", children: [controller.pendingFiles.length > 0 ? (_jsx("div", { className: "lemma-assistant-experience-pending flex flex-wrap items-center gap-1.5 px-1", children: controller.pendingFiles.map((file) => {
1009
- const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
1010
- return (_jsx("div", { children: renderPendingFile({
1011
- file,
1012
- remove: () => controller.removePendingFile(fileKey),
1013
- }) }, fileKey));
1014
- }) })) : null, _jsxs("div", { className: "lemma-assistant-experience-input-row relative flex items-end gap-2", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "lemma-assistant-experience-file-input hidden", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: cx("lemma-assistant-experience-upload", "mb-1.5 ml-1 h-9 w-9 rounded-full flex items-center justify-center transition-colors", isConversationBusy || controller.isUploadingFiles
1015
- ? "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"
1016
- : "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: "lemma-assistant-experience-textarea 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: "lemma-assistant-experience-send-wrap pb-1.5 pr-1.5", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: cx("lemma-assistant-experience-send", "h-9 w-9 rounded-full flex items-center justify-center transition-all duration-200", isConversationBusy
1017
- ? "bg-[var(--text-primary)] text-[var(--text-inverse)] hover:bg-[color:color-mix(in_srgb,_var(--text-primary)_80%,_transparent)] hover:scale-105"
1018
- : draft.trim()
1019
- ? "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))]"
1020
- : "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "■" : "→" }) })] })] }))] })] })] }));
1039
+ }), showInlineStatus ? (_jsx("div", { className: "lemma-assistant-experience-inline-status flex min-h-[38px] items-center px-1", children: _jsx("div", { className: cx("transition-all duration-200", lastMessageHasContent ? "opacity-80" : "opacity-100"), children: _jsx(AssistantStatusPill, { label: liveStatusLabel, subtle: lastMessageHasContent }) }) })) : null, controller.error ? (_jsx("div", { className: "lemma-assistant-experience-error 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("div", { ref: bottomAnchorRef, "aria-hidden": "true", className: "lemma-assistant-experience-bottom-anchor h-px" })] })] }), _jsx(AssistantComposer, { className: "lemma-assistant-experience-composer", tone: composerTone, floating: planSummary ? (isPlanHidden ? (_jsxs("button", { type: "button", onClick: () => setIsPlanHidden(false), className: "inline-flex items-center gap-2 rounded-full border border-[color:color-mix(in_srgb,_var(--border-default)_88%,_transparent)] bg-[var(--bg-surface)] px-3 py-1.5 text-[11px] font-medium text-[var(--text-secondary)] shadow-[var(--shadow-xs)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-subtle)] transition-colors", children: ["Show plan (", planSummary.completedCount, "/", planSummary.steps.length, ")"] })) : (_jsx(PlanSummaryStrip, { plan: planSummary, onHide: () => setIsPlanHidden(true) }))) : undefined, status: showComposerStatus ? (_jsx(AssistantStatusPill, { label: liveStatusLabel, subtle: true })) : undefined, pendingFiles: controller.pendingFiles.length > 0 ? (_jsx(_Fragment, { children: controller.pendingFiles.map((file) => {
1040
+ const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
1041
+ return (_jsx("div", { children: renderPendingFile({
1042
+ file,
1043
+ remove: () => controller.removePendingFile(fileKey),
1044
+ }) }, fileKey));
1045
+ }) })) : 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 })) : (_jsx("div", { className: "lemma-assistant-experience-composer-body space-y-1.5", children: _jsxs("div", { className: "lemma-assistant-experience-input-row relative flex items-end gap-2", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "lemma-assistant-experience-file-input hidden", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: cx("lemma-assistant-experience-upload", "mb-1.5 ml-1 h-9 w-9 rounded-full flex items-center justify-center transition-colors", isConversationBusy || controller.isUploadingFiles
1046
+ ? "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"
1047
+ : "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: "lemma-assistant-experience-textarea 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: "lemma-assistant-experience-send-wrap pb-1.5 pr-1.5", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: cx("lemma-assistant-experience-send", "h-9 w-9 rounded-full flex items-center justify-center transition-all duration-200", isConversationBusy
1048
+ ? "bg-[var(--text-primary)] text-[var(--text-inverse)] hover:bg-[color:color-mix(in_srgb,_var(--text-primary)_80%,_transparent)] hover:scale-105"
1049
+ : draft.trim()
1050
+ ? "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))]"
1051
+ : "bg-[var(--bg-subtle)] text-[var(--text-tertiary)]"), title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "■" : "→" }) })] }) })) })] })] }));
1021
1052
  }
@@ -14,9 +14,9 @@ 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
16
  export { AssistantAskOverlay, AssistantComposer, AssistantConversationList, AssistantHeader, AssistantMessageViewport, AssistantModelPicker, AssistantPendingFileChip, AssistantShellLayout, AssistantStatusPill, AssistantThemeScope, } from "./components/AssistantChrome.js";
17
- export type { AssistantAskOverlayProps, AssistantComposerProps, AssistantConversationListProps, AssistantHeaderProps, AssistantMessageViewportProps, AssistantModelPickerProps, AssistantPendingFileChipProps, AssistantShellLayoutProps, AssistantStatusPillProps, AssistantThemeScopeProps, } from "./components/AssistantChrome.js";
17
+ export type { AssistantAskOverlayProps, AssistantComposerProps, AssistantConversationListProps, AssistantHeaderProps, AssistantMessageViewportProps, AssistantModelPickerProps, AssistantPendingFileChipProps, AssistantShellLayoutProps, AssistantStatusPillProps, AssistantSurfaceTone, AssistantThemeMode, AssistantThemeScopeProps, } from "./components/AssistantChrome.js";
18
18
  export { AssistantExperienceView } from "./components/AssistantExperience.js";
19
- export type { ActiveToolBanner, AskUserInputQuestion, AssistantExperienceViewProps, DisplayMessageRow, PendingAskUserInput, PlanStepState, PlanSummaryState, } from "./components/AssistantExperience.js";
19
+ export type { ActiveToolBanner, AskUserInputQuestion, AssistantChromeStyle, AssistantExperienceViewProps, AssistantStatusPlacement, DisplayMessageRow, PendingAskUserInput, PlanStepState, PlanSummaryState, } from "./components/AssistantExperience.js";
20
20
  export { AssistantEmbedded } from "./components/AssistantEmbedded.js";
21
21
  export type { AssistantEmbeddedProps } from "./components/AssistantEmbedded.js";
22
22
  export { buildDisplayMessageRows, dedupToolInvocations, EmptyState, findPendingAskUserInput, formatAskUserInputAnswers, getActiveToolBanner, extractPresentFilePathsFromInvocation, latestPlanSummary, MessageGroup, PlanSummaryStrip, ThinkingIndicator, } from "./components/AssistantExperience.js";
@@ -1,4 +1,5 @@
1
1
  .lemma-assistant-theme {
2
+ color-scheme: light;
2
3
  --bg-canvas: #f6f2ea;
3
4
  --bg-surface: #fffdf9;
4
5
  --bg-subtle: #f1ebde;
@@ -53,6 +54,272 @@
53
54
  color: var(--text-tertiary);
54
55
  }
55
56
 
57
+ @media (prefers-color-scheme: dark) {
58
+ .lemma-assistant-theme:not([data-lemma-theme="light"]):not([data-lemma-theme="dark"]),
59
+ .lemma-assistant-theme[data-lemma-theme="auto"] {
60
+ color-scheme: dark;
61
+ --bg-canvas: #12100d;
62
+ --bg-surface: #1b1814;
63
+ --bg-subtle: #272118;
64
+ --border-default: #3d3428;
65
+ --border-subtle: #2d261d;
66
+ --text-primary: #f5ede0;
67
+ --text-secondary: #d0c3b1;
68
+ --text-tertiary: #988d7d;
69
+ --text-inverse: #15120f;
70
+ --text-on-brand: #15120f;
71
+ --brand-primary: #ead9b3;
72
+ --brand-secondary: #8daa73;
73
+ --brand-accent: #d9a44b;
74
+ --brand-glow: #2d2418;
75
+ --state-success: #7cbc72;
76
+ --state-error: #ec8d74;
77
+ --state-info: #7eb8f5;
78
+ --state-warning: #e0b45d;
79
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.32);
80
+ --shadow-sm: 0 10px 26px rgba(0, 0, 0, 0.32);
81
+ --shadow-md: 0 20px 40px rgba(0, 0, 0, 0.4);
82
+ --shadow-lg: 0 28px 56px rgba(0, 0, 0, 0.5);
83
+ }
84
+ }
85
+
86
+ :where(.light, [data-theme="light"], [data-color-scheme="light"], [data-mode="light"]) .lemma-assistant-theme:not([data-lemma-theme="dark"]),
87
+ .lemma-assistant-theme[data-lemma-theme="light"] {
88
+ color-scheme: light;
89
+ }
90
+
91
+ :where(.dark, [data-theme="dark"], [data-color-scheme="dark"], [data-mode="dark"]) .lemma-assistant-theme:not([data-lemma-theme="light"]),
92
+ .lemma-assistant-theme[data-lemma-theme="dark"] {
93
+ color-scheme: dark;
94
+ --bg-canvas: #12100d;
95
+ --bg-surface: #1b1814;
96
+ --bg-subtle: #272118;
97
+ --border-default: #3d3428;
98
+ --border-subtle: #2d261d;
99
+ --text-primary: #f5ede0;
100
+ --text-secondary: #d0c3b1;
101
+ --text-tertiary: #988d7d;
102
+ --text-inverse: #15120f;
103
+ --text-on-brand: #15120f;
104
+ --brand-primary: #ead9b3;
105
+ --brand-secondary: #8daa73;
106
+ --brand-accent: #d9a44b;
107
+ --brand-glow: #2d2418;
108
+ --state-success: #7cbc72;
109
+ --state-error: #ec8d74;
110
+ --state-info: #7eb8f5;
111
+ --state-warning: #e0b45d;
112
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.32);
113
+ --shadow-sm: 0 10px 26px rgba(0, 0, 0, 0.32);
114
+ --shadow-md: 0 20px 40px rgba(0, 0, 0, 0.4);
115
+ --shadow-lg: 0 28px 56px rgba(0, 0, 0, 0.5);
116
+ }
117
+
118
+ .lemma-assistant-embedded {
119
+ display: flex;
120
+ flex-direction: column;
121
+ min-height: 0;
122
+ height: 100%;
123
+ }
124
+
125
+ .lemma-assistant-markdown {
126
+ color: inherit;
127
+ line-height: 1.7;
128
+ word-break: break-word;
129
+ }
130
+
131
+ .lemma-assistant-markdown > :first-child {
132
+ margin-top: 0;
133
+ }
134
+
135
+ .lemma-assistant-markdown > :last-child {
136
+ margin-bottom: 0;
137
+ }
138
+
139
+ .lemma-assistant-markdown p,
140
+ .lemma-assistant-markdown ul,
141
+ .lemma-assistant-markdown ol,
142
+ .lemma-assistant-markdown pre,
143
+ .lemma-assistant-markdown blockquote,
144
+ .lemma-assistant-markdown table,
145
+ .lemma-assistant-markdown hr {
146
+ margin: 0 0 0.95em;
147
+ }
148
+
149
+ .lemma-assistant-markdown h1,
150
+ .lemma-assistant-markdown h2,
151
+ .lemma-assistant-markdown h3,
152
+ .lemma-assistant-markdown h4 {
153
+ margin: 1.15em 0 0.55em;
154
+ line-height: 1.25;
155
+ color: var(--text-primary);
156
+ }
157
+
158
+ .lemma-assistant-markdown h1 {
159
+ font-size: 1.4rem;
160
+ }
161
+
162
+ .lemma-assistant-markdown h2 {
163
+ font-size: 1.2rem;
164
+ }
165
+
166
+ .lemma-assistant-markdown h3 {
167
+ font-size: 1.05rem;
168
+ }
169
+
170
+ .lemma-assistant-markdown h4 {
171
+ font-size: 0.95rem;
172
+ }
173
+
174
+ .lemma-assistant-markdown ul,
175
+ .lemma-assistant-markdown ol {
176
+ padding-left: 1.35rem;
177
+ }
178
+
179
+ .lemma-assistant-markdown ul {
180
+ list-style: disc;
181
+ }
182
+
183
+ .lemma-assistant-markdown ol {
184
+ list-style: decimal;
185
+ }
186
+
187
+ .lemma-assistant-markdown li + li {
188
+ margin-top: 0.3rem;
189
+ }
190
+
191
+ .lemma-assistant-markdown li > ul,
192
+ .lemma-assistant-markdown li > ol {
193
+ margin-top: 0.35rem;
194
+ margin-bottom: 0;
195
+ }
196
+
197
+ .lemma-assistant-markdown a {
198
+ color: var(--state-info);
199
+ text-decoration: underline;
200
+ text-decoration-color: color-mix(in srgb, var(--state-info) 56%, transparent);
201
+ text-underline-offset: 0.16em;
202
+ }
203
+
204
+ .lemma-assistant-markdown a:hover {
205
+ color: color-mix(in srgb, var(--state-info) 82%, var(--text-primary));
206
+ }
207
+
208
+ .lemma-assistant-markdown strong {
209
+ color: var(--text-primary);
210
+ font-weight: 650;
211
+ }
212
+
213
+ .lemma-assistant-markdown em {
214
+ color: var(--text-secondary);
215
+ }
216
+
217
+ .lemma-assistant-markdown hr {
218
+ border: 0;
219
+ border-top: 1px solid color-mix(in srgb, var(--border-default) 88%, transparent);
220
+ }
221
+
222
+ .lemma-assistant-markdown blockquote {
223
+ padding: 0.15rem 0 0.15rem 0.9rem;
224
+ border-left: 3px solid color-mix(in srgb, var(--brand-accent) 60%, var(--border-default));
225
+ color: var(--text-secondary);
226
+ }
227
+
228
+ .lemma-assistant-markdown :not(pre) > code {
229
+ display: inline-block;
230
+ margin: 0 0.08rem;
231
+ padding: 0.1rem 0.42rem;
232
+ border: 1px solid color-mix(in srgb, var(--border-default) 82%, transparent);
233
+ border-radius: 999px;
234
+ background: color-mix(in srgb, var(--bg-subtle) 88%, transparent);
235
+ color: var(--text-primary);
236
+ font-size: 0.92em;
237
+ line-height: 1.45;
238
+ }
239
+
240
+ .lemma-assistant-markdown pre {
241
+ overflow-x: auto;
242
+ padding: 0.9rem 1rem;
243
+ border: 1px solid color-mix(in srgb, var(--border-default) 82%, transparent);
244
+ border-radius: 16px;
245
+ background: color-mix(in srgb, var(--bg-canvas) 74%, var(--bg-surface));
246
+ box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bg-surface) 60%, transparent);
247
+ }
248
+
249
+ .lemma-assistant-markdown pre code {
250
+ padding: 0;
251
+ border: 0;
252
+ background: transparent;
253
+ color: inherit;
254
+ font-size: 0.92em;
255
+ line-height: 1.65;
256
+ }
257
+
258
+ .lemma-assistant-markdown table {
259
+ display: block;
260
+ width: 100%;
261
+ overflow-x: auto;
262
+ border-collapse: separate;
263
+ border-spacing: 0;
264
+ border: 1px solid color-mix(in srgb, var(--border-default) 82%, transparent);
265
+ border-radius: 16px;
266
+ background: color-mix(in srgb, var(--bg-surface) 96%, transparent);
267
+ }
268
+
269
+ .lemma-assistant-markdown thead {
270
+ background: color-mix(in srgb, var(--bg-subtle) 76%, transparent);
271
+ }
272
+
273
+ .lemma-assistant-markdown th,
274
+ .lemma-assistant-markdown td {
275
+ padding: 0.7rem 0.85rem;
276
+ border-bottom: 1px solid color-mix(in srgb, var(--border-default) 72%, transparent);
277
+ text-align: left;
278
+ vertical-align: top;
279
+ }
280
+
281
+ .lemma-assistant-markdown tr:last-child td {
282
+ border-bottom: 0;
283
+ }
284
+
285
+ .lemma-assistant-markdown img {
286
+ max-width: 100%;
287
+ border-radius: 14px;
288
+ }
289
+
290
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown,
291
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown h1,
292
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown h2,
293
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown h3,
294
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown h4,
295
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown strong,
296
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown em,
297
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown a,
298
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown code,
299
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown pre code {
300
+ color: inherit;
301
+ }
302
+
303
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown a {
304
+ text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
305
+ }
306
+
307
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown blockquote {
308
+ border-left-color: color-mix(in srgb, currentColor 34%, transparent);
309
+ color: inherit;
310
+ }
311
+
312
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown :not(pre) > code {
313
+ border-color: color-mix(in srgb, currentColor 18%, transparent);
314
+ background: color-mix(in srgb, currentColor 10%, transparent);
315
+ color: inherit;
316
+ }
317
+
318
+ .lemma-assistant-message-user-bubble .lemma-assistant-markdown pre {
319
+ border-color: color-mix(in srgb, currentColor 18%, transparent);
320
+ background: color-mix(in srgb, currentColor 8%, transparent);
321
+ }
322
+
56
323
  .lemma-assistant-shell,
57
324
  .lemma-assistant-experience,
58
325
  .lemma-assistant-shell-sidebar,
@@ -95,6 +362,24 @@
95
362
  align-items: center;
96
363
  justify-content: space-between;
97
364
  gap: 12px;
365
+ background: transparent;
366
+ border-bottom: 0;
367
+ box-shadow: none;
368
+ }
369
+
370
+
371
+ .lemma-assistant-header[data-tone="default"],
372
+ .lemma-assistant-experience-header[data-tone="default"] {
373
+ background: var(--bg-surface);
374
+ border-bottom: 1px solid var(--border-default);
375
+ }
376
+
377
+ .lemma-assistant-header[data-tone="flat"],
378
+ .lemma-assistant-experience-header[data-tone="flat"] {
379
+ background: transparent;
380
+ border-bottom: 0;
381
+ box-shadow: none;
382
+ padding-bottom: 0;
98
383
  }
99
384
 
100
385
  .lemma-assistant-header-copy,
@@ -177,6 +462,8 @@
177
462
  .lemma-assistant-experience-model-picker {
178
463
  appearance: none;
179
464
  min-width: 132px;
465
+ border-color: transparent;
466
+ background: color-mix(in srgb, var(--bg-surface) 88%, transparent);
180
467
  }
181
468
 
182
469
  .lemma-assistant-viewport,
@@ -198,6 +485,22 @@
198
485
  box-shadow: var(--shadow-md);
199
486
  }
200
487
 
488
+
489
+ .lemma-assistant-composer[data-tone="subtle"],
490
+ .lemma-assistant-experience-composer[data-tone="subtle"] {
491
+ background: color-mix(in srgb, var(--bg-surface) 96%, transparent);
492
+ border-color: color-mix(in srgb, var(--border-default) 88%, transparent);
493
+ box-shadow: var(--shadow-sm);
494
+ }
495
+
496
+ .lemma-assistant-composer[data-tone="flat"],
497
+ .lemma-assistant-experience-composer[data-tone="flat"] {
498
+ background: transparent;
499
+ border-color: transparent;
500
+ box-shadow: none;
501
+ padding: 0;
502
+ }
503
+
201
504
  .lemma-assistant-composer-floating,
202
505
  .lemma-assistant-experience-plan {
203
506
  position: absolute;
@@ -205,6 +508,7 @@
205
508
  right: 0;
206
509
  bottom: calc(100% + 8px);
207
510
  z-index: 20;
511
+ pointer-events: auto;
208
512
  }
209
513
 
210
514
  .lemma-assistant-composer-status-rail {
@@ -223,6 +527,41 @@
223
527
  gap: 6px;
224
528
  }
225
529
 
530
+ .lemma-assistant-conversation-list-new,
531
+ .lemma-assistant-experience-sidebar-new {
532
+ border-color: color-mix(in srgb, var(--border-default) 88%, transparent);
533
+ }
534
+
535
+
536
+ .lemma-assistant-experience-new,
537
+ .lemma-assistant-conversation-list-new,
538
+ .lemma-assistant-experience-sidebar-new {
539
+ background: color-mix(in srgb, var(--bg-surface) 90%, transparent);
540
+ backdrop-filter: blur(12px);
541
+ }
542
+
543
+ .lemma-assistant-experience-new {
544
+ border: 0;
545
+ }
546
+
547
+ .lemma-assistant-experience[data-chrome-style="subtle"] .lemma-assistant-experience-card,
548
+ .lemma-assistant-experience[data-chrome-style="subtle"] .lemma-assistant-experience-sidebar {
549
+ box-shadow: var(--shadow-md);
550
+ }
551
+
552
+ .lemma-assistant-experience[data-chrome-style="flat"] .lemma-assistant-experience-card,
553
+ .lemma-assistant-experience[data-chrome-style="flat"] .lemma-assistant-experience-sidebar {
554
+ background: transparent;
555
+ border-color: transparent;
556
+ box-shadow: none;
557
+ }
558
+
559
+ .lemma-assistant-plan-strip {
560
+ background: var(--bg-surface);
561
+ border-color: color-mix(in srgb, var(--border-default) 88%, transparent);
562
+ box-shadow: var(--shadow-sm);
563
+ }
564
+
226
565
  .lemma-assistant-pending-file-chip {
227
566
  display: inline-flex;
228
567
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemma-sdk",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Official TypeScript SDK for Lemma pod-scoped APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,6 +53,8 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
+ "react-markdown": "^10.1.0",
57
+ "remark-gfm": "^4.0.1",
56
58
  "supertokens-web-js": "^0.16.0"
57
59
  },
58
60
  "devDependencies": {