lemma-sdk 0.2.25 → 0.2.27

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 +272 -32
  44. package/dist/react/components/assistant-types.d.ts +1 -0
  45. package/dist/react/styles.css +594 -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,37 +770,56 @@ 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
775
  function InlineToolCall({ invocation, isSelected, onClick, }) {
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
+ 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" }), _jsx("span", { className: "lemma-assistant-inline-tool-call-stem" })] }), _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 }), _jsx("span", { className: "lemma-assistant-inline-tool-call-summary", children: summary })] })] }));
596
789
  }
597
790
  function ToolActivityRollup({ detailParts, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
598
791
  const [activeToolCallId, setActiveToolCallId] = useState(null);
599
792
  const [isExpanded, setIsExpanded] = useState(false);
600
793
  const toolParts = detailParts.filter((part) => part.type === "tool");
601
794
  const reasoningParts = detailParts.filter((part) => part.type === "reasoning");
795
+ const totalThoughtDurationMs = reasoningParts.reduce((total, part) => total + (part.durationMs ?? 0), 0);
796
+ const shouldCollapse = detailParts.length > 1;
602
797
  const activeInvocation = [...toolParts]
603
798
  .reverse()
604
799
  .find((part) => part.toolInvocation.state !== "result")
605
800
  ?.toolInvocation;
606
801
  const failedCount = toolParts.filter((part) => (part.toolInvocation.state === "result" && part.toolInvocation.result?.success === false)).length;
607
802
  const isWorking = !!activeInvocation || reasoningParts.some((part) => part.state === "streaming");
803
+ const completionSummary = toolParts.length > 0
804
+ ? `Completed ${toolParts.length} tool${toolParts.length === 1 ? "" : "s"}`
805
+ : totalThoughtDurationMs > 0
806
+ ? `Thought for ${formatDurationCompact(totalThoughtDurationMs)}`
807
+ : "Completed";
608
808
  const summary = activeInvocation
609
809
  ? 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) => {
810
+ : isWorking
811
+ ? "Working on it…"
812
+ : `${completionSummary}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
813
+ const collapsedSummary = isWorking
814
+ ? summary
815
+ : `${totalThoughtDurationMs > 0
816
+ ? `Worked for ${formatDurationCompact(totalThoughtDurationMs)}`
817
+ : `Worked through ${detailParts.length} step${detailParts.length === 1 ? "" : "s"}`}${failedCount > 0 ? ` · ${failedCount} failed` : ""}`;
818
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup", 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" })] })) : (_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 })] })), !shouldCollapse || isExpanded ? (_jsx("div", { className: "lemma-assistant-tool-rollup-details", children: detailParts.map((part) => {
612
819
  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}`));
820
+ return (_jsxs("div", { className: "lemma-assistant-tool-rollup-thinking", children: [_jsx("div", { className: "lemma-assistant-tool-rollup-thinking-title", children: part.state === "streaming"
821
+ ? "Internal note"
822
+ : `Internal note${part.durationMs ? ` · ${formatDurationCompact(part.durationMs)}` : ""}` }), _jsx("pre", { className: "lemma-assistant-tool-rollup-thinking-text", children: part.text })] }, `thinking-${part.id}`));
614
823
  }
615
824
  const invocation = part.toolInvocation;
616
825
  const isSelected = activeToolCallId === invocation.toolCallId;
@@ -714,15 +923,16 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
714
923
  .reverse()
715
924
  .find((part) => part.type === "text" && part.text.trim().length > 0)
716
925
  ?.id;
926
+ const messageTimestamp = formatMessageTimestamp(message.createdAt);
717
927
  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
- }) }) }));
928
+ return (_jsxs("div", { className: "lemma-assistant-message lemma-assistant-message-user", children: [_jsx("div", { className: "lemma-assistant-message-user-bubble", children: renderMessageContent({
929
+ message: {
930
+ ...message,
931
+ content: message.content,
932
+ parts: undefined,
933
+ toolInvocations: undefined,
934
+ },
935
+ }) }), messageTimestamp ? (_jsx("time", { className: "lemma-assistant-message-timestamp lemma-assistant-message-timestamp-user", dateTime: messageTimestamp.dateTime, children: messageTimestamp.text })) : null] }));
726
936
  }
727
937
  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
938
  if (block.kind === "tools") {
@@ -760,12 +970,14 @@ export function MessageGroup({ message, conversationId, onNavigateResource, onWi
760
970
  return null;
761
971
  }), presentableFilepaths.length > 0 ? (_jsx(PresentFilesCard, { filepaths: presentableFilepaths, conversationId: conversationId, renderPresentedFile: renderPresentedFile })) : null] })] }));
762
972
  }
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, }) {
973
+ 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
974
  const [draft, setDraft] = useControllableDraft(controlledDraft, onDraftChange);
765
975
  const [isPlanHidden, setIsPlanHidden] = useState(false);
766
976
  const [dismissedAskToolCallIds, setDismissedAskToolCallIds] = useState([]);
767
977
  const [askOverlayState, setAskOverlayState] = useState(null);
768
978
  const [isUpdatingModel, setIsUpdatingModel] = useState(false);
979
+ const [showScrollToBottom, setShowScrollToBottom] = useState(false);
980
+ const [thinkingLabelIndex, setThinkingLabelIndex] = useState(0);
769
981
  const messagesContainerRef = useRef(null);
770
982
  const inputRef = useRef(null);
771
983
  const fileInputRef = useRef(null);
@@ -788,7 +1000,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
788
1000
  return;
789
1001
  const minHeight = 48;
790
1002
  const maxHeight = 220;
791
- textarea.style.height = "0px";
1003
+ textarea.style.height = "auto";
792
1004
  const nextHeight = Math.min(maxHeight, Math.max(minHeight, textarea.scrollHeight));
793
1005
  textarea.style.height = `${nextHeight}px`;
794
1006
  textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
@@ -801,6 +1013,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
801
1013
  behavior,
802
1014
  });
803
1015
  isPinnedToBottomRef.current = true;
1016
+ setShowScrollToBottom(false);
804
1017
  return;
805
1018
  }
806
1019
  const el = messagesContainerRef.current;
@@ -811,13 +1024,16 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
811
1024
  behavior,
812
1025
  });
813
1026
  isPinnedToBottomRef.current = true;
1027
+ setShowScrollToBottom(false);
814
1028
  }, []);
815
1029
  const updatePinnedState = useCallback(() => {
816
1030
  const el = messagesContainerRef.current;
817
1031
  if (!el)
818
1032
  return;
819
1033
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
820
- isPinnedToBottomRef.current = distanceFromBottom <= 112;
1034
+ const isPinned = distanceFromBottom <= 112;
1035
+ isPinnedToBottomRef.current = isPinned;
1036
+ setShowScrollToBottom((prev) => (prev === !isPinned ? prev : !isPinned));
821
1037
  if (el.scrollTop > 48)
822
1038
  return;
823
1039
  if (!controller.hasOlderMessages || controller.isLoadingMessages || controller.isLoadingOlderMessages || loadingOlderFromScrollRef.current)
@@ -856,8 +1072,10 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
856
1072
  }, [controller.messages, isConversationBusy, scrollToLatest]);
857
1073
  useEffect(() => {
858
1074
  isPinnedToBottomRef.current = true;
1075
+ setShowScrollToBottom(false);
859
1076
  requestAnimationFrame(() => {
860
1077
  scrollToLatest("auto");
1078
+ inputRef.current?.focus();
861
1079
  });
862
1080
  }, [controller.activeConversationId, scrollToLatest]);
863
1081
  useEffect(() => {
@@ -865,6 +1083,7 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
865
1083
  }, [draft, resizeComposer]);
866
1084
  const displayMessageRows = useMemo(() => buildDisplayMessageRows(controller.messages), [controller.messages]);
867
1085
  const activeToolBanner = useMemo(() => getActiveToolBanner(controller.messages), [controller.messages]);
1086
+ const thinkingLabels = useMemo(() => thinkingLabelsFromSummary(activeToolBanner?.summary), [activeToolBanner?.summary]);
868
1087
  const planSummary = useMemo(() => latestPlanSummary(controller.messages), [controller.messages]);
869
1088
  const pendingAskUserInput = useMemo(() => {
870
1089
  const pending = findPendingAskUserInput(controller.messages);
@@ -896,6 +1115,17 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
896
1115
  const hasTools = (lastMsg.toolInvocations?.length || 0) > 0 || (lastMsg.parts || []).some((part) => part.type === "tool");
897
1116
  return hasText || hasTools;
898
1117
  }, [controller.messages]);
1118
+ useEffect(() => {
1119
+ setThinkingLabelIndex(0);
1120
+ }, [activeToolBanner?.summary, isConversationBusy]);
1121
+ useEffect(() => {
1122
+ if (!isConversationBusy || thinkingLabels.length < 2)
1123
+ return;
1124
+ const interval = window.setInterval(() => {
1125
+ setThinkingLabelIndex((prev) => (prev + 1) % thinkingLabels.length);
1126
+ }, 1700);
1127
+ return () => clearInterval(interval);
1128
+ }, [isConversationBusy, thinkingLabels]);
899
1129
  const dismissAskOverlay = useCallback((toolCallId) => {
900
1130
  setDismissedAskToolCallIds((prev) => (prev.includes(toolCallId) ? prev : [...prev, toolCallId]));
901
1131
  setAskOverlayState(null);
@@ -985,6 +1215,13 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
985
1215
  scrollToLatest("smooth");
986
1216
  await controller.sendMessage(message);
987
1217
  }, [controller, isConversationBusy, scrollToLatest, setDraft]);
1218
+ const handleSuggestionSend = useCallback(async (suggestion) => {
1219
+ const message = suggestion.trim();
1220
+ if (!message || isConversationBusy)
1221
+ return;
1222
+ scrollToLatest("smooth");
1223
+ await controller.sendMessage(message);
1224
+ }, [controller, isConversationBusy, scrollToLatest]);
988
1225
  const handleUploadSelection = useCallback(async (files) => {
989
1226
  const selectedFiles = files ? Array.from(files) : [];
990
1227
  if (selectedFiles.length === 0)
@@ -1027,26 +1264,29 @@ export function AssistantExperienceView({ controller, title = "Lemma Assistant",
1027
1264
  ? effectiveAskOverlayState.answers[effectiveAskOverlayState.currentQuestionIndex] || []
1028
1265
  : [];
1029
1266
  const canContinueAsk = activeAskAnswers.length > 0;
1030
- const liveStatusLabel = activeToolBanner?.summary || "Thinking through this...";
1267
+ const liveStatusLabel = thinkingLabels[thinkingLabelIndex] || "Working on it…";
1031
1268
  const headerTone = chromeStyle === "elevated" ? "default" : chromeStyle === "flat" ? "flat" : "subtle";
1032
1269
  const composerTone = chromeStyle === "flat" ? "flat" : chromeStyle === "subtle" ? "subtle" : "default";
1033
1270
  const showInlineStatus = statusPlacement === "inline" && isConversationBusy;
1034
1271
  const showComposerStatus = statusPlacement === "composer" && isConversationBusy;
1272
+ const resolvedHeaderBadge = badge === undefined
1273
+ ? _jsx(LemmaMarkIcon, { className: "lemma-assistant-experience-header-badge-icon" })
1274
+ : badge;
1035
1275
  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
1276
  const isActive = conversation.id === controller.activeConversationId;
1037
1277
  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) => {
1278
+ }) })] })) : 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) => {
1279
+ const previousRow = index > 0 ? displayMessageRows[index - 1] : null;
1280
+ const showAssistantHeader = row.message.role !== "assistant"
1281
+ ? false
1282
+ : previousRow?.message.role !== "assistant";
1283
+ const includesLastRawMessage = row.sourceIndexes.includes(controller.messages.length - 1);
1284
+ 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));
1285
+ }), 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
1286
  const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
1047
1287
  return (_jsx("div", { children: renderPendingFile({
1048
1288
  file,
1049
1289
  remove: () => controller.removePendingFile(fileKey),
1050
1290
  }) }, 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 ? "■" : "→" }) })] }) })) })] })] }));
1291
+ }) })) : 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
1292
  }
@@ -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[];