lemma-sdk 0.2.25 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +25 -1
  2. package/dist/browser/lemma-client.js +176 -2
  3. package/dist/client.d.ts +2 -0
  4. package/dist/client.js +3 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/namespaces/pod-join-requests.d.ts +16 -0
  7. package/dist/namespaces/pod-join-requests.js +24 -0
  8. package/dist/namespaces/pod-members.d.ts +1 -0
  9. package/dist/namespaces/pod-members.js +3 -0
  10. package/dist/openapi_client/index.d.ts +9 -1
  11. package/dist/openapi_client/index.js +4 -0
  12. package/dist/openapi_client/models/DataStoreFlowStart.d.ts +3 -6
  13. package/dist/openapi_client/models/DatastoreOperation.d.ts +5 -0
  14. package/dist/openapi_client/models/DatastoreOperation.js +10 -0
  15. package/dist/openapi_client/models/FlowInstallEntity.d.ts +2 -2
  16. package/dist/openapi_client/models/FlowResponse.d.ts +4 -3
  17. package/dist/openapi_client/models/{FlowStart.d.ts → FlowStart_Input.d.ts} +1 -1
  18. package/dist/openapi_client/models/FlowStart_Output.d.ts +14 -0
  19. package/dist/openapi_client/models/FlowStart_Output.js +1 -0
  20. package/dist/openapi_client/models/PodJoinRequestApproveRequest.d.ts +6 -0
  21. package/dist/openapi_client/models/PodJoinRequestApproveRequest.js +1 -0
  22. package/dist/openapi_client/models/PodJoinRequestCreateResponse.d.ts +17 -0
  23. package/dist/openapi_client/models/PodJoinRequestCreateResponse.js +1 -0
  24. package/dist/openapi_client/models/PodJoinRequestListResponse.d.ts +7 -0
  25. package/dist/openapi_client/models/PodJoinRequestListResponse.js +1 -0
  26. package/dist/openapi_client/models/PodJoinRequestStatus.d.ts +5 -0
  27. package/dist/openapi_client/models/PodJoinRequestStatus.js +10 -0
  28. package/dist/openapi_client/models/WorkflowCreateRequest.d.ts +7 -6
  29. package/dist/openapi_client/models/WorkflowGraphUpdateRequest.d.ts +2 -2
  30. package/dist/openapi_client/models/WorkflowInstallMode.d.ts +7 -0
  31. package/dist/openapi_client/models/WorkflowInstallMode.js +12 -0
  32. package/dist/openapi_client/models/WorkflowUpdateRequest.d.ts +5 -4
  33. package/dist/openapi_client/services/FilesService.d.ts +1 -1
  34. package/dist/openapi_client/services/FilesService.js +1 -1
  35. package/dist/openapi_client/services/PodJoinRequestsService.d.ts +44 -0
  36. package/dist/openapi_client/services/PodJoinRequestsService.js +93 -0
  37. package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
  38. package/dist/openapi_client/services/WorkflowsService.js +1 -1
  39. package/dist/react/AuthGuard.d.ts +5 -2
  40. package/dist/react/AuthGuard.js +126 -3
  41. package/dist/react/components/AssistantChrome.js +1 -1
  42. package/dist/react/components/AssistantExperience.d.ts +7 -2
  43. package/dist/react/components/AssistantExperience.js +276 -34
  44. package/dist/react/components/assistant-types.d.ts +1 -0
  45. package/dist/react/styles.css +600 -224
  46. package/dist/react/useAssistantController.js +2 -1
  47. package/dist/react/useAssistantRuntime.d.ts +2 -1
  48. package/dist/react/useAssistantRuntime.js +7 -3
  49. package/dist/types.d.ts +2 -1
  50. package/package.json +1 -1
  51. /package/dist/openapi_client/models/{FlowStart.js → FlowStart_Input.js} +0 -0
@@ -29,6 +29,35 @@ function fileNameFromPath(path) {
29
29
  const parts = normalized.split("/").filter(Boolean);
30
30
  return parts[parts.length - 1] || normalized;
31
31
  }
32
+ function formatMessageTimestamp(createdAt) {
33
+ if (!(createdAt instanceof Date) || Number.isNaN(createdAt.getTime()))
34
+ return null;
35
+ return {
36
+ text: new Intl.DateTimeFormat(undefined, {
37
+ month: "short",
38
+ day: "numeric",
39
+ hour: "numeric",
40
+ minute: "2-digit",
41
+ }).format(createdAt),
42
+ dateTime: createdAt.toISOString(),
43
+ };
44
+ }
45
+ function thinkingLabelsFromSummary(summary) {
46
+ const normalized = summary?.toLowerCase() || "";
47
+ if (normalized.includes("search") || normalized.includes("find") || normalized.includes("query")) {
48
+ return ["Searching…", "Working on it…", "Checking results…"];
49
+ }
50
+ if (normalized.includes("plan") || normalized.includes("step")) {
51
+ return ["Planning next steps…", "Working on it…", "Organizing tasks…"];
52
+ }
53
+ if (normalized.includes("run") || normalized.includes("command") || normalized.includes("exec")) {
54
+ return ["Running checks…", "Working on it…", "Inspecting output…"];
55
+ }
56
+ if (normalized.includes("file") || normalized.includes("present")) {
57
+ return ["Preparing files…", "Working on it…", "Finalizing output…"];
58
+ }
59
+ return ["Working on it…", "Thinking…", "Preparing response…"];
60
+ }
32
61
  function toolInvocationKey(tool) {
33
62
  return `${tool.toolCallId}:${tool.state}`;
34
63
  }
@@ -222,6 +251,16 @@ function formatCommandPreview(cmd) {
222
251
  const compact = cmd.replace(/\s+/g, " ").trim();
223
252
  return truncateLabel(compact, 64);
224
253
  }
254
+ function formatDurationCompact(durationMs) {
255
+ const totalSeconds = Math.max(1, Math.round(durationMs / 1000));
256
+ const minutes = Math.floor(totalSeconds / 60);
257
+ const seconds = totalSeconds % 60;
258
+ if (minutes <= 0)
259
+ return `${totalSeconds}s`;
260
+ if (seconds <= 0)
261
+ return `${minutes}m`;
262
+ return `${minutes}m ${seconds}s`;
263
+ }
225
264
  function primaryToolArgs(args) {
226
265
  const request = asRecord(args.request);
227
266
  if (Object.keys(request).length > 0)
@@ -246,10 +285,13 @@ function commentLabelFromArgs(args) {
246
285
  const comment = asString(toolArg(args, "comment"));
247
286
  return comment ? truncateLabel(comment, 72) : null;
248
287
  }
288
+ function toolCallPrimaryLabel(toolName, args) {
289
+ return commentLabelFromArgs(args) || formatToolDisplayName(toolName);
290
+ }
249
291
  function formatActiveToolSummary(toolName, args) {
250
292
  const lowerName = toolName.toLowerCase();
293
+ const comment = commentLabelFromArgs(args);
251
294
  if (lowerName === "exec_command") {
252
- const comment = commentLabelFromArgs(args);
253
295
  if (comment)
254
296
  return `Running ${comment}`;
255
297
  const cmd = asString(toolArg(args, "cmd"));
@@ -276,6 +318,8 @@ function formatActiveToolSummary(toolName, args) {
276
318
  const plan = asArray(toolArg(args, "plan"));
277
319
  return `Updating plan (${plan.length} step${plan.length === 1 ? "" : "s"})`;
278
320
  }
321
+ if (comment)
322
+ return `Running ${comment}`;
279
323
  return `Running ${formatToolDisplayName(toolName)}`;
280
324
  }
281
325
  function formatToolResultSummary(toolName, args, result) {
@@ -520,15 +564,33 @@ export function PlanSummaryStrip({ plan, onHide }) {
520
564
  const hiddenCount = Math.max(0, plan.steps.length - visibleSteps.length);
521
565
  return (_jsxs("div", { className: "lemma-assistant-plan-strip", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-header", children: [_jsxs("div", { className: "lemma-assistant-plan-strip-summary", children: [_jsx("span", { className: "lemma-assistant-plan-strip-title", children: "Task plan" }), _jsxs("span", { className: "lemma-assistant-plan-strip-count", children: [plan.completedCount, "/", plan.steps.length, " complete"] }), plan.inProgressCount > 0 ? (_jsxs("span", { className: "lemma-assistant-plan-strip-active", children: [plan.inProgressCount, " active"] })) : null] }), _jsx("button", { type: "button", onClick: onHide, className: "lemma-assistant-plan-strip-hide", children: "Hide" })] }), plan.activeStep ? (_jsxs("div", { className: "lemma-assistant-plan-strip-current", title: plan.activeStep, children: [plan.running ? "Running:" : "Current:", " ", plan.activeStep] })) : null, _jsxs("div", { className: "lemma-assistant-plan-strip-steps", children: [visibleSteps.map((step, index) => (_jsxs("div", { className: "lemma-assistant-plan-strip-step", "data-status": step.status, children: [_jsx("span", { className: cx("lemma-assistant-plan-strip-step-dot", step.status === "completed" && "lemma-assistant-plan-strip-step-dot-completed", step.status === "in_progress" && "lemma-assistant-plan-strip-step-dot-in-progress", step.status === "pending" && "lemma-assistant-plan-strip-step-dot-pending") }), _jsx("span", { className: cx("lemma-assistant-plan-strip-step-label", step.status === "completed" && "lemma-assistant-plan-strip-step-label-completed", step.status === "in_progress" && "lemma-assistant-plan-strip-step-label-in-progress", step.status === "pending" && "lemma-assistant-plan-strip-step-label-pending"), children: step.step })] }, `${step.step}-${index}`))), plan.steps.length > 5 ? (_jsxs("div", { className: "lemma-assistant-plan-strip-footer", children: [_jsx("button", { type: "button", onClick: () => setShowAll((prev) => !prev), className: "lemma-assistant-plan-strip-toggle", children: showAll ? "Show less" : `See all ${plan.steps.length} steps` }), !showAll && hiddenCount > 0 ? (_jsxs("span", { className: "lemma-assistant-plan-strip-hidden-count", children: ["+", hiddenCount, " more"] })) : null] })) : null] })] }));
522
566
  }
523
- export function ThinkingIndicator() {
567
+ function resolvedThinkingLabels(labels, activeToolSummary) {
568
+ if (labels && labels.length > 0)
569
+ return labels;
570
+ return thinkingLabelsFromSummary(activeToolSummary);
571
+ }
572
+ export function ThinkingIndicator({ activeToolSummary, labels, } = {}) {
524
573
  const [show, setShow] = useState(false);
574
+ const labelOptions = useMemo(() => resolvedThinkingLabels(labels, activeToolSummary), [labels, activeToolSummary]);
575
+ const [labelIndex, setLabelIndex] = useState(0);
525
576
  useEffect(() => {
526
577
  const timer = setTimeout(() => setShow(true), 350);
527
578
  return () => clearTimeout(timer);
528
579
  }, []);
580
+ useEffect(() => {
581
+ setLabelIndex(0);
582
+ }, [labelOptions]);
583
+ useEffect(() => {
584
+ if (!show || labelOptions.length < 2)
585
+ return;
586
+ const interval = window.setInterval(() => {
587
+ setLabelIndex((prev) => (prev + 1) % labelOptions.length);
588
+ }, 1600);
589
+ return () => clearInterval(interval);
590
+ }, [show, labelOptions]);
529
591
  if (!show)
530
592
  return null;
531
- return (_jsx("div", { className: "lemma-assistant-thinking", children: _jsxs("div", { className: "lemma-assistant-thinking-label", children: [_jsx("span", { className: "lemma-assistant-thinking-dot" }), _jsx("span", { className: "lemma-assistant-thinking-text", children: "Thinking..." })] }) }));
593
+ return (_jsx("div", { className: "lemma-assistant-thinking", role: "status", "aria-live": "polite", "aria-label": "Generating response", children: _jsxs("div", { className: "lemma-assistant-thinking-label", children: [_jsx("span", { className: "lemma-assistant-thinking-dot" }), _jsx("span", { className: "lemma-assistant-thinking-text", children: labelOptions[labelIndex] || "Working on it…" })] }) }));
532
594
  }
533
595
  export const DEFAULT_EMPTY_STATE_SUGGESTIONS = [
534
596
  { text: "Help me get started", icon: "→" },
@@ -536,11 +598,14 @@ export const DEFAULT_EMPTY_STATE_SUGGESTIONS = [
536
598
  { text: "Help me draft a reply", icon: "✎" },
537
599
  { text: "Brainstorm next steps", icon: "⋯" },
538
600
  ];
601
+ function LemmaMarkIcon({ className }) {
602
+ return (_jsxs("svg", { viewBox: "0 0 20 20", fill: "none", className: className, "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M10 2.5 16.25 5v4.85c0 4.25-2.55 7.05-6.25 8.15-3.7-1.1-6.25-3.9-6.25-8.15V5L10 2.5Z", fill: "currentColor", fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "m7.1 10.1 1.8 1.8 4-4.1", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" })] }));
603
+ }
539
604
  export function EmptyState({ onSendMessage, suggestions = DEFAULT_EMPTY_STATE_SUGGESTIONS, }) {
540
- return (_jsxs("div", { className: "lemma-assistant-empty-state", children: [_jsxs("div", { className: "lemma-assistant-empty-state-hero", children: [_jsx("div", { className: "lemma-assistant-empty-state-badge", children: _jsx("span", { className: "lemma-assistant-empty-state-badge-icon", children: "\u2728" }) }), _jsx("h4", { className: "lemma-assistant-empty-state-title", children: "How can I help?" }), _jsx("p", { className: "lemma-assistant-empty-state-copy", children: "Ask a question, share context, or start with one of these prompts." })] }), _jsx("div", { className: "lemma-assistant-empty-state-suggestions", children: suggestions.map((suggestion, index) => (_jsxs("button", { onClick: () => onSendMessage(suggestion.text), className: "lemma-assistant-empty-state-suggestion", children: [suggestion.icon ? (_jsx("span", { className: "lemma-assistant-empty-state-suggestion-icon", children: suggestion.icon })) : null, _jsx("span", { className: "lemma-assistant-empty-state-suggestion-text", children: suggestion.text }), _jsx("span", { className: "lemma-assistant-empty-state-suggestion-arrow", children: "\u203A" })] }, `${suggestion.text}-${index}`))) })] }));
605
+ return (_jsxs("div", { className: "lemma-assistant-empty-state", children: [_jsxs("div", { className: "lemma-assistant-empty-state-hero", children: [_jsx("div", { className: "lemma-assistant-empty-state-badge", children: _jsx(LemmaMarkIcon, { className: "lemma-assistant-empty-state-badge-icon" }) }), _jsx("h4", { className: "lemma-assistant-empty-state-title", children: "How can I help?" }), _jsx("p", { className: "lemma-assistant-empty-state-copy", children: "Ask a question, share context, or start with one of these prompts." })] }), _jsx("div", { className: "lemma-assistant-empty-state-suggestions", children: suggestions.map((suggestion, index) => (_jsxs("button", { onClick: () => onSendMessage(suggestion.text), className: "lemma-assistant-empty-state-suggestion", children: [suggestion.icon ? (_jsx("span", { className: "lemma-assistant-empty-state-suggestion-icon", children: suggestion.icon })) : null, _jsx("span", { className: "lemma-assistant-empty-state-suggestion-text", children: suggestion.text }), _jsx("span", { className: "lemma-assistant-empty-state-suggestion-arrow", children: "\u203A" })] }, `${suggestion.text}-${index}`))) })] }));
541
606
  }
542
607
  function ReasoningPartCard({ text, isStreaming, durationMs, }) {
543
- return (_jsxs("details", { className: "lemma-assistant-reasoning", open: isStreaming, children: [_jsxs("summary", { className: "lemma-assistant-reasoning-summary", children: [_jsx("span", { className: "lemma-assistant-reasoning-caret", children: "\u203A" }), _jsx("span", { className: cx("lemma-assistant-reasoning-label", isStreaming && "lemma-assistant-reasoning-label-streaming"), children: isStreaming ? "Thinking" : `Thought${durationMs ? ` · ${Math.max(1, Math.round(durationMs / 1000))}s` : ""}` })] }), _jsx("div", { className: "lemma-assistant-reasoning-body", children: _jsx("pre", { className: "lemma-assistant-reasoning-text", children: text }) })] }));
608
+ return (_jsxs("details", { className: "lemma-assistant-reasoning", open: isStreaming, children: [_jsx("summary", { className: "lemma-assistant-reasoning-summary", children: _jsx("span", { className: cx("lemma-assistant-reasoning-label", isStreaming && "lemma-assistant-reasoning-label-streaming"), children: isStreaming ? "Thinking" : `Thought${durationMs ? ` · ${Math.max(1, Math.round(durationMs / 1000))}s` : ""}` }) }), _jsx("div", { className: "lemma-assistant-reasoning-body", children: _jsx("pre", { className: "lemma-assistant-reasoning-text", children: text }) })] }));
544
609
  }
545
610
  function PresentFilesCard({ filepaths, conversationId, renderPresentedFile, }) {
546
611
  const fakeMessage = {
@@ -561,12 +626,137 @@ function PresentFilesCard({ filepaths, conversationId, renderPresentedFile, }) {
561
626
  message: fakeMessage,
562
627
  }) }, `present-file-${filepath}`))) }));
563
628
  }
629
+ function formatToolDetailValue(value) {
630
+ if (value === null)
631
+ return "null";
632
+ if (typeof value === "undefined")
633
+ return "undefined";
634
+ if (typeof value === "string") {
635
+ const trimmed = value.trim();
636
+ return trimmed.length <= 160 ? trimmed : `${trimmed.slice(0, 157)}...`;
637
+ }
638
+ if (typeof value === "number" || typeof value === "boolean") {
639
+ return String(value);
640
+ }
641
+ if (Array.isArray(value)) {
642
+ if (value.length === 0)
643
+ return "[]";
644
+ const primitives = value.filter((entry) => (typeof entry === "string"
645
+ || typeof entry === "number"
646
+ || typeof entry === "boolean"
647
+ || entry === null));
648
+ if (primitives.length === value.length) {
649
+ const preview = primitives
650
+ .slice(0, 4)
651
+ .map((entry) => (typeof entry === "string" ? `"${entry}"` : String(entry)))
652
+ .join(", ");
653
+ return `[${preview}${value.length > 4 ? ", ..." : ""}]`;
654
+ }
655
+ return `${value.length} item${value.length === 1 ? "" : "s"}`;
656
+ }
657
+ const record = asRecord(value);
658
+ const keys = Object.keys(record);
659
+ if (keys.length === 0)
660
+ return "{}";
661
+ const preview = keys.slice(0, 4).join(", ");
662
+ return `{ ${preview}${keys.length > 4 ? ", ..." : ""} }`;
663
+ }
664
+ function humanizeKey(value) {
665
+ return value
666
+ .replace(/[_-]+/g, " ")
667
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
668
+ .replace(/\s+/g, " ")
669
+ .trim()
670
+ .replace(/\b\w/g, (char) => char.toUpperCase());
671
+ }
672
+ function isToolPayloadValueMeaningful(value) {
673
+ if (value === null || typeof value === "undefined")
674
+ return false;
675
+ if (typeof value === "string")
676
+ return value.trim().length > 0;
677
+ if (Array.isArray(value))
678
+ return value.length > 0;
679
+ if (typeof value === "object")
680
+ return Object.keys(asRecord(value)).length > 0;
681
+ return true;
682
+ }
683
+ function summarizeToolPayload(payload, options) {
684
+ const excluded = new Set((options?.excludeKeys || []).map((key) => key.toLowerCase()));
685
+ return Object.entries(payload)
686
+ .filter(([key, value]) => !excluded.has(key.toLowerCase()) && isToolPayloadValueMeaningful(value))
687
+ .slice(0, 8)
688
+ .map(([key, value]) => ({
689
+ key,
690
+ value: formatToolDetailValue(value),
691
+ }));
692
+ }
693
+ function countSummarizablePayloadEntries(payload, options) {
694
+ const excluded = new Set((options?.excludeKeys || []).map((key) => key.toLowerCase()));
695
+ return Object.entries(payload)
696
+ .filter(([key, value]) => !excluded.has(key.toLowerCase()) && isToolPayloadValueMeaningful(value))
697
+ .length;
698
+ }
699
+ function pickPreferredEntries(entries, preferredKeys, max) {
700
+ const preferredSet = new Set(preferredKeys.map((key) => key.toLowerCase()));
701
+ const preferred = entries.filter((entry) => preferredSet.has(entry.key.toLowerCase()));
702
+ const rest = entries.filter((entry) => !preferredSet.has(entry.key.toLowerCase()));
703
+ return [...preferred, ...rest].slice(0, max);
704
+ }
564
705
  function ToolDetailsPanel({ toolName, args, state, result, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
565
706
  const resultData = result || {};
707
+ const primaryLabel = toolCallPrimaryLabel(toolName, args);
708
+ const hasCommentLabel = !!commentLabelFromArgs(args);
709
+ const toolDisplayName = formatToolDisplayName(toolName);
566
710
  const canNavigate = state === "result"
567
711
  && resultData.success !== false
568
712
  && typeof resultData.resourceType === "string"
569
713
  && typeof resultData.resourceId === "string";
714
+ const summaryOptions = {
715
+ input: { excludeKeys: ["comment", "request", "wait_config"] },
716
+ output: { excludeKeys: ["success", "completed"] },
717
+ };
718
+ const inputEntries = summarizeToolPayload(args, summaryOptions.input);
719
+ const outputEntries = summarizeToolPayload(resultData, summaryOptions.output);
720
+ const inputHighlights = pickPreferredEntries(inputEntries, [
721
+ "cmd",
722
+ "query",
723
+ "path",
724
+ "filepath",
725
+ "filepaths",
726
+ "table",
727
+ "resource_type",
728
+ "resourceType",
729
+ "resource_id",
730
+ "resourceId",
731
+ ], 3);
732
+ const outputHighlights = pickPreferredEntries(outputEntries, [
733
+ "message",
734
+ "stdout",
735
+ "stderr",
736
+ "error",
737
+ "resource_type",
738
+ "resourceType",
739
+ "resource_id",
740
+ "resourceId",
741
+ "exit_code",
742
+ "session_id",
743
+ ], 3);
744
+ const detailRows = [
745
+ {
746
+ label: "Tool",
747
+ value: hasCommentLabel ? `${toolDisplayName} (${toolName})` : toolDisplayName,
748
+ },
749
+ ...inputHighlights.map((entry) => ({
750
+ label: humanizeKey(entry.key),
751
+ value: entry.value,
752
+ })),
753
+ ...outputHighlights.map((entry) => ({
754
+ label: humanizeKey(entry.key),
755
+ value: entry.value,
756
+ })),
757
+ ].slice(0, 8);
758
+ const hiddenInputCount = Math.max(0, countSummarizablePayloadEntries(args, summaryOptions.input) - inputEntries.length);
759
+ const hiddenOutputCount = Math.max(0, countSummarizablePayloadEntries(resultData, summaryOptions.output) - outputEntries.length);
570
760
  if (renderToolInvocation) {
571
761
  return (_jsx("div", { className: "lemma-assistant-tool-details-panel lemma-assistant-tool-details-panel-custom", children: renderToolInvocation({
572
762
  invocation: {
@@ -580,41 +770,62 @@ function ToolDetailsPanel({ toolName, args, state, result, onNavigateResource, r
580
770
  activeConversationId,
581
771
  }) }));
582
772
  }
583
- return (_jsxs("div", { className: "lemma-assistant-tool-details-panel", children: [_jsxs("div", { className: "lemma-assistant-tool-details-header", children: [_jsx("div", { className: "lemma-assistant-tool-details-title", children: formatToolDisplayName(toolName) }), canNavigate && onNavigateResource ? (_jsx("button", { type: "button", onClick: () => onNavigateResource(resultData.resourceType, resultData.resourceId, resultData), className: "lemma-assistant-tool-details-link", children: "Open \u203A" })) : null] }), _jsxs("div", { className: "lemma-assistant-tool-details-grid", children: [_jsxs("div", { className: "lemma-assistant-tool-details-section", children: [_jsx("div", { className: "lemma-assistant-tool-details-label", children: "Input" }), _jsx("div", { className: "lemma-assistant-tool-details-code", children: _jsx("pre", { className: "lemma-assistant-tool-details-code-text", children: JSON.stringify(args, null, 2) }) })] }), _jsxs("div", { className: "lemma-assistant-tool-details-section", children: [_jsx("div", { className: "lemma-assistant-tool-details-label", children: "Output" }), _jsx("div", { className: "lemma-assistant-tool-details-code", children: _jsx("pre", { className: "lemma-assistant-tool-details-code-text", children: Object.keys(resultData).length > 0 ? JSON.stringify(resultData, null, 2) : "No output yet" }) })] })] })] }));
773
+ return (_jsxs("div", { className: "lemma-assistant-tool-details-panel", children: [_jsxs("div", { className: "lemma-assistant-tool-details-header", children: [_jsxs("div", { className: "lemma-assistant-tool-details-heading", children: [_jsx("div", { className: "lemma-assistant-tool-details-title", children: primaryLabel }), hasCommentLabel ? (_jsx("div", { className: "lemma-assistant-tool-details-meta", children: toolDisplayName })) : null] }), canNavigate && onNavigateResource ? (_jsx("button", { type: "button", onClick: () => onNavigateResource(resultData.resourceType, resultData.resourceId, resultData), className: "lemma-assistant-tool-details-link", children: "Open" })) : null] }), _jsx("div", { className: "lemma-assistant-tool-details-stack", children: _jsxs("div", { className: "lemma-assistant-tool-details-section", children: [_jsx("dl", { className: "lemma-assistant-tool-details-list", children: detailRows.map((row, index) => (_jsxs("div", { className: "lemma-assistant-tool-details-list-item", children: [_jsx("dt", { className: "lemma-assistant-tool-details-key", children: row.label }), _jsx("dd", { className: "lemma-assistant-tool-details-value", children: row.value })] }, `${row.label}-${index}`))) }), hiddenInputCount > 0 ? (_jsxs("div", { className: "lemma-assistant-tool-details-more", children: ["+", hiddenInputCount, " more input field", hiddenInputCount === 1 ? "" : "s"] })) : null, hiddenOutputCount > 0 ? (_jsxs("div", { className: "lemma-assistant-tool-details-more", children: ["+", hiddenOutputCount, " more output field", hiddenOutputCount === 1 ? "" : "s"] })) : null, _jsxs("div", { className: "lemma-assistant-tool-details-raw-row", children: [_jsxs("details", { className: "lemma-assistant-tool-details-raw", children: [_jsx("summary", { className: "lemma-assistant-tool-details-raw-summary", children: "Raw input JSON" }), _jsx("div", { className: "lemma-assistant-tool-details-code", children: _jsx("pre", { className: "lemma-assistant-tool-details-code-text", children: JSON.stringify(args, null, 2) }) })] }), Object.keys(resultData).length > 0 ? (_jsxs("details", { className: "lemma-assistant-tool-details-raw", children: [_jsx("summary", { className: "lemma-assistant-tool-details-raw-summary", children: "Raw output JSON" }), _jsx("div", { className: "lemma-assistant-tool-details-code", children: _jsx("pre", { className: "lemma-assistant-tool-details-code-text", children: JSON.stringify(resultData, null, 2) }) })] })) : null] })] }) })] }));
584
774
  }
585
- function InlineToolCall({ invocation, isSelected, onClick, }) {
775
+ function InlineToolCall({ invocation, isSelected, onClick, showStem = true, }) {
586
776
  const resultData = (invocation.result || {});
587
777
  const isExecuting = invocation.state !== "result";
588
778
  const isComplete = invocation.state === "result" && resultData.success !== false;
589
779
  const isFailed = invocation.state === "result" && resultData.success === false;
780
+ const primaryLabel = toolCallPrimaryLabel(invocation.toolName, invocation.args);
781
+ const statusLabel = isExecuting ? "Working" : isFailed ? "Failed" : "Done";
782
+ const toolMeta = isExecuting ? `${invocation.toolName} · running` : invocation.toolName;
590
783
  const summary = isExecuting
591
- ? formatActiveToolSummary(invocation.toolName, invocation.args)
784
+ ? "Running"
592
785
  : isFailed
593
786
  ? (typeof resultData.error === "string" ? resultData.error : "Tool failed")
594
787
  : (formatToolResultSummary(invocation.toolName, invocation.args, resultData) || "Completed");
595
- return (_jsxs("button", { type: "button", onClick: onClick, className: "lemma-assistant-inline-tool-call", "data-state": isExecuting ? "executing" : isComplete ? "complete" : isFailed ? "failed" : "idle", "data-selected": isSelected ? "true" : "false", children: [_jsx("span", { className: "lemma-assistant-inline-tool-call-name", children: formatToolDisplayName(invocation.toolName) }), _jsx("span", { className: "lemma-assistant-inline-tool-call-summary", children: summary }), _jsx("span", { className: "lemma-assistant-inline-tool-call-caret", children: isSelected ? "⌄" : "›" })] }));
788
+ const showSummary = summary !== "Completed";
789
+ return (_jsxs("button", { type: "button", onClick: onClick, className: "lemma-assistant-inline-tool-call", "data-state": isExecuting ? "executing" : isComplete ? "complete" : isFailed ? "failed" : "idle", "data-selected": isSelected ? "true" : "false", children: [_jsxs("span", { className: "lemma-assistant-inline-tool-call-rail", "aria-hidden": "true", children: [_jsx("span", { className: "lemma-assistant-inline-tool-call-node" }), showStem ? _jsx("span", { className: "lemma-assistant-inline-tool-call-stem" }) : null] }), _jsxs("span", { className: "lemma-assistant-inline-tool-call-main", children: [_jsxs("span", { className: "lemma-assistant-inline-tool-call-head", children: [_jsx("span", { className: "lemma-assistant-inline-tool-call-name", children: primaryLabel }), _jsx("span", { className: "lemma-assistant-inline-tool-call-status", children: statusLabel }), _jsx("span", { className: "lemma-assistant-inline-tool-call-caret", children: isSelected ? "⌄" : "›" })] }), _jsx("span", { className: "lemma-assistant-inline-tool-call-meta", children: toolMeta }), showSummary ? _jsx("span", { className: "lemma-assistant-inline-tool-call-summary", children: summary }) : null] })] }));
596
790
  }
597
791
  function ToolActivityRollup({ detailParts, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
598
792
  const [activeToolCallId, setActiveToolCallId] = useState(null);
599
793
  const [isExpanded, setIsExpanded] = useState(false);
600
794
  const toolParts = detailParts.filter((part) => part.type === "tool");
601
795
  const reasoningParts = detailParts.filter((part) => part.type === "reasoning");
796
+ const totalThoughtDurationMs = reasoningParts.reduce((total, part) => total + (part.durationMs ?? 0), 0);
797
+ const shouldCollapse = detailParts.length > 1;
602
798
  const activeInvocation = [...toolParts]
603
799
  .reverse()
604
800
  .find((part) => part.toolInvocation.state !== "result")
605
801
  ?.toolInvocation;
606
802
  const failedCount = toolParts.filter((part) => (part.toolInvocation.state === "result" && part.toolInvocation.result?.success === false)).length;
607
803
  const isWorking = !!activeInvocation || reasoningParts.some((part) => part.state === "streaming");
804
+ const isSingleDetail = detailParts.length === 1;
805
+ const completionSummary = toolParts.length > 0
806
+ ? `Completed ${toolParts.length} tool${toolParts.length === 1 ? "" : "s"}`
807
+ : totalThoughtDurationMs > 0
808
+ ? `Thought for ${formatDurationCompact(totalThoughtDurationMs)}`
809
+ : "Completed";
608
810
  const summary = activeInvocation
609
811
  ? formatActiveToolSummary(activeInvocation.toolName, activeInvocation.args)
610
- : `Worked across ${toolParts.length} tool${toolParts.length === 1 ? "" : "s"}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
611
- return (_jsxs("div", { className: "lemma-assistant-tool-rollup", children: [_jsxs("button", { type: "button", onClick: () => setIsExpanded((prev) => !prev), className: "lemma-assistant-tool-rollup-toggle", "data-expanded": isExpanded ? "true" : "false", children: [_jsx("span", { className: "lemma-assistant-tool-rollup-caret", children: "\u203A" }), isWorking ? _jsx("span", { className: "lemma-assistant-tool-rollup-dot" }) : null, _jsx("span", { className: cx("lemma-assistant-tool-rollup-summary", isWorking && "lemma-assistant-tool-rollup-summary-working"), children: summary })] }), isExpanded ? (_jsx("div", { className: "lemma-assistant-tool-rollup-details", children: detailParts.map((part) => {
812
+ : isWorking
813
+ ? "Working on it…"
814
+ : `${completionSummary}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
815
+ const collapsedSummary = isWorking
816
+ ? summary
817
+ : `${totalThoughtDurationMs > 0
818
+ ? `Worked for ${formatDurationCompact(totalThoughtDurationMs)}`
819
+ : `Worked through ${detailParts.length} step${detailParts.length === 1 ? "" : "s"}`}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
820
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup", "data-single": isSingleDetail ? "true" : "false", children: [shouldCollapse ? (_jsxs("button", { type: "button", className: "lemma-assistant-tool-rollup-banner", onClick: () => setIsExpanded((prev) => !prev), "aria-expanded": isExpanded, "aria-label": isExpanded ? "Hide tool activity details" : "Show tool activity details", children: [_jsx("span", { className: "lemma-assistant-tool-rollup-banner-line", "aria-hidden": "true" }), _jsxs("span", { className: "lemma-assistant-tool-rollup-banner-copy", children: [isWorking ? _jsx("span", { className: "lemma-assistant-tool-rollup-dot", "aria-hidden": "true" }) : null, _jsx("span", { className: cx("lemma-assistant-tool-rollup-banner-label", isWorking && "lemma-assistant-tool-rollup-banner-label-working"), children: collapsedSummary }), _jsx("span", { className: "lemma-assistant-tool-rollup-banner-caret", "data-expanded": isExpanded ? "true" : "false", "aria-hidden": "true", children: "\u203A" })] }), _jsx("span", { className: "lemma-assistant-tool-rollup-banner-line", "aria-hidden": "true" })] })) : (!isSingleDetail ? (_jsxs("div", { className: "lemma-assistant-tool-rollup-header", children: [isWorking ? _jsx("span", { className: "lemma-assistant-tool-rollup-dot" }) : null, _jsx("span", { className: cx("lemma-assistant-tool-rollup-summary", isWorking && "lemma-assistant-tool-rollup-summary-working"), children: summary })] })) : null), !shouldCollapse || isExpanded ? (_jsx("div", { className: cx("lemma-assistant-tool-rollup-details", isSingleDetail && "lemma-assistant-tool-rollup-details-single"), children: detailParts.map((part, partIndex) => {
612
821
  if (part.type === "reasoning") {
613
- return (_jsxs("div", { className: "lemma-assistant-tool-rollup-thinking", children: [_jsx("div", { className: "lemma-assistant-tool-rollup-thinking-title", children: part.state === "streaming" ? "Thinking" : "Thought" }), _jsx("pre", { className: "lemma-assistant-tool-rollup-thinking-text", children: part.text })] }, `thinking-${part.id}`));
822
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup-thinking", children: [_jsx("div", { className: "lemma-assistant-tool-rollup-thinking-title", children: part.state === "streaming"
823
+ ? "Internal note"
824
+ : `Internal note${part.durationMs ? ` · ${formatDurationCompact(part.durationMs)}` : ""}` }), _jsx("pre", { className: "lemma-assistant-tool-rollup-thinking-text", children: part.text })] }, `thinking-${part.id}`));
614
825
  }
615
826
  const invocation = part.toolInvocation;
616
827
  const isSelected = activeToolCallId === invocation.toolCallId;
617
- return (_jsxs("div", { className: "lemma-assistant-tool-rollup-item", children: [_jsx(InlineToolCall, { invocation: invocation, isSelected: isSelected, onClick: () => setActiveToolCallId((prev) => (prev === invocation.toolCallId ? null : invocation.toolCallId)) }), isSelected ? (_jsx(ToolDetailsPanel, { toolName: invocation.toolName, args: invocation.args, state: invocation.state, result: invocation.result, onNavigateResource: onNavigateResource, renderToolInvocation: renderToolInvocation, message: message, activeConversationId: activeConversationId })) : null] }, part.id));
828
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup-item", children: [_jsx(InlineToolCall, { invocation: invocation, isSelected: isSelected, showStem: partIndex < detailParts.length - 1, onClick: () => setActiveToolCallId((prev) => (prev === invocation.toolCallId ? null : invocation.toolCallId)) }), isSelected ? (_jsx(ToolDetailsPanel, { toolName: invocation.toolName, args: invocation.args, state: invocation.state, result: invocation.result, onNavigateResource: onNavigateResource, renderToolInvocation: renderToolInvocation, message: message, activeConversationId: activeConversationId })) : null] }, part.id));
618
829
  }) })) : null] }));
619
830
  }
620
831
  function ShowWidgetToolCard({ invocation, onSendPrompt, }) {
@@ -714,15 +925,16 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
714
925
  .reverse()
715
926
  .find((part) => part.type === "text" && part.text.trim().length > 0)
716
927
  ?.id;
928
+ const messageTimestamp = formatMessageTimestamp(message.createdAt);
717
929
  if (message.role === "user") {
718
- return (_jsx("div", { className: "lemma-assistant-message lemma-assistant-message-user", children: _jsx("div", { className: "lemma-assistant-message-user-bubble", children: renderMessageContent({
719
- message: {
720
- ...message,
721
- content: message.content,
722
- parts: undefined,
723
- toolInvocations: undefined,
724
- },
725
- }) }) }));
930
+ return (_jsxs("div", { className: "lemma-assistant-message lemma-assistant-message-user", children: [_jsx("div", { className: "lemma-assistant-message-user-bubble", children: renderMessageContent({
931
+ message: {
932
+ ...message,
933
+ content: message.content,
934
+ parts: undefined,
935
+ toolInvocations: undefined,
936
+ },
937
+ }) }), messageTimestamp ? (_jsx("time", { className: "lemma-assistant-message-timestamp lemma-assistant-message-timestamp-user", dateTime: messageTimestamp.dateTime, children: messageTimestamp.text })) : null] }));
726
938
  }
727
939
  return (_jsxs("div", { className: "lemma-assistant-message lemma-assistant-message-assistant", children: [showAssistantHeader ? (_jsxs("div", { className: "lemma-assistant-message-header", children: [_jsx("span", { className: "lemma-assistant-message-header-dot" }), "Lemma"] })) : null, _jsxs("div", { className: "lemma-assistant-message-body", children: [blocks.map((block) => {
728
940
  if (block.kind === "tools") {
@@ -760,12 +972,14 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
760
972
  return null;
761
973
  }), presentableFilepaths.length > 0 ? (_jsx(PresentFilesCard, { filepaths: presentableFilepaths, conversationId: conversationId, renderPresentedFile: renderPresentedFile })) : null] })] }));
762
974
  }
763
- export function AssistantExperienceView({ controller, title = "Lemma Assistant", subtitle = "Ask across your workspace and organization.", placeholder = "Message Lemma Assistant", emptyState, emptyStateSuggestions, draft: controlledDraft, onDraftChange, showConversationList = false, chromeStyle = "subtle", statusPlacement = "inline", radius = "md", showModelPicker = true, showNewConversationButton = true, onNavigateResource, renderConversationLabel = defaultConversationLabel, renderMessageContent = defaultMessageContent, renderPresentedFile, renderPendingFile = defaultPendingFile, renderToolInvocation, }) {
975
+ export function AssistantExperienceView({ controller, title = "Lemma Assistant", subtitle = "Ask across your workspace and organization.", badge, placeholder = "Message Lemma Assistant", emptyState, emptyStateSuggestions, draft: controlledDraft, onDraftChange, showConversationList = false, chromeStyle = "subtle", statusPlacement = "inline", radius = "sm", showModelPicker = false, showNewConversationButton = true, onNavigateResource, renderConversationLabel = defaultConversationLabel, renderMessageContent = defaultMessageContent, renderPresentedFile, renderPendingFile = defaultPendingFile, renderToolInvocation, }) {
764
976
  const [draft, setDraft] = useControllableDraft(controlledDraft, onDraftChange);
765
977
  const [isPlanHidden, setIsPlanHidden] = useState(false);
766
978
  const [dismissedAskToolCallIds, setDismissedAskToolCallIds] = useState([]);
767
979
  const [askOverlayState, setAskOverlayState] = useState(null);
768
980
  const [isUpdatingModel, setIsUpdatingModel] = useState(false);
981
+ const [showScrollToBottom, setShowScrollToBottom] = useState(false);
982
+ const [thinkingLabelIndex, setThinkingLabelIndex] = useState(0);
769
983
  const messagesContainerRef = useRef(null);
770
984
  const inputRef = useRef(null);
771
985
  const fileInputRef = useRef(null);
@@ -788,7 +1002,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
788
1002
  return;
789
1003
  const minHeight = 48;
790
1004
  const maxHeight = 220;
791
- textarea.style.height = "0px";
1005
+ textarea.style.height = "auto";
792
1006
  const nextHeight = Math.min(maxHeight, Math.max(minHeight, textarea.scrollHeight));
793
1007
  textarea.style.height = `${nextHeight}px`;
794
1008
  textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
@@ -801,6 +1015,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
801
1015
  behavior,
802
1016
  });
803
1017
  isPinnedToBottomRef.current = true;
1018
+ setShowScrollToBottom(false);
804
1019
  return;
805
1020
  }
806
1021
  const el = messagesContainerRef.current;
@@ -811,13 +1026,16 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
811
1026
  behavior,
812
1027
  });
813
1028
  isPinnedToBottomRef.current = true;
1029
+ setShowScrollToBottom(false);
814
1030
  }, []);
815
1031
  const updatePinnedState = useCallback(() => {
816
1032
  const el = messagesContainerRef.current;
817
1033
  if (!el)
818
1034
  return;
819
1035
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
820
- isPinnedToBottomRef.current = distanceFromBottom <= 112;
1036
+ const isPinned = distanceFromBottom <= 112;
1037
+ isPinnedToBottomRef.current = isPinned;
1038
+ setShowScrollToBottom((prev) => (prev === !isPinned ? prev : !isPinned));
821
1039
  if (el.scrollTop > 48)
822
1040
  return;
823
1041
  if (!controller.hasOlderMessages || controller.isLoadingMessages || controller.isLoadingOlderMessages || loadingOlderFromScrollRef.current)
@@ -856,8 +1074,10 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
856
1074
  }, [controller.messages, isConversationBusy, scrollToLatest]);
857
1075
  useEffect(() => {
858
1076
  isPinnedToBottomRef.current = true;
1077
+ setShowScrollToBottom(false);
859
1078
  requestAnimationFrame(() => {
860
1079
  scrollToLatest("auto");
1080
+ inputRef.current?.focus();
861
1081
  });
862
1082
  }, [controller.activeConversationId, scrollToLatest]);
863
1083
  useEffect(() => {
@@ -865,6 +1085,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
865
1085
  }, [draft, resizeComposer]);
866
1086
  const displayMessageRows = useMemo(() => buildDisplayMessageRows(controller.messages), [controller.messages]);
867
1087
  const activeToolBanner = useMemo(() => getActiveToolBanner(controller.messages), [controller.messages]);
1088
+ const thinkingLabels = useMemo(() => thinkingLabelsFromSummary(activeToolBanner?.summary), [activeToolBanner?.summary]);
868
1089
  const planSummary = useMemo(() => latestPlanSummary(controller.messages), [controller.messages]);
869
1090
  const pendingAskUserInput = useMemo(() => {
870
1091
  const pending = findPendingAskUserInput(controller.messages);
@@ -896,6 +1117,17 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
896
1117
  const hasTools = (lastMsg.toolInvocations?.length || 0) > 0 || (lastMsg.parts || []).some((part) => part.type === "tool");
897
1118
  return hasText || hasTools;
898
1119
  }, [controller.messages]);
1120
+ useEffect(() => {
1121
+ setThinkingLabelIndex(0);
1122
+ }, [activeToolBanner?.summary, isConversationBusy]);
1123
+ useEffect(() => {
1124
+ if (!isConversationBusy || thinkingLabels.length < 2)
1125
+ return;
1126
+ const interval = window.setInterval(() => {
1127
+ setThinkingLabelIndex((prev) => (prev + 1) % thinkingLabels.length);
1128
+ }, 1700);
1129
+ return () => clearInterval(interval);
1130
+ }, [isConversationBusy, thinkingLabels]);
899
1131
  const dismissAskOverlay = useCallback((toolCallId) => {
900
1132
  setDismissedAskToolCallIds((prev) => (prev.includes(toolCallId) ? prev : [...prev, toolCallId]));
901
1133
  setAskOverlayState(null);
@@ -985,6 +1217,13 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
985
1217
  scrollToLatest("smooth");
986
1218
  await controller.sendMessage(message);
987
1219
  }, [controller, isConversationBusy, scrollToLatest, setDraft]);
1220
+ const handleSuggestionSend = useCallback(async (suggestion) => {
1221
+ const message = suggestion.trim();
1222
+ if (!message || isConversationBusy)
1223
+ return;
1224
+ scrollToLatest("smooth");
1225
+ await controller.sendMessage(message);
1226
+ }, [controller, isConversationBusy, scrollToLatest]);
988
1227
  const handleUploadSelection = useCallback(async (files) => {
989
1228
  const selectedFiles = files ? Array.from(files) : [];
990
1229
  if (selectedFiles.length === 0)
@@ -1027,26 +1266,29 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
1027
1266
  ? effectiveAskOverlayState.answers[effectiveAskOverlayState.currentQuestionIndex] || []
1028
1267
  : [];
1029
1268
  const canContinueAsk = activeAskAnswers.length > 0;
1030
- const liveStatusLabel = activeToolBanner?.summary || "Thinking through this...";
1269
+ const liveStatusLabel = thinkingLabels[thinkingLabelIndex] || "Working on it…";
1031
1270
  const headerTone = chromeStyle === "elevated" ? "default" : chromeStyle === "flat" ? "flat" : "subtle";
1032
1271
  const composerTone = chromeStyle === "flat" ? "flat" : chromeStyle === "subtle" ? "subtle" : "default";
1033
1272
  const showInlineStatus = statusPlacement === "inline" && isConversationBusy;
1034
1273
  const showComposerStatus = statusPlacement === "composer" && isConversationBusy;
1274
+ const resolvedHeaderBadge = badge === undefined
1275
+ ? _jsx(LemmaMarkIcon, { className: "lemma-assistant-experience-header-badge-icon" })
1276
+ : badge;
1035
1277
  return (_jsxs("div", { className: "lemma-assistant-experience", "data-chrome-style": chromeStyle, "data-status-placement": statusPlacement, "data-radius": radius, "data-show-model-picker": showModelPicker ? "true" : "false", "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", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-header", children: _jsxs("div", { className: "lemma-assistant-experience-sidebar-header-row", children: [_jsxs("div", { className: "lemma-assistant-experience-sidebar-copy", children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-title", children: "Conversations" }), _jsxs("div", { className: "lemma-assistant-experience-sidebar-meta", children: [controller.conversations.length, " total"] })] }), showNewConversationButton ? (_jsx("button", { type: "button", onClick: controller.clearMessages, className: "lemma-assistant-experience-sidebar-new", children: "New" })) : null] }) }), _jsx("div", { className: "lemma-assistant-experience-sidebar-items", children: controller.conversations.map((conversation) => {
1036
1278
  const isActive = conversation.id === controller.activeConversationId;
1037
1279
  return (_jsxs("button", { type: "button", onClick: () => controller.selectConversation(conversation.id), className: cx("lemma-assistant-experience-sidebar-item", isActive && "lemma-assistant-experience-sidebar-item-active"), children: [_jsx("div", { className: "lemma-assistant-experience-sidebar-item-title", children: renderConversationLabel({ conversation, isActive }) }), _jsx("div", { className: "lemma-assistant-experience-sidebar-item-status", children: (conversation.status || "waiting").toLowerCase() })] }, conversation.id));
1038
- }) })] })) : null, _jsxs("div", { className: "lemma-assistant-experience-main", children: [_jsxs("div", { className: "lemma-assistant-experience-card", children: [_jsx(AssistantHeader, { className: "lemma-assistant-experience-header", tone: headerTone, title: title, subtitle: subtitle, badge: _jsx("span", { className: "lemma-assistant-experience-header-badge-icon", children: "\u2728" }), controls: showModelPicker || showNewConversationButton ? (_jsxs(_Fragment, { children: [showModelPicker ? (_jsx(AssistantModelPicker, { value: controller.conversationModel, options: availableModels, getOptionLabel: (model) => availableModelLabels.get(model) ?? model, onChange: (nextModel) => { void handleModelChange(nextModel); }, disabled: isConversationBusy || isUpdatingModel, autoLabel: "Auto", className: "lemma-assistant-experience-model-picker" })) : null, showNewConversationButton ? (_jsx("button", { type: "button", onClick: controller.clearMessages, title: "New conversation", className: "lemma-assistant-experience-new", children: "\u21BA" })) : null] })) : undefined }), _jsxs(AssistantMessageViewport, { className: "lemma-assistant-experience-viewport", ref: messagesContainerRef, onScroll: updatePinnedState, children: [controller.messages.length === 0 && !isConversationBusy ? (emptyState || (_jsx(EmptyState, { onSendMessage: (message) => { void controller.sendMessage(message); }, suggestions: emptyStateSuggestions }))) : null, (controller.isLoadingMessages && controller.messages.length === 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading", children: _jsx("span", { className: "lemma-assistant-experience-loading-text", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading-older", children: _jsx("span", { className: "lemma-assistant-experience-loading-older-text", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
1039
- const previousRow = index > 0 ? displayMessageRows[index - 1] : null;
1040
- const showAssistantHeader = row.message.role !== "assistant"
1041
- ? false
1042
- : previousRow?.message.role !== "assistant";
1043
- const includesLastRawMessage = row.sourceIndexes.includes(controller.messages.length - 1);
1044
- 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));
1045
- }), showInlineStatus ? (_jsx("div", { className: "lemma-assistant-experience-inline-status", children: _jsx("div", { className: "lemma-assistant-experience-inline-status-pill", "data-has-content": lastMessageHasContent ? "true" : "false", children: _jsx(AssistantStatusPill, { label: liveStatusLabel, subtle: lastMessageHasContent }) }) })) : null, controller.error ? (_jsx("div", { className: "lemma-assistant-experience-error", children: _jsxs("div", { children: [_jsx("p", { className: "lemma-assistant-experience-error-title", children: "Something went wrong" }), _jsx("p", { className: "lemma-assistant-experience-error-copy", children: controller.error })] }) })) : null, (controller.messages.length > 0 || isConversationBusy || !!controller.error) ? (_jsx("div", { "aria-hidden": "true", className: "lemma-assistant-experience-bottom-spacer" })) : null, _jsx("div", { ref: bottomAnchorRef, "aria-hidden": "true", className: "lemma-assistant-experience-bottom-anchor" })] })] }), _jsx(AssistantComposer, { className: "lemma-assistant-experience-composer", tone: composerTone, floating: planSummary ? (isPlanHidden ? (_jsxs("button", { type: "button", onClick: () => setIsPlanHidden(false), className: "lemma-assistant-experience-plan-button", 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) => {
1280
+ }) })] })) : null, _jsxs("div", { className: "lemma-assistant-experience-main", children: [_jsxs("div", { className: "lemma-assistant-experience-card", children: [_jsx(AssistantHeader, { className: "lemma-assistant-experience-header", tone: headerTone, title: title, subtitle: subtitle, badge: resolvedHeaderBadge, controls: showModelPicker || showNewConversationButton ? (_jsxs(_Fragment, { children: [showModelPicker ? (_jsx(AssistantModelPicker, { value: controller.conversationModel, options: availableModels, getOptionLabel: (model) => availableModelLabels.get(model) ?? model, onChange: (nextModel) => { void handleModelChange(nextModel); }, disabled: isConversationBusy || isUpdatingModel, autoLabel: "Auto", className: "lemma-assistant-experience-model-picker" })) : null, showNewConversationButton ? (_jsx("button", { type: "button", onClick: controller.clearMessages, title: "New conversation", className: "lemma-assistant-experience-new", children: "\u21BA" })) : null] })) : undefined }), _jsx(AssistantMessageViewport, { className: "lemma-assistant-experience-viewport", ref: messagesContainerRef, onScroll: updatePinnedState, children: _jsxs("div", { className: "lemma-assistant-experience-live-region", "aria-live": "polite", "aria-atomic": "false", children: [controller.messages.length === 0 && !isConversationBusy ? (emptyState || (_jsx(EmptyState, { onSendMessage: (message) => { void handleSuggestionSend(message); }, suggestions: emptyStateSuggestions }))) : null, (controller.isLoadingMessages && controller.messages.length === 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading", children: _jsx("span", { className: "lemma-assistant-experience-loading-text", children: "Loading\u2026" }) })) : null, (controller.isLoadingOlderMessages && controller.messages.length > 0) ? (_jsx("div", { className: "lemma-assistant-experience-loading-older", children: _jsx("span", { className: "lemma-assistant-experience-loading-older-text", children: "Loading older\u2026" }) })) : null, displayMessageRows.map((row, index) => {
1281
+ const previousRow = index > 0 ? displayMessageRows[index - 1] : null;
1282
+ const showAssistantHeader = row.message.role !== "assistant"
1283
+ ? false
1284
+ : previousRow?.message.role !== "assistant";
1285
+ const includesLastRawMessage = row.sourceIndexes.includes(controller.messages.length - 1);
1286
+ 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));
1287
+ }), showInlineStatus ? (_jsx("div", { className: "lemma-assistant-experience-inline-status", children: _jsx("div", { className: "lemma-assistant-experience-inline-status-pill", "data-has-content": lastMessageHasContent ? "true" : "false", children: _jsx(AssistantStatusPill, { label: liveStatusLabel, subtle: lastMessageHasContent }) }) })) : null, controller.error ? (_jsx("div", { className: "lemma-assistant-experience-error", children: _jsxs("div", { children: [_jsx("p", { className: "lemma-assistant-experience-error-title", children: "Something went wrong" }), _jsx("p", { className: "lemma-assistant-experience-error-copy", children: controller.error })] }) })) : null, showScrollToBottom ? (_jsx("button", { type: "button", onClick: () => scrollToLatest("smooth"), className: "lemma-assistant-scroll-to-bottom", "aria-label": "Scroll to latest messages", title: "Scroll to latest messages", children: "\u2193" })) : null, (controller.messages.length > 0 || isConversationBusy || !!controller.error) ? (_jsx("div", { "aria-hidden": "true", className: "lemma-assistant-experience-bottom-spacer" })) : null, _jsx("div", { ref: bottomAnchorRef, "aria-hidden": "true", className: "lemma-assistant-experience-bottom-anchor" })] }) })] }), _jsx(AssistantComposer, { className: "lemma-assistant-experience-composer", tone: composerTone, floating: planSummary ? (isPlanHidden ? (_jsxs("button", { type: "button", onClick: () => setIsPlanHidden(false), className: "lemma-assistant-experience-plan-button", 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) => {
1046
1288
  const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
1047
1289
  return (_jsx("div", { children: renderPendingFile({
1048
1290
  file,
1049
1291
  remove: () => controller.removePendingFile(fileKey),
1050
1292
  }) }, fileKey));
1051
- }) })) : 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", children: _jsxs("div", { className: "lemma-assistant-experience-input-row", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "lemma-assistant-experience-file-input", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: "lemma-assistant-experience-upload", "data-disabled": isConversationBusy || controller.isUploadingFiles ? "true" : "false", 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", rows: 1, disabled: isConversationBusy }), _jsx("div", { className: "lemma-assistant-experience-send-wrap", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: "lemma-assistant-experience-send", "data-state": isConversationBusy ? "busy" : draft.trim() ? "ready" : "idle", title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "■" : "→" }) })] }) })) })] })] }));
1293
+ }) })) : 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", children: _jsxs("div", { className: "lemma-assistant-experience-input-row", children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "lemma-assistant-experience-file-input", onChange: (event) => { void handleUploadSelection(event.target.files); } }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isConversationBusy || controller.isUploadingFiles, className: "lemma-assistant-experience-upload", "data-disabled": isConversationBusy || controller.isUploadingFiles ? "true" : "false", 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", rows: 1, disabled: isConversationBusy }), _jsx("div", { className: "lemma-assistant-experience-send-wrap", children: _jsx("button", { onClick: isConversationBusy ? controller.stop : () => { void handleSubmit(); }, disabled: !isConversationBusy && !draft.trim(), className: "lemma-assistant-experience-send", "data-state": isConversationBusy ? "busy" : draft.trim() ? "ready" : "idle", "aria-label": isConversationBusy ? "Stop generating" : "Send message", title: isConversationBusy ? "Stop generating" : "Send message", children: isConversationBusy ? "■" : "→" }) })] }) })) })] })] }));
1052
1294
  }
@@ -65,6 +65,7 @@ export interface EmptyStateSuggestion {
65
65
  export interface AssistantExperienceCustomizationProps {
66
66
  title?: ReactNode;
67
67
  subtitle?: ReactNode;
68
+ badge?: ReactNode | null;
68
69
  placeholder?: string;
69
70
  emptyState?: ReactNode;
70
71
  emptyStateSuggestions?: EmptyStateSuggestion[];