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.
- package/README.md +25 -1
- package/dist/browser/lemma-client.js +176 -2
- package/dist/client.d.ts +2 -0
- package/dist/client.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/namespaces/pod-join-requests.d.ts +16 -0
- package/dist/namespaces/pod-join-requests.js +24 -0
- package/dist/namespaces/pod-members.d.ts +1 -0
- package/dist/namespaces/pod-members.js +3 -0
- package/dist/openapi_client/index.d.ts +9 -1
- package/dist/openapi_client/index.js +4 -0
- package/dist/openapi_client/models/DataStoreFlowStart.d.ts +3 -6
- package/dist/openapi_client/models/DatastoreOperation.d.ts +5 -0
- package/dist/openapi_client/models/DatastoreOperation.js +10 -0
- package/dist/openapi_client/models/FlowInstallEntity.d.ts +2 -2
- package/dist/openapi_client/models/FlowResponse.d.ts +4 -3
- package/dist/openapi_client/models/{FlowStart.d.ts → FlowStart_Input.d.ts} +1 -1
- package/dist/openapi_client/models/FlowStart_Output.d.ts +14 -0
- package/dist/openapi_client/models/FlowStart_Output.js +1 -0
- package/dist/openapi_client/models/PodJoinRequestApproveRequest.d.ts +6 -0
- package/dist/openapi_client/models/PodJoinRequestApproveRequest.js +1 -0
- package/dist/openapi_client/models/PodJoinRequestCreateResponse.d.ts +17 -0
- package/dist/openapi_client/models/PodJoinRequestCreateResponse.js +1 -0
- package/dist/openapi_client/models/PodJoinRequestListResponse.d.ts +7 -0
- package/dist/openapi_client/models/PodJoinRequestListResponse.js +1 -0
- package/dist/openapi_client/models/PodJoinRequestStatus.d.ts +5 -0
- package/dist/openapi_client/models/PodJoinRequestStatus.js +10 -0
- package/dist/openapi_client/models/WorkflowCreateRequest.d.ts +7 -6
- package/dist/openapi_client/models/WorkflowGraphUpdateRequest.d.ts +2 -2
- package/dist/openapi_client/models/WorkflowInstallMode.d.ts +7 -0
- package/dist/openapi_client/models/WorkflowInstallMode.js +12 -0
- package/dist/openapi_client/models/WorkflowUpdateRequest.d.ts +5 -4
- package/dist/openapi_client/services/FilesService.d.ts +1 -1
- package/dist/openapi_client/services/FilesService.js +1 -1
- package/dist/openapi_client/services/PodJoinRequestsService.d.ts +44 -0
- package/dist/openapi_client/services/PodJoinRequestsService.js +93 -0
- package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
- package/dist/openapi_client/services/WorkflowsService.js +1 -1
- package/dist/react/AuthGuard.d.ts +5 -2
- package/dist/react/AuthGuard.js +126 -3
- package/dist/react/components/AssistantChrome.js +1 -1
- package/dist/react/components/AssistantExperience.d.ts +7 -2
- package/dist/react/components/AssistantExperience.js +276 -34
- package/dist/react/components/assistant-types.d.ts +1 -0
- package/dist/react/styles.css +600 -224
- package/dist/react/useAssistantController.js +2 -1
- package/dist/react/useAssistantRuntime.d.ts +2 -1
- package/dist/react/useAssistantRuntime.js +7 -3
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- /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
|
-
|
|
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: "
|
|
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(
|
|
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: [
|
|
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:
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
-
:
|
|
611
|
-
|
|
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"
|
|
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 (
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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 = "
|
|
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 = "
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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[];
|