lemma-sdk 0.2.30 → 0.2.32
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 +213 -51
- package/dist/react/index.d.ts +23 -1
- package/dist/react/index.js +11 -0
- package/dist/react/useAgentInputSchema.d.ts +19 -0
- package/dist/react/useAgentInputSchema.js +73 -0
- package/dist/react/useAgentRun.js +18 -20
- package/dist/react/useAgentRuns.d.ts +33 -0
- package/dist/react/useAgentRuns.js +149 -0
- package/dist/react/useAssistantRun.js +10 -9
- package/dist/react/useAssistantSession.js +21 -25
- package/dist/react/useBulkRecords.js +9 -16
- package/dist/react/useConversation.js +24 -8
- package/dist/react/useConversations.d.ts +4 -0
- package/dist/react/useConversations.js +49 -3
- package/dist/react/useCreateRecord.d.ts +33 -3
- package/dist/react/useCreateRecord.js +20 -17
- package/dist/react/useCurrentUser.d.ts +14 -0
- package/dist/react/useCurrentUser.js +68 -0
- package/dist/react/useDeleteRecord.js +9 -16
- package/dist/react/useFlowRunHistory.js +1 -5
- package/dist/react/useFlowSession.js +41 -33
- package/dist/react/useForeignKeyOptions.d.ts +18 -0
- package/dist/react/useForeignKeyOptions.js +26 -15
- package/dist/react/useFunctionRun.d.ts +36 -0
- package/dist/react/useFunctionRun.js +30 -0
- package/dist/react/useFunctionRuns.d.ts +33 -0
- package/dist/react/useFunctionRuns.js +149 -0
- package/dist/react/useFunctionSession.js +37 -29
- package/dist/react/useJoinedRecords.d.ts +57 -2
- package/dist/react/useJoinedRecords.js +77 -27
- package/dist/react/useMembers.d.ts +4 -0
- package/dist/react/useMembers.js +55 -16
- package/dist/react/useOrganizationMembers.d.ts +26 -0
- package/dist/react/useOrganizationMembers.js +113 -0
- package/dist/react/usePodAccess.d.ts +22 -0
- package/dist/react/usePodAccess.js +128 -0
- package/dist/react/useRecord.d.ts +16 -0
- package/dist/react/useRecord.js +24 -13
- package/dist/react/useRecordForm.d.ts +42 -3
- package/dist/react/useRecordForm.js +44 -24
- package/dist/react/useRecords.d.ts +2 -0
- package/dist/react/useRecords.js +62 -22
- package/dist/react/useReferencingRecords.d.ts +66 -0
- package/dist/react/useReferencingRecords.js +159 -0
- package/dist/react/useRelatedRecords.d.ts +17 -0
- package/dist/react/useRelatedRecords.js +28 -21
- package/dist/react/useReverseRelatedRecords.d.ts +21 -0
- package/dist/react/useReverseRelatedRecords.js +30 -21
- package/dist/react/useSchemaForm.js +1 -13
- package/dist/react/useTable.js +24 -13
- package/dist/react/useTables.d.ts +4 -0
- package/dist/react/useTables.js +57 -15
- package/dist/react/useTaskSession.js +11 -22
- package/dist/react/useUpdateRecord.d.ts +34 -3
- package/dist/react/useUpdateRecord.js +21 -17
- package/dist/react/useWorkflowResume.d.ts +18 -0
- package/dist/react/useWorkflowResume.js +45 -0
- package/dist/react/useWorkflowRun.d.ts +21 -0
- package/dist/react/useWorkflowRun.js +49 -0
- package/dist/react/useWorkflowRuns.d.ts +33 -0
- package/dist/react/useWorkflowRuns.js +149 -0
- package/dist/react/useWorkflowStart.js +20 -27
- package/dist/react/utils.d.ts +5 -0
- package/dist/react/utils.js +25 -0
- package/dist/types.d.ts +1 -0
- package/package.json +2 -4
- package/dist/react/components/AssistantChrome.d.ts +0 -86
- package/dist/react/components/AssistantChrome.js +0 -48
- package/dist/react/components/AssistantEmbedded.d.ts +0 -10
- package/dist/react/components/AssistantEmbedded.js +0 -15
- package/dist/react/components/AssistantExperience.d.ts +0 -96
- package/dist/react/components/AssistantExperience.js +0 -1294
- package/dist/react/components/assistant-types.d.ts +0 -80
- package/dist/react/components/assistant-types.js +0 -1
|
@@ -1,1294 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
-
import ReactMarkdown from "react-markdown";
|
|
4
|
-
import remarkGfm from "remark-gfm";
|
|
5
|
-
import { AvailableModels } from "../../types.js";
|
|
6
|
-
import { AssistantAskOverlay, AssistantComposer, AssistantHeader, AssistantMessageViewport, AssistantModelPicker, AssistantStatusPill, } from "./AssistantChrome.js";
|
|
7
|
-
function cx(...values) {
|
|
8
|
-
return values.filter(Boolean).join(" ");
|
|
9
|
-
}
|
|
10
|
-
function asArray(value) {
|
|
11
|
-
return Array.isArray(value) ? value : [];
|
|
12
|
-
}
|
|
13
|
-
function asRecord(value) {
|
|
14
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
15
|
-
? value
|
|
16
|
-
: {};
|
|
17
|
-
}
|
|
18
|
-
function asString(value) {
|
|
19
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
20
|
-
}
|
|
21
|
-
function truncateLabel(value, max = 72) {
|
|
22
|
-
const trimmed = value.trim();
|
|
23
|
-
if (trimmed.length <= max)
|
|
24
|
-
return trimmed;
|
|
25
|
-
return `${trimmed.slice(0, max - 1)}…`;
|
|
26
|
-
}
|
|
27
|
-
function fileNameFromPath(path) {
|
|
28
|
-
const normalized = path.replace(/\\/g, "/");
|
|
29
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
30
|
-
return parts[parts.length - 1] || normalized;
|
|
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
|
-
}
|
|
61
|
-
function toolInvocationKey(tool) {
|
|
62
|
-
return `${tool.toolCallId}:${tool.state}`;
|
|
63
|
-
}
|
|
64
|
-
export function dedupToolInvocations(message) {
|
|
65
|
-
const invocations = [];
|
|
66
|
-
const seen = new Set();
|
|
67
|
-
(message.parts || []).forEach((part) => {
|
|
68
|
-
if (part.type !== "tool")
|
|
69
|
-
return;
|
|
70
|
-
const key = toolInvocationKey(part.toolInvocation);
|
|
71
|
-
if (seen.has(key))
|
|
72
|
-
return;
|
|
73
|
-
seen.add(key);
|
|
74
|
-
invocations.push(part.toolInvocation);
|
|
75
|
-
});
|
|
76
|
-
(message.toolInvocations || []).forEach((invocation) => {
|
|
77
|
-
const key = toolInvocationKey(invocation);
|
|
78
|
-
if (seen.has(key))
|
|
79
|
-
return;
|
|
80
|
-
seen.add(key);
|
|
81
|
-
invocations.push(invocation);
|
|
82
|
-
});
|
|
83
|
-
return invocations;
|
|
84
|
-
}
|
|
85
|
-
function normalizePlanStatus(rawStatus) {
|
|
86
|
-
const status = typeof rawStatus === "string" ? rawStatus.trim().toLowerCase() : "";
|
|
87
|
-
if (status === "completed" || status === "complete" || status === "done")
|
|
88
|
-
return "completed";
|
|
89
|
-
if (status === "in_progress" || status === "in-progress" || status === "running" || status === "active")
|
|
90
|
-
return "in_progress";
|
|
91
|
-
return "pending";
|
|
92
|
-
}
|
|
93
|
-
function parsePlanSteps(value) {
|
|
94
|
-
const entries = asArray(value);
|
|
95
|
-
return entries
|
|
96
|
-
.map((entry, index) => {
|
|
97
|
-
const obj = asRecord(entry);
|
|
98
|
-
const step = asString(obj.step) || asString(obj.title) || `Step ${index + 1}`;
|
|
99
|
-
if (!step)
|
|
100
|
-
return null;
|
|
101
|
-
return {
|
|
102
|
-
step,
|
|
103
|
-
status: normalizePlanStatus(obj.status),
|
|
104
|
-
};
|
|
105
|
-
})
|
|
106
|
-
.filter((entry) => entry !== null);
|
|
107
|
-
}
|
|
108
|
-
export function latestPlanSummary(messages) {
|
|
109
|
-
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
110
|
-
const invocations = dedupToolInvocations(messages[messageIndex]);
|
|
111
|
-
for (let invocationIndex = invocations.length - 1; invocationIndex >= 0; invocationIndex -= 1) {
|
|
112
|
-
const invocation = invocations[invocationIndex];
|
|
113
|
-
if (invocation.toolName.toLowerCase() !== "update_plan")
|
|
114
|
-
continue;
|
|
115
|
-
const argsObj = asRecord(invocation.args);
|
|
116
|
-
let steps = parsePlanSteps(argsObj.plan);
|
|
117
|
-
if (steps.length === 0) {
|
|
118
|
-
const resultObj = asRecord(invocation.result);
|
|
119
|
-
const outputObj = asRecord(resultObj.output);
|
|
120
|
-
steps = parsePlanSteps(outputObj.plan ?? resultObj.plan);
|
|
121
|
-
}
|
|
122
|
-
if (steps.length === 0)
|
|
123
|
-
continue;
|
|
124
|
-
const completedCount = steps.filter((step) => step.status === "completed").length;
|
|
125
|
-
const inProgressCount = steps.filter((step) => step.status === "in_progress").length;
|
|
126
|
-
const activeStep = steps.find((step) => step.status === "in_progress")?.step;
|
|
127
|
-
const running = invocation.state !== "result" || inProgressCount > 0;
|
|
128
|
-
return {
|
|
129
|
-
steps,
|
|
130
|
-
completedCount,
|
|
131
|
-
inProgressCount,
|
|
132
|
-
running,
|
|
133
|
-
activeStep,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
function normalizeQuestionType(value) {
|
|
140
|
-
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
141
|
-
if (normalized === "multi_select")
|
|
142
|
-
return "multi_select";
|
|
143
|
-
if (normalized === "rank_priorities")
|
|
144
|
-
return "rank_priorities";
|
|
145
|
-
return "single_select";
|
|
146
|
-
}
|
|
147
|
-
function parseAskUserInputQuestions(value) {
|
|
148
|
-
return asArray(value)
|
|
149
|
-
.map((entry) => {
|
|
150
|
-
const obj = asRecord(entry);
|
|
151
|
-
const question = asString(obj.question);
|
|
152
|
-
const options = asArray(obj.options)
|
|
153
|
-
.map((option) => (typeof option === "string" ? option.trim() : ""))
|
|
154
|
-
.filter((option) => option.length > 0)
|
|
155
|
-
.slice(0, 4);
|
|
156
|
-
if (!question || options.length < 2) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
question,
|
|
161
|
-
options,
|
|
162
|
-
type: normalizeQuestionType(obj.type),
|
|
163
|
-
};
|
|
164
|
-
})
|
|
165
|
-
.filter((entry) => entry !== null)
|
|
166
|
-
.slice(0, 3);
|
|
167
|
-
}
|
|
168
|
-
function extractAskUserInputQuestionsFromInvocation(invocation) {
|
|
169
|
-
if (invocation.toolName.toLowerCase() !== "ask_user_input")
|
|
170
|
-
return [];
|
|
171
|
-
const args = asRecord(invocation.args);
|
|
172
|
-
const result = asRecord(invocation.result);
|
|
173
|
-
const output = asRecord(result.output);
|
|
174
|
-
const fromArgs = parseAskUserInputQuestions(args.questions);
|
|
175
|
-
if (fromArgs.length > 0)
|
|
176
|
-
return fromArgs;
|
|
177
|
-
const fromResult = parseAskUserInputQuestions(result.questions);
|
|
178
|
-
if (fromResult.length > 0)
|
|
179
|
-
return fromResult;
|
|
180
|
-
return parseAskUserInputQuestions(output.questions);
|
|
181
|
-
}
|
|
182
|
-
export function findPendingAskUserInput(messages) {
|
|
183
|
-
let latestUserMessageIndex = -1;
|
|
184
|
-
messages.forEach((message, index) => {
|
|
185
|
-
if (message.role === "user") {
|
|
186
|
-
latestUserMessageIndex = index;
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
let pending;
|
|
190
|
-
messages.forEach((message, messageIndex) => {
|
|
191
|
-
if (message.role !== "assistant")
|
|
192
|
-
return;
|
|
193
|
-
const invocations = dedupToolInvocations(message);
|
|
194
|
-
invocations.forEach((invocation) => {
|
|
195
|
-
const questions = extractAskUserInputQuestionsFromInvocation(invocation);
|
|
196
|
-
if (questions.length === 0)
|
|
197
|
-
return;
|
|
198
|
-
pending = {
|
|
199
|
-
toolCallId: invocation.toolCallId || `${message.id}-ask-user-input`,
|
|
200
|
-
messageIndex,
|
|
201
|
-
questions,
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
if (!pending || latestUserMessageIndex > pending.messageIndex) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
return pending;
|
|
209
|
-
}
|
|
210
|
-
export function formatAskUserInputAnswers(questions, answers) {
|
|
211
|
-
const rows = [];
|
|
212
|
-
questions.forEach((question, index) => {
|
|
213
|
-
const answer = answers[index] || [];
|
|
214
|
-
if (answer.length === 0)
|
|
215
|
-
return;
|
|
216
|
-
const answerText = question.type === "rank_priorities"
|
|
217
|
-
? answer.join(" > ")
|
|
218
|
-
: answer.join(", ");
|
|
219
|
-
rows.push(`Q: ${question.question}`);
|
|
220
|
-
rows.push(`A: ${answerText}`);
|
|
221
|
-
rows.push("");
|
|
222
|
-
});
|
|
223
|
-
return rows.join("\n").trim();
|
|
224
|
-
}
|
|
225
|
-
function normalizeFilepaths(value) {
|
|
226
|
-
return asArray(value)
|
|
227
|
-
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
228
|
-
.filter((path) => path.length > 0);
|
|
229
|
-
}
|
|
230
|
-
export function extractPresentFilePathsFromInvocation(invocation) {
|
|
231
|
-
if (invocation.toolName.toLowerCase() !== "present_files")
|
|
232
|
-
return [];
|
|
233
|
-
const args = asRecord(invocation.args);
|
|
234
|
-
const result = asRecord(invocation.result);
|
|
235
|
-
const output = asRecord(result.output);
|
|
236
|
-
const fromArgs = [
|
|
237
|
-
normalizeFilepaths(args.filepaths),
|
|
238
|
-
normalizeFilepaths(args.file_paths),
|
|
239
|
-
normalizeFilepaths(args.paths),
|
|
240
|
-
].find((entries) => entries.length > 0) || [];
|
|
241
|
-
if (fromArgs.length > 0)
|
|
242
|
-
return fromArgs;
|
|
243
|
-
return [
|
|
244
|
-
normalizeFilepaths(result.filepaths),
|
|
245
|
-
normalizeFilepaths(result.file_paths),
|
|
246
|
-
normalizeFilepaths(output.filepaths),
|
|
247
|
-
normalizeFilepaths(output.file_paths),
|
|
248
|
-
].find((entries) => entries.length > 0) || [];
|
|
249
|
-
}
|
|
250
|
-
function formatCommandPreview(cmd) {
|
|
251
|
-
const compact = cmd.replace(/\s+/g, " ").trim();
|
|
252
|
-
return truncateLabel(compact, 64);
|
|
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
|
-
}
|
|
264
|
-
function primaryToolArgs(args) {
|
|
265
|
-
const request = asRecord(args.request);
|
|
266
|
-
if (Object.keys(request).length > 0)
|
|
267
|
-
return request;
|
|
268
|
-
const waitConfig = asRecord(args.wait_config);
|
|
269
|
-
if (Object.keys(waitConfig).length > 0)
|
|
270
|
-
return waitConfig;
|
|
271
|
-
return args;
|
|
272
|
-
}
|
|
273
|
-
function toolArg(args, key) {
|
|
274
|
-
const direct = args[key];
|
|
275
|
-
if (typeof direct !== "undefined")
|
|
276
|
-
return direct;
|
|
277
|
-
return primaryToolArgs(args)[key];
|
|
278
|
-
}
|
|
279
|
-
function formatToolDisplayName(toolName) {
|
|
280
|
-
return toolName
|
|
281
|
-
.replace(/_/g, " ")
|
|
282
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
283
|
-
}
|
|
284
|
-
function commentLabelFromArgs(args) {
|
|
285
|
-
const comment = asString(toolArg(args, "comment"));
|
|
286
|
-
return comment ? truncateLabel(comment, 72) : null;
|
|
287
|
-
}
|
|
288
|
-
function toolCallPrimaryLabel(toolName, args) {
|
|
289
|
-
return commentLabelFromArgs(args) || formatToolDisplayName(toolName);
|
|
290
|
-
}
|
|
291
|
-
function formatActiveToolSummary(toolName, args) {
|
|
292
|
-
const lowerName = toolName.toLowerCase();
|
|
293
|
-
const comment = commentLabelFromArgs(args);
|
|
294
|
-
if (lowerName === "exec_command") {
|
|
295
|
-
if (comment)
|
|
296
|
-
return `Running ${comment}`;
|
|
297
|
-
const cmd = asString(toolArg(args, "cmd"));
|
|
298
|
-
return cmd ? `Running ${formatCommandPreview(cmd)}` : "Running command";
|
|
299
|
-
}
|
|
300
|
-
if (lowerName === "ask_user_input") {
|
|
301
|
-
const questions = parseAskUserInputQuestions(toolArg(args, "questions"));
|
|
302
|
-
return questions.length > 0
|
|
303
|
-
? `Waiting for user input (${questions.length} question${questions.length === 1 ? "" : "s"})`
|
|
304
|
-
: "Waiting for user input";
|
|
305
|
-
}
|
|
306
|
-
if (lowerName === "present_files") {
|
|
307
|
-
const filepaths = extractPresentFilePathsFromInvocation({
|
|
308
|
-
toolCallId: "",
|
|
309
|
-
toolName,
|
|
310
|
-
args,
|
|
311
|
-
state: "call",
|
|
312
|
-
});
|
|
313
|
-
return filepaths.length > 0
|
|
314
|
-
? `Presenting ${filepaths.length} file${filepaths.length === 1 ? "" : "s"}`
|
|
315
|
-
: "Presenting files";
|
|
316
|
-
}
|
|
317
|
-
if (lowerName === "update_plan") {
|
|
318
|
-
const plan = asArray(toolArg(args, "plan"));
|
|
319
|
-
return `Updating plan (${plan.length} step${plan.length === 1 ? "" : "s"})`;
|
|
320
|
-
}
|
|
321
|
-
if (comment)
|
|
322
|
-
return `Running ${comment}`;
|
|
323
|
-
return `Running ${formatToolDisplayName(toolName)}`;
|
|
324
|
-
}
|
|
325
|
-
function formatToolResultSummary(toolName, args, result) {
|
|
326
|
-
const lowerName = toolName.toLowerCase();
|
|
327
|
-
if (lowerName === "present_files") {
|
|
328
|
-
const filepaths = extractPresentFilePathsFromInvocation({
|
|
329
|
-
toolCallId: "",
|
|
330
|
-
toolName,
|
|
331
|
-
args,
|
|
332
|
-
state: "result",
|
|
333
|
-
result,
|
|
334
|
-
});
|
|
335
|
-
if (filepaths.length > 0) {
|
|
336
|
-
return `Presented ${filepaths.length} file${filepaths.length === 1 ? "" : "s"}`;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
if (lowerName === "update_plan") {
|
|
340
|
-
const plan = asArray(toolArg(args, "plan"));
|
|
341
|
-
const completed = plan.filter((step) => asRecord(step).status === "completed").length;
|
|
342
|
-
if (plan.length > 0) {
|
|
343
|
-
return `${completed}/${plan.length} complete`;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const rawMessage = asString(result.message);
|
|
347
|
-
if (rawMessage)
|
|
348
|
-
return truncateLabel(rawMessage, 90);
|
|
349
|
-
if (typeof result.error === "string" && result.error.trim()) {
|
|
350
|
-
return truncateLabel(result.error.trim(), 90);
|
|
351
|
-
}
|
|
352
|
-
if (typeof result.resourceType === "string" && typeof result.resourceId === "string") {
|
|
353
|
-
return `Created ${result.resourceType}`;
|
|
354
|
-
}
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
function hasMeaningfulTextPart(message) {
|
|
358
|
-
return (message.parts || []).some((part) => part.type === "text" && part.text.trim().length > 0);
|
|
359
|
-
}
|
|
360
|
-
function isShowWidgetToolName(toolName) {
|
|
361
|
-
const normalized = toolName.trim().toLowerCase();
|
|
362
|
-
return normalized === "visualize:show_widget"
|
|
363
|
-
|| normalized === "visualize.show_widget"
|
|
364
|
-
|| normalized === "show_widget"
|
|
365
|
-
|| normalized === "render_widget";
|
|
366
|
-
}
|
|
367
|
-
function isCollapsibleAssistantMessage(message) {
|
|
368
|
-
if (message.role !== "assistant")
|
|
369
|
-
return false;
|
|
370
|
-
const hasTools = (message.toolInvocations?.length || 0) > 0 || (message.parts || []).some((part) => part.type === "tool");
|
|
371
|
-
const hasReasoning = (message.parts || []).some((part) => part.type === "reasoning" && part.text.trim().length > 0);
|
|
372
|
-
if (!hasTools && !hasReasoning)
|
|
373
|
-
return false;
|
|
374
|
-
return !hasMeaningfulTextPart(message) && (!message.content || message.content.trim().length === 0);
|
|
375
|
-
}
|
|
376
|
-
export function buildDisplayMessageRows(messages) {
|
|
377
|
-
const rows = [];
|
|
378
|
-
for (let i = 0; i < messages.length; i += 1) {
|
|
379
|
-
const message = messages[i];
|
|
380
|
-
if (!isCollapsibleAssistantMessage(message)) {
|
|
381
|
-
rows.push({
|
|
382
|
-
id: message.id,
|
|
383
|
-
message,
|
|
384
|
-
sourceIndexes: [i],
|
|
385
|
-
});
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
const cluster = [message];
|
|
389
|
-
const sourceIndexes = [i];
|
|
390
|
-
let j = i + 1;
|
|
391
|
-
while (j < messages.length && isCollapsibleAssistantMessage(messages[j])) {
|
|
392
|
-
cluster.push(messages[j]);
|
|
393
|
-
sourceIndexes.push(j);
|
|
394
|
-
j += 1;
|
|
395
|
-
}
|
|
396
|
-
if (cluster.length === 1) {
|
|
397
|
-
rows.push({
|
|
398
|
-
id: message.id,
|
|
399
|
-
message,
|
|
400
|
-
sourceIndexes,
|
|
401
|
-
});
|
|
402
|
-
i = j - 1;
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
const mergedParts = [];
|
|
406
|
-
const mergedToolInvocations = [];
|
|
407
|
-
cluster.forEach((entry) => {
|
|
408
|
-
if (entry.parts?.length) {
|
|
409
|
-
mergedParts.push(...entry.parts);
|
|
410
|
-
}
|
|
411
|
-
if (entry.toolInvocations?.length) {
|
|
412
|
-
mergedToolInvocations.push(...entry.toolInvocations);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
rows.push({
|
|
416
|
-
id: `tool-cluster-${cluster[0].id}`,
|
|
417
|
-
message: {
|
|
418
|
-
id: `tool-cluster-${cluster[0].id}`,
|
|
419
|
-
role: "assistant",
|
|
420
|
-
content: "",
|
|
421
|
-
parts: mergedParts,
|
|
422
|
-
toolInvocations: mergedToolInvocations,
|
|
423
|
-
createdAt: cluster[cluster.length - 1]?.createdAt ?? cluster[0]?.createdAt,
|
|
424
|
-
},
|
|
425
|
-
sourceIndexes,
|
|
426
|
-
});
|
|
427
|
-
i = j - 1;
|
|
428
|
-
}
|
|
429
|
-
return rows;
|
|
430
|
-
}
|
|
431
|
-
export function getActiveToolBanner(messages) {
|
|
432
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
433
|
-
const message = messages[i];
|
|
434
|
-
if (message.role !== "assistant")
|
|
435
|
-
continue;
|
|
436
|
-
const activeInvocations = dedupToolInvocations(message).filter((invocation) => invocation.state !== "result");
|
|
437
|
-
if (activeInvocations.length === 0)
|
|
438
|
-
continue;
|
|
439
|
-
const currentInvocation = activeInvocations[activeInvocations.length - 1];
|
|
440
|
-
return {
|
|
441
|
-
summary: formatActiveToolSummary(currentInvocation.toolName, currentInvocation.args),
|
|
442
|
-
activeCount: activeInvocations.length,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
function extractShowWidgetPayloadFromRecord(value) {
|
|
448
|
-
const record = asRecord(value);
|
|
449
|
-
const widgetCode = asString(record.widget_code);
|
|
450
|
-
if (!widgetCode) {
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
const loadingMessages = asArray(record.loading_messages)
|
|
454
|
-
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
|
455
|
-
.filter((entry) => entry.length > 0)
|
|
456
|
-
.slice(0, 4);
|
|
457
|
-
return {
|
|
458
|
-
title: asString(record.title),
|
|
459
|
-
widgetCode,
|
|
460
|
-
loadingMessages,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
function extractShowWidgetPayload(args, result) {
|
|
464
|
-
const fromArgs = extractShowWidgetPayloadFromRecord(args);
|
|
465
|
-
if (fromArgs)
|
|
466
|
-
return fromArgs;
|
|
467
|
-
const resultObject = asRecord(result);
|
|
468
|
-
const outputObject = asRecord(resultObject.output);
|
|
469
|
-
const dataObject = asRecord(resultObject.data);
|
|
470
|
-
return extractShowWidgetPayloadFromRecord(outputObject)
|
|
471
|
-
|| extractShowWidgetPayloadFromRecord(dataObject)
|
|
472
|
-
|| extractShowWidgetPayloadFromRecord(resultObject);
|
|
473
|
-
}
|
|
474
|
-
function escapeHtml(value) {
|
|
475
|
-
return value
|
|
476
|
-
.replace(/&/g, "&")
|
|
477
|
-
.replace(/</g, "<")
|
|
478
|
-
.replace(/>/g, ">")
|
|
479
|
-
.replace(/"/g, """)
|
|
480
|
-
.replace(/'/g, "'");
|
|
481
|
-
}
|
|
482
|
-
function buildWidgetIframeDocument(toolCallId, payload) {
|
|
483
|
-
const widgetCode = payload.widgetCode.trim();
|
|
484
|
-
const isSvg = /^<svg[\s>]/i.test(widgetCode);
|
|
485
|
-
const safeToolCallId = JSON.stringify(toolCallId);
|
|
486
|
-
const safeTitle = escapeHtml(payload.title || "Widget");
|
|
487
|
-
return `<!doctype html>
|
|
488
|
-
<html>
|
|
489
|
-
<head>
|
|
490
|
-
<meta charset="utf-8" />
|
|
491
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
492
|
-
<style>
|
|
493
|
-
html, body { margin: 0; padding: 0; width: 100%; background: transparent; color: #0f172a; }
|
|
494
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
495
|
-
.widget-svg-root { width: 100%; display: flex; justify-content: center; align-items: flex-start; overflow: visible; }
|
|
496
|
-
.widget-svg-root > svg { width: 100%; max-width: 100%; height: auto; }
|
|
497
|
-
</style>
|
|
498
|
-
<script>
|
|
499
|
-
(function () {
|
|
500
|
-
var toolCallId = ${safeToolCallId};
|
|
501
|
-
function computeHeight() {
|
|
502
|
-
var doc = document.documentElement;
|
|
503
|
-
var body = document.body;
|
|
504
|
-
return Math.max(
|
|
505
|
-
doc ? doc.scrollHeight : 0,
|
|
506
|
-
body ? body.scrollHeight : 0,
|
|
507
|
-
120
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
function reportHeight() {
|
|
511
|
-
parent.postMessage({ type: 'lemma-widget-height', height: Math.max(120, Math.ceil(computeHeight())), toolCallId: toolCallId }, '*');
|
|
512
|
-
}
|
|
513
|
-
window.sendPrompt = function (text) {
|
|
514
|
-
var message = typeof text === 'string' ? text.trim() : '';
|
|
515
|
-
if (!message) return;
|
|
516
|
-
parent.postMessage({ type: 'lemma-widget-send-prompt', text: message, toolCallId: toolCallId }, '*');
|
|
517
|
-
};
|
|
518
|
-
window.addEventListener('load', function () {
|
|
519
|
-
reportHeight();
|
|
520
|
-
setTimeout(reportHeight, 50);
|
|
521
|
-
});
|
|
522
|
-
window.addEventListener('resize', reportHeight);
|
|
523
|
-
if (typeof MutationObserver !== 'undefined' && document.documentElement) {
|
|
524
|
-
var observer = new MutationObserver(reportHeight);
|
|
525
|
-
observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
526
|
-
}
|
|
527
|
-
})();
|
|
528
|
-
</script>
|
|
529
|
-
</head>
|
|
530
|
-
<body aria-label="${safeTitle}">
|
|
531
|
-
${isSvg ? `<div class="widget-svg-root">${widgetCode}</div>` : widgetCode}
|
|
532
|
-
</body>
|
|
533
|
-
</html>`;
|
|
534
|
-
}
|
|
535
|
-
function useControllableDraft(controlledValue, onChange) {
|
|
536
|
-
const [uncontrolledValue, setUncontrolledValue] = useState("");
|
|
537
|
-
const isControlled = typeof controlledValue === "string";
|
|
538
|
-
const setValue = useCallback((nextValue) => {
|
|
539
|
-
if (!isControlled) {
|
|
540
|
-
setUncontrolledValue(nextValue);
|
|
541
|
-
}
|
|
542
|
-
onChange?.(nextValue);
|
|
543
|
-
}, [isControlled, onChange]);
|
|
544
|
-
return [isControlled ? controlledValue : uncontrolledValue, setValue];
|
|
545
|
-
}
|
|
546
|
-
function defaultConversationLabel({ conversation }) {
|
|
547
|
-
return conversation.title || "Untitled conversation";
|
|
548
|
-
}
|
|
549
|
-
const markdownComponents = {
|
|
550
|
-
a: ({ node: _node, ...props }) => (_jsx("a", { ...props, target: props.target || "_blank", rel: props.rel || "noreferrer noopener" })),
|
|
551
|
-
};
|
|
552
|
-
function defaultMessageContent({ message }) {
|
|
553
|
-
return (_jsx("div", { className: "lemma-assistant-markdown", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], skipHtml: true, components: markdownComponents, children: message.content }) }));
|
|
554
|
-
}
|
|
555
|
-
function defaultPresentedFile({ filepath }) {
|
|
556
|
-
return (_jsxs("div", { className: "lemma-assistant-presented-file-card", children: [_jsx("div", { className: "lemma-assistant-presented-file-name", children: fileNameFromPath(filepath) }), _jsx("div", { className: "lemma-assistant-presented-file-path", children: filepath })] }));
|
|
557
|
-
}
|
|
558
|
-
function defaultPendingFile({ file, remove }) {
|
|
559
|
-
return (_jsxs("span", { className: "lemma-assistant-pending-file-chip", children: [_jsx("span", { className: "lemma-assistant-pending-file-chip-label", children: file.name }), _jsx("button", { type: "button", onClick: remove, className: "lemma-assistant-pending-file-chip-remove", title: "Remove file", children: "\u00D7" })] }));
|
|
560
|
-
}
|
|
561
|
-
export function PlanSummaryStrip({ plan, onHide }) {
|
|
562
|
-
const [showAll, setShowAll] = useState(false);
|
|
563
|
-
const visibleSteps = showAll ? plan.steps : plan.steps.slice(0, 5);
|
|
564
|
-
const hiddenCount = Math.max(0, plan.steps.length - visibleSteps.length);
|
|
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] })] }));
|
|
566
|
-
}
|
|
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, } = {}) {
|
|
573
|
-
const [show, setShow] = useState(false);
|
|
574
|
-
const labelOptions = useMemo(() => resolvedThinkingLabels(labels, activeToolSummary), [labels, activeToolSummary]);
|
|
575
|
-
const [labelIndex, setLabelIndex] = useState(0);
|
|
576
|
-
useEffect(() => {
|
|
577
|
-
const timer = setTimeout(() => setShow(true), 350);
|
|
578
|
-
return () => clearTimeout(timer);
|
|
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]);
|
|
591
|
-
if (!show)
|
|
592
|
-
return null;
|
|
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…" })] }) }));
|
|
594
|
-
}
|
|
595
|
-
export const DEFAULT_EMPTY_STATE_SUGGESTIONS = [
|
|
596
|
-
{ text: "Help me get started", icon: "→" },
|
|
597
|
-
{ text: "Summarize this for me", icon: "✦" },
|
|
598
|
-
{ text: "Help me draft a reply", icon: "✎" },
|
|
599
|
-
{ text: "Brainstorm next steps", icon: "⋯" },
|
|
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
|
-
}
|
|
604
|
-
export function EmptyState({ onSendMessage, suggestions = DEFAULT_EMPTY_STATE_SUGGESTIONS, }) {
|
|
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}`))) })] }));
|
|
606
|
-
}
|
|
607
|
-
function ReasoningPartCard({ text, isStreaming, durationMs, }) {
|
|
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 }) })] }));
|
|
609
|
-
}
|
|
610
|
-
function PresentFilesCard({ filepaths, conversationId, renderPresentedFile, }) {
|
|
611
|
-
const fakeMessage = {
|
|
612
|
-
id: "present-files",
|
|
613
|
-
role: "assistant",
|
|
614
|
-
content: "",
|
|
615
|
-
};
|
|
616
|
-
const fakeInvocation = {
|
|
617
|
-
toolCallId: "present-files",
|
|
618
|
-
toolName: "present_files",
|
|
619
|
-
args: { filepaths },
|
|
620
|
-
state: "result",
|
|
621
|
-
};
|
|
622
|
-
return (_jsx("div", { className: "lemma-assistant-presented-files", children: filepaths.map((filepath) => (_jsx("div", { className: "lemma-assistant-presented-file", children: (renderPresentedFile || defaultPresentedFile)({
|
|
623
|
-
filepath,
|
|
624
|
-
activeConversationId: conversationId ?? null,
|
|
625
|
-
invocation: fakeInvocation,
|
|
626
|
-
message: fakeMessage,
|
|
627
|
-
}) }, `present-file-${filepath}`))) }));
|
|
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
|
-
}
|
|
705
|
-
function ToolDetailsPanel({ toolName, args, state, result, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
|
|
706
|
-
const resultData = result || {};
|
|
707
|
-
const primaryLabel = toolCallPrimaryLabel(toolName, args);
|
|
708
|
-
const hasCommentLabel = !!commentLabelFromArgs(args);
|
|
709
|
-
const toolDisplayName = formatToolDisplayName(toolName);
|
|
710
|
-
const canNavigate = state === "result"
|
|
711
|
-
&& resultData.success !== false
|
|
712
|
-
&& typeof resultData.resourceType === "string"
|
|
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);
|
|
760
|
-
if (renderToolInvocation) {
|
|
761
|
-
return (_jsx("div", { className: "lemma-assistant-tool-details-panel lemma-assistant-tool-details-panel-custom", children: renderToolInvocation({
|
|
762
|
-
invocation: {
|
|
763
|
-
toolCallId: "detail-tool",
|
|
764
|
-
toolName,
|
|
765
|
-
args,
|
|
766
|
-
state: state === "result" ? "result" : "call",
|
|
767
|
-
...(result ? { result } : {}),
|
|
768
|
-
},
|
|
769
|
-
message,
|
|
770
|
-
activeConversationId,
|
|
771
|
-
}) }));
|
|
772
|
-
}
|
|
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] })] }) })] }));
|
|
774
|
-
}
|
|
775
|
-
function InlineToolCall({ invocation, isSelected, onClick, showStem = true, }) {
|
|
776
|
-
const resultData = (invocation.result || {});
|
|
777
|
-
const isExecuting = invocation.state !== "result";
|
|
778
|
-
const isComplete = invocation.state === "result" && resultData.success !== false;
|
|
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;
|
|
783
|
-
const summary = isExecuting
|
|
784
|
-
? "Running"
|
|
785
|
-
: isFailed
|
|
786
|
-
? (typeof resultData.error === "string" ? resultData.error : "Tool failed")
|
|
787
|
-
: (formatToolResultSummary(invocation.toolName, invocation.args, resultData) || "Completed");
|
|
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] })] }));
|
|
790
|
-
}
|
|
791
|
-
function ToolActivityRollup({ detailParts, onNavigateResource, renderToolInvocation, message, activeConversationId, }) {
|
|
792
|
-
const [activeToolCallId, setActiveToolCallId] = useState(null);
|
|
793
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
794
|
-
const toolParts = detailParts.filter((part) => part.type === "tool");
|
|
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;
|
|
798
|
-
const activeInvocation = [...toolParts]
|
|
799
|
-
.reverse()
|
|
800
|
-
.find((part) => part.toolInvocation.state !== "result")
|
|
801
|
-
?.toolInvocation;
|
|
802
|
-
const failedCount = toolParts.filter((part) => (part.toolInvocation.state === "result" && part.toolInvocation.result?.success === false)).length;
|
|
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";
|
|
810
|
-
const summary = activeInvocation
|
|
811
|
-
? formatActiveToolSummary(activeInvocation.toolName, activeInvocation.args)
|
|
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) => {
|
|
821
|
-
if (part.type === "reasoning") {
|
|
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}`));
|
|
825
|
-
}
|
|
826
|
-
const invocation = part.toolInvocation;
|
|
827
|
-
const isSelected = activeToolCallId === invocation.toolCallId;
|
|
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));
|
|
829
|
-
}) })) : null] }));
|
|
830
|
-
}
|
|
831
|
-
function ShowWidgetToolCard({ invocation, onSendPrompt, }) {
|
|
832
|
-
const resultData = (invocation.result || {});
|
|
833
|
-
const payload = extractShowWidgetPayload(invocation.args, resultData);
|
|
834
|
-
const displayName = payload?.title || formatToolDisplayName(invocation.toolName);
|
|
835
|
-
const hasResultData = Object.keys(resultData).length > 0;
|
|
836
|
-
const isExecuting = invocation.state !== "result" && !hasResultData;
|
|
837
|
-
const isFailed = resultData.success === false || (!isExecuting && typeof resultData.error === "string" && resultData.error.length > 0);
|
|
838
|
-
const iframeRef = useRef(null);
|
|
839
|
-
const [height, setHeight] = useState(220);
|
|
840
|
-
const iframeDocument = useMemo(() => (payload ? buildWidgetIframeDocument(invocation.toolCallId, payload) : ""), [invocation.toolCallId, payload]);
|
|
841
|
-
useEffect(() => {
|
|
842
|
-
const handleMessage = (event) => {
|
|
843
|
-
if (!iframeRef.current || event.source !== iframeRef.current.contentWindow)
|
|
844
|
-
return;
|
|
845
|
-
const data = asRecord(event.data);
|
|
846
|
-
const messageType = asString(data.type);
|
|
847
|
-
if (!messageType)
|
|
848
|
-
return;
|
|
849
|
-
if (messageType === "lemma-widget-send-prompt") {
|
|
850
|
-
const text = asString(data.text);
|
|
851
|
-
if (!text)
|
|
852
|
-
return;
|
|
853
|
-
void onSendPrompt(text);
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
if (messageType === "lemma-widget-height") {
|
|
857
|
-
const rawHeight = typeof data.height === "number" ? data.height : Number(data.height);
|
|
858
|
-
if (!Number.isFinite(rawHeight))
|
|
859
|
-
return;
|
|
860
|
-
setHeight(Math.max(120, Math.min(2400, Math.round(rawHeight))));
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
window.addEventListener("message", handleMessage);
|
|
864
|
-
return () => {
|
|
865
|
-
window.removeEventListener("message", handleMessage);
|
|
866
|
-
};
|
|
867
|
-
}, [onSendPrompt]);
|
|
868
|
-
return (_jsxs("div", { className: "lemma-assistant-widget-card", children: [_jsxs("div", { className: "lemma-assistant-widget-card-header", children: [_jsx("div", { className: "lemma-assistant-widget-card-title", children: displayName }), _jsx("span", { className: cx("lemma-assistant-widget-card-badge", isExecuting && "lemma-assistant-widget-card-badge-rendering", isFailed && "lemma-assistant-widget-card-badge-failed", !isExecuting && !isFailed && "lemma-assistant-widget-card-badge-ready"), children: isExecuting ? "Rendering" : isFailed ? "Failed" : "Ready" })] }), isFailed ? (_jsx("p", { className: "lemma-assistant-widget-card-error", children: typeof resultData.error === "string" && resultData.error.length > 0
|
|
869
|
-
? resultData.error
|
|
870
|
-
: "Failed to render widget." })) : null, !isFailed && payload ? (_jsx("iframe", { ref: iframeRef, title: displayName, srcDoc: iframeDocument, sandbox: "allow-scripts allow-forms allow-popups allow-downloads", height: height, className: "lemma-assistant-widget-card-frame" })) : null, !isFailed && !payload ? (_jsx("p", { className: "lemma-assistant-widget-card-missing", children: "Widget output is missing `widget_code`." })) : null] }));
|
|
871
|
-
}
|
|
872
|
-
export function MessageGroup({ message, conversationId, onNavigateResource, onWidgetSendPrompt, isStreaming, showAssistantHeader, renderMessageContent, renderPresentedFile, renderToolInvocation, }) {
|
|
873
|
-
const orderedParts = message.parts && message.parts.length > 0
|
|
874
|
-
? message.parts
|
|
875
|
-
: [
|
|
876
|
-
...(message.content?.trim()
|
|
877
|
-
? [{ id: `${message.id}-fallback-text`, type: "text", text: message.content }]
|
|
878
|
-
: []),
|
|
879
|
-
...((message.toolInvocations || []).map((tool, index) => ({
|
|
880
|
-
id: `${tool.toolCallId || message.id}-fallback-tool-${index}`,
|
|
881
|
-
type: "tool",
|
|
882
|
-
toolInvocation: tool,
|
|
883
|
-
}))),
|
|
884
|
-
];
|
|
885
|
-
const toolParts = orderedParts.filter((part) => part.type === "tool");
|
|
886
|
-
const groupedToolParts = toolParts.filter((part) => !isShowWidgetToolName(part.toolInvocation.toolName));
|
|
887
|
-
const reasoningParts = orderedParts.filter((part) => part.type === "reasoning");
|
|
888
|
-
const presentableFilepaths = Array.from(new Set(groupedToolParts.flatMap((part) => extractPresentFilePathsFromInvocation(part.toolInvocation))));
|
|
889
|
-
const rollupOrderedParts = orderedParts.filter((part) => (part.type === "reasoning" || (part.type === "tool" && !isShowWidgetToolName(part.toolInvocation.toolName))));
|
|
890
|
-
const blocks = [];
|
|
891
|
-
orderedParts.forEach((part) => {
|
|
892
|
-
if (part.type === "tool") {
|
|
893
|
-
if (isShowWidgetToolName(part.toolInvocation.toolName)) {
|
|
894
|
-
blocks.push({
|
|
895
|
-
id: `${part.id}-widget`,
|
|
896
|
-
kind: "widget",
|
|
897
|
-
toolPart: part,
|
|
898
|
-
});
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
const lastBlock = blocks[blocks.length - 1];
|
|
902
|
-
if (lastBlock?.kind === "tools") {
|
|
903
|
-
lastBlock.toolParts.push(part);
|
|
904
|
-
}
|
|
905
|
-
else {
|
|
906
|
-
blocks.push({
|
|
907
|
-
id: `${part.id}-tools`,
|
|
908
|
-
kind: "tools",
|
|
909
|
-
toolParts: [part],
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
blocks.push({
|
|
915
|
-
id: part.id,
|
|
916
|
-
kind: "content",
|
|
917
|
-
part,
|
|
918
|
-
});
|
|
919
|
-
});
|
|
920
|
-
const nonToolParts = orderedParts.filter((part) => part.type !== "tool");
|
|
921
|
-
const firstToolsBlockId = blocks.find((block) => block.kind === "tools")?.id;
|
|
922
|
-
const hasTextParts = orderedParts.some((part) => part.type === "text" && part.text.trim().length > 0);
|
|
923
|
-
const foldReasoningIntoToolRollup = groupedToolParts.length > 0 && reasoningParts.length > 0 && !hasTextParts;
|
|
924
|
-
const lastTextPartId = [...nonToolParts]
|
|
925
|
-
.reverse()
|
|
926
|
-
.find((part) => part.type === "text" && part.text.trim().length > 0)
|
|
927
|
-
?.id;
|
|
928
|
-
const messageTimestamp = formatMessageTimestamp(message.createdAt);
|
|
929
|
-
if (message.role === "user") {
|
|
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] }));
|
|
938
|
-
}
|
|
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) => {
|
|
940
|
-
if (block.kind === "tools") {
|
|
941
|
-
if (foldReasoningIntoToolRollup && block.id !== firstToolsBlockId) {
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
return (_jsx(ToolActivityRollup, { detailParts: foldReasoningIntoToolRollup && block.id === firstToolsBlockId
|
|
945
|
-
? rollupOrderedParts
|
|
946
|
-
: block.toolParts, onNavigateResource: onNavigateResource, renderToolInvocation: renderToolInvocation, message: message, activeConversationId: conversationId ?? null }, block.id));
|
|
947
|
-
}
|
|
948
|
-
if (block.kind === "widget") {
|
|
949
|
-
return (_jsx(ShowWidgetToolCard, { invocation: block.toolPart.toolInvocation, onSendPrompt: onWidgetSendPrompt }, block.id));
|
|
950
|
-
}
|
|
951
|
-
const part = block.part;
|
|
952
|
-
if (part.type === "text") {
|
|
953
|
-
const trimmedText = part.text.trim();
|
|
954
|
-
if (trimmedText.length === 0) {
|
|
955
|
-
return null;
|
|
956
|
-
}
|
|
957
|
-
return (_jsx("div", { className: "lemma-assistant-message-text", children: renderMessageContent({
|
|
958
|
-
message: {
|
|
959
|
-
...message,
|
|
960
|
-
content: trimmedText + (isStreaming && part.id === lastTextPartId ? " ▍" : ""),
|
|
961
|
-
parts: undefined,
|
|
962
|
-
toolInvocations: undefined,
|
|
963
|
-
},
|
|
964
|
-
}) }, part.id));
|
|
965
|
-
}
|
|
966
|
-
if (part.type === "reasoning") {
|
|
967
|
-
if (foldReasoningIntoToolRollup) {
|
|
968
|
-
return null;
|
|
969
|
-
}
|
|
970
|
-
return (_jsx(ReasoningPartCard, { text: part.text, isStreaming: part.state === "streaming", durationMs: part.durationMs }, part.id));
|
|
971
|
-
}
|
|
972
|
-
return null;
|
|
973
|
-
}), presentableFilepaths.length > 0 ? (_jsx(PresentFilesCard, { filepaths: presentableFilepaths, conversationId: conversationId, renderPresentedFile: renderPresentedFile })) : null] })] }));
|
|
974
|
-
}
|
|
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, }) {
|
|
976
|
-
const [draft, setDraft] = useControllableDraft(controlledDraft, onDraftChange);
|
|
977
|
-
const [isPlanHidden, setIsPlanHidden] = useState(false);
|
|
978
|
-
const [dismissedAskToolCallIds, setDismissedAskToolCallIds] = useState([]);
|
|
979
|
-
const [askOverlayState, setAskOverlayState] = useState(null);
|
|
980
|
-
const [isUpdatingModel, setIsUpdatingModel] = useState(false);
|
|
981
|
-
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
|
982
|
-
const [thinkingLabelIndex, setThinkingLabelIndex] = useState(0);
|
|
983
|
-
const messagesContainerRef = useRef(null);
|
|
984
|
-
const inputRef = useRef(null);
|
|
985
|
-
const fileInputRef = useRef(null);
|
|
986
|
-
const bottomAnchorRef = useRef(null);
|
|
987
|
-
const isPinnedToBottomRef = useRef(true);
|
|
988
|
-
const loadingOlderFromScrollRef = useRef(false);
|
|
989
|
-
const isConversationBusy = controller.isLoading || controller.isActiveConversationRunning;
|
|
990
|
-
const availableModels = useMemo(() => {
|
|
991
|
-
const dynamicModels = controller.availableModels
|
|
992
|
-
.map((model) => model.id)
|
|
993
|
-
.filter((model) => model.trim().length > 0);
|
|
994
|
-
return dynamicModels.length > 0
|
|
995
|
-
? dynamicModels
|
|
996
|
-
: Object.values(AvailableModels);
|
|
997
|
-
}, [controller.availableModels]);
|
|
998
|
-
const availableModelLabels = useMemo(() => new Map(controller.availableModels.map((model) => [model.id, model.name])), [controller.availableModels]);
|
|
999
|
-
const resizeComposer = useCallback(() => {
|
|
1000
|
-
const textarea = inputRef.current;
|
|
1001
|
-
if (!textarea)
|
|
1002
|
-
return;
|
|
1003
|
-
const minHeight = 48;
|
|
1004
|
-
const maxHeight = 220;
|
|
1005
|
-
textarea.style.height = "auto";
|
|
1006
|
-
const nextHeight = Math.min(maxHeight, Math.max(minHeight, textarea.scrollHeight));
|
|
1007
|
-
textarea.style.height = `${nextHeight}px`;
|
|
1008
|
-
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
1009
|
-
}, []);
|
|
1010
|
-
const scrollToLatest = useCallback((behavior = "auto") => {
|
|
1011
|
-
const anchor = bottomAnchorRef.current;
|
|
1012
|
-
if (anchor) {
|
|
1013
|
-
anchor.scrollIntoView({
|
|
1014
|
-
block: "end",
|
|
1015
|
-
behavior,
|
|
1016
|
-
});
|
|
1017
|
-
isPinnedToBottomRef.current = true;
|
|
1018
|
-
setShowScrollToBottom(false);
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
const el = messagesContainerRef.current;
|
|
1022
|
-
if (!el)
|
|
1023
|
-
return;
|
|
1024
|
-
el.scrollTo({
|
|
1025
|
-
top: el.scrollHeight,
|
|
1026
|
-
behavior,
|
|
1027
|
-
});
|
|
1028
|
-
isPinnedToBottomRef.current = true;
|
|
1029
|
-
setShowScrollToBottom(false);
|
|
1030
|
-
}, []);
|
|
1031
|
-
const updatePinnedState = useCallback(() => {
|
|
1032
|
-
const el = messagesContainerRef.current;
|
|
1033
|
-
if (!el)
|
|
1034
|
-
return;
|
|
1035
|
-
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
1036
|
-
const isPinned = distanceFromBottom <= 112;
|
|
1037
|
-
isPinnedToBottomRef.current = isPinned;
|
|
1038
|
-
setShowScrollToBottom((prev) => (prev === !isPinned ? prev : !isPinned));
|
|
1039
|
-
if (el.scrollTop > 48)
|
|
1040
|
-
return;
|
|
1041
|
-
if (!controller.hasOlderMessages || controller.isLoadingMessages || controller.isLoadingOlderMessages || loadingOlderFromScrollRef.current)
|
|
1042
|
-
return;
|
|
1043
|
-
const previousScrollTop = el.scrollTop;
|
|
1044
|
-
const previousScrollHeight = el.scrollHeight;
|
|
1045
|
-
loadingOlderFromScrollRef.current = true;
|
|
1046
|
-
void controller.loadOlderMessages()
|
|
1047
|
-
.then((didLoad) => {
|
|
1048
|
-
if (!didLoad)
|
|
1049
|
-
return;
|
|
1050
|
-
requestAnimationFrame(() => {
|
|
1051
|
-
const nextEl = messagesContainerRef.current;
|
|
1052
|
-
if (!nextEl)
|
|
1053
|
-
return;
|
|
1054
|
-
nextEl.scrollTop = previousScrollTop + (nextEl.scrollHeight - previousScrollHeight);
|
|
1055
|
-
});
|
|
1056
|
-
})
|
|
1057
|
-
.finally(() => {
|
|
1058
|
-
loadingOlderFromScrollRef.current = false;
|
|
1059
|
-
});
|
|
1060
|
-
}, [
|
|
1061
|
-
controller,
|
|
1062
|
-
]);
|
|
1063
|
-
useEffect(() => {
|
|
1064
|
-
const el = messagesContainerRef.current;
|
|
1065
|
-
if (!el)
|
|
1066
|
-
return;
|
|
1067
|
-
if (isPinnedToBottomRef.current) {
|
|
1068
|
-
requestAnimationFrame(() => {
|
|
1069
|
-
requestAnimationFrame(() => {
|
|
1070
|
-
scrollToLatest(isConversationBusy ? "auto" : "smooth");
|
|
1071
|
-
});
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
}, [controller.messages, isConversationBusy, scrollToLatest]);
|
|
1075
|
-
useEffect(() => {
|
|
1076
|
-
isPinnedToBottomRef.current = true;
|
|
1077
|
-
setShowScrollToBottom(false);
|
|
1078
|
-
requestAnimationFrame(() => {
|
|
1079
|
-
scrollToLatest("auto");
|
|
1080
|
-
inputRef.current?.focus();
|
|
1081
|
-
});
|
|
1082
|
-
}, [controller.activeConversationId, scrollToLatest]);
|
|
1083
|
-
useEffect(() => {
|
|
1084
|
-
resizeComposer();
|
|
1085
|
-
}, [draft, resizeComposer]);
|
|
1086
|
-
const displayMessageRows = useMemo(() => buildDisplayMessageRows(controller.messages), [controller.messages]);
|
|
1087
|
-
const activeToolBanner = useMemo(() => getActiveToolBanner(controller.messages), [controller.messages]);
|
|
1088
|
-
const thinkingLabels = useMemo(() => thinkingLabelsFromSummary(activeToolBanner?.summary), [activeToolBanner?.summary]);
|
|
1089
|
-
const planSummary = useMemo(() => latestPlanSummary(controller.messages), [controller.messages]);
|
|
1090
|
-
const pendingAskUserInput = useMemo(() => {
|
|
1091
|
-
const pending = findPendingAskUserInput(controller.messages);
|
|
1092
|
-
if (!pending)
|
|
1093
|
-
return null;
|
|
1094
|
-
if (dismissedAskToolCallIds.includes(pending.toolCallId))
|
|
1095
|
-
return null;
|
|
1096
|
-
return pending;
|
|
1097
|
-
}, [controller.messages, dismissedAskToolCallIds]);
|
|
1098
|
-
const effectiveAskOverlayState = useMemo(() => {
|
|
1099
|
-
if (!pendingAskUserInput)
|
|
1100
|
-
return null;
|
|
1101
|
-
if (askOverlayState && askOverlayState.toolCallId === pendingAskUserInput.toolCallId) {
|
|
1102
|
-
return askOverlayState;
|
|
1103
|
-
}
|
|
1104
|
-
return {
|
|
1105
|
-
toolCallId: pendingAskUserInput.toolCallId,
|
|
1106
|
-
currentQuestionIndex: 0,
|
|
1107
|
-
answers: pendingAskUserInput.questions.map(() => []),
|
|
1108
|
-
};
|
|
1109
|
-
}, [askOverlayState, pendingAskUserInput]);
|
|
1110
|
-
const lastMessageHasContent = useMemo(() => {
|
|
1111
|
-
if (controller.messages.length === 0)
|
|
1112
|
-
return false;
|
|
1113
|
-
const lastMsg = controller.messages[controller.messages.length - 1];
|
|
1114
|
-
if (lastMsg.role !== "assistant")
|
|
1115
|
-
return false;
|
|
1116
|
-
const hasText = (lastMsg.parts || []).some((part) => part.type === "text" && part.text.trim().length > 0);
|
|
1117
|
-
const hasTools = (lastMsg.toolInvocations?.length || 0) > 0 || (lastMsg.parts || []).some((part) => part.type === "tool");
|
|
1118
|
-
return hasText || hasTools;
|
|
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]);
|
|
1131
|
-
const dismissAskOverlay = useCallback((toolCallId) => {
|
|
1132
|
-
setDismissedAskToolCallIds((prev) => (prev.includes(toolCallId) ? prev : [...prev, toolCallId]));
|
|
1133
|
-
setAskOverlayState(null);
|
|
1134
|
-
}, []);
|
|
1135
|
-
const commitAskAnswersToComposer = useCallback((toolCallId, answers) => {
|
|
1136
|
-
if (!pendingAskUserInput || pendingAskUserInput.toolCallId !== toolCallId)
|
|
1137
|
-
return;
|
|
1138
|
-
const formatted = formatAskUserInputAnswers(pendingAskUserInput.questions, answers);
|
|
1139
|
-
if (formatted.length > 0) {
|
|
1140
|
-
const nextDraft = draft.trim().length > 0 ? `${draft.trim()}\n\n${formatted}` : formatted;
|
|
1141
|
-
setDraft(nextDraft);
|
|
1142
|
-
}
|
|
1143
|
-
dismissAskOverlay(toolCallId);
|
|
1144
|
-
requestAnimationFrame(() => {
|
|
1145
|
-
inputRef.current?.focus();
|
|
1146
|
-
});
|
|
1147
|
-
}, [dismissAskOverlay, draft, pendingAskUserInput, setDraft]);
|
|
1148
|
-
const updateAskAnswer = useCallback((option) => {
|
|
1149
|
-
if (!pendingAskUserInput || !effectiveAskOverlayState)
|
|
1150
|
-
return;
|
|
1151
|
-
if (effectiveAskOverlayState.toolCallId !== pendingAskUserInput.toolCallId)
|
|
1152
|
-
return;
|
|
1153
|
-
const questionIndex = effectiveAskOverlayState.currentQuestionIndex;
|
|
1154
|
-
const question = pendingAskUserInput.questions[questionIndex];
|
|
1155
|
-
if (!question)
|
|
1156
|
-
return;
|
|
1157
|
-
const nextAnswers = effectiveAskOverlayState.answers.map((answers) => [...answers]);
|
|
1158
|
-
const currentAnswers = nextAnswers[questionIndex] || [];
|
|
1159
|
-
if (question.type === "single_select") {
|
|
1160
|
-
nextAnswers[questionIndex] = [option];
|
|
1161
|
-
if (questionIndex >= pendingAskUserInput.questions.length - 1) {
|
|
1162
|
-
commitAskAnswersToComposer(effectiveAskOverlayState.toolCallId, nextAnswers);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
setAskOverlayState({
|
|
1166
|
-
...effectiveAskOverlayState,
|
|
1167
|
-
answers: nextAnswers,
|
|
1168
|
-
currentQuestionIndex: questionIndex + 1,
|
|
1169
|
-
});
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
nextAnswers[questionIndex] = currentAnswers.includes(option)
|
|
1173
|
-
? currentAnswers.filter((entry) => entry !== option)
|
|
1174
|
-
: [...currentAnswers, option];
|
|
1175
|
-
setAskOverlayState({
|
|
1176
|
-
...effectiveAskOverlayState,
|
|
1177
|
-
answers: nextAnswers,
|
|
1178
|
-
});
|
|
1179
|
-
}, [commitAskAnswersToComposer, effectiveAskOverlayState, pendingAskUserInput]);
|
|
1180
|
-
const continueAskQuestions = useCallback(() => {
|
|
1181
|
-
if (!pendingAskUserInput || !effectiveAskOverlayState)
|
|
1182
|
-
return;
|
|
1183
|
-
if (effectiveAskOverlayState.toolCallId !== pendingAskUserInput.toolCallId)
|
|
1184
|
-
return;
|
|
1185
|
-
const questionIndex = effectiveAskOverlayState.currentQuestionIndex;
|
|
1186
|
-
const answers = effectiveAskOverlayState.answers[questionIndex] || [];
|
|
1187
|
-
if (answers.length === 0)
|
|
1188
|
-
return;
|
|
1189
|
-
if (questionIndex >= pendingAskUserInput.questions.length - 1) {
|
|
1190
|
-
commitAskAnswersToComposer(effectiveAskOverlayState.toolCallId, effectiveAskOverlayState.answers);
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
setAskOverlayState({
|
|
1194
|
-
...effectiveAskOverlayState,
|
|
1195
|
-
currentQuestionIndex: questionIndex + 1,
|
|
1196
|
-
});
|
|
1197
|
-
}, [commitAskAnswersToComposer, effectiveAskOverlayState, pendingAskUserInput]);
|
|
1198
|
-
const handleSubmit = useCallback(async () => {
|
|
1199
|
-
if (!draft.trim() || isConversationBusy)
|
|
1200
|
-
return;
|
|
1201
|
-
const message = draft.trim();
|
|
1202
|
-
setDraft("");
|
|
1203
|
-
scrollToLatest("smooth");
|
|
1204
|
-
await controller.sendMessage(message);
|
|
1205
|
-
}, [controller, draft, isConversationBusy, scrollToLatest, setDraft]);
|
|
1206
|
-
const handleWidgetSendPrompt = useCallback(async (prompt) => {
|
|
1207
|
-
const message = prompt.trim();
|
|
1208
|
-
if (!message)
|
|
1209
|
-
return;
|
|
1210
|
-
if (isConversationBusy) {
|
|
1211
|
-
setDraft(message);
|
|
1212
|
-
requestAnimationFrame(() => {
|
|
1213
|
-
inputRef.current?.focus();
|
|
1214
|
-
});
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
scrollToLatest("smooth");
|
|
1218
|
-
await controller.sendMessage(message);
|
|
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]);
|
|
1227
|
-
const handleUploadSelection = useCallback(async (files) => {
|
|
1228
|
-
const selectedFiles = files ? Array.from(files) : [];
|
|
1229
|
-
if (selectedFiles.length === 0)
|
|
1230
|
-
return;
|
|
1231
|
-
try {
|
|
1232
|
-
await controller.uploadFiles(selectedFiles, { deferUntilSend: true });
|
|
1233
|
-
requestAnimationFrame(() => {
|
|
1234
|
-
inputRef.current?.focus();
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
finally {
|
|
1238
|
-
if (fileInputRef.current) {
|
|
1239
|
-
fileInputRef.current.value = "";
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}, [controller]);
|
|
1243
|
-
const handleKeyDown = useCallback((event) => {
|
|
1244
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
1245
|
-
event.preventDefault();
|
|
1246
|
-
void handleSubmit();
|
|
1247
|
-
}
|
|
1248
|
-
}, [handleSubmit]);
|
|
1249
|
-
const handleModelChange = useCallback(async (nextModel) => {
|
|
1250
|
-
if (isUpdatingModel)
|
|
1251
|
-
return;
|
|
1252
|
-
setIsUpdatingModel(true);
|
|
1253
|
-
try {
|
|
1254
|
-
await controller.setConversationModel(nextModel);
|
|
1255
|
-
}
|
|
1256
|
-
finally {
|
|
1257
|
-
setIsUpdatingModel(false);
|
|
1258
|
-
}
|
|
1259
|
-
}, [controller, isUpdatingModel]);
|
|
1260
|
-
const activeAskQuestion = pendingAskUserInput
|
|
1261
|
-
&& effectiveAskOverlayState
|
|
1262
|
-
&& effectiveAskOverlayState.toolCallId === pendingAskUserInput.toolCallId
|
|
1263
|
-
? pendingAskUserInput.questions[effectiveAskOverlayState.currentQuestionIndex]
|
|
1264
|
-
: null;
|
|
1265
|
-
const activeAskAnswers = activeAskQuestion && effectiveAskOverlayState
|
|
1266
|
-
? effectiveAskOverlayState.answers[effectiveAskOverlayState.currentQuestionIndex] || []
|
|
1267
|
-
: [];
|
|
1268
|
-
const canContinueAsk = activeAskAnswers.length > 0;
|
|
1269
|
-
const liveStatusLabel = thinkingLabels[thinkingLabelIndex] || "Working on it…";
|
|
1270
|
-
const headerTone = chromeStyle === "elevated" ? "default" : chromeStyle === "flat" ? "flat" : "subtle";
|
|
1271
|
-
const composerTone = chromeStyle === "flat" ? "flat" : chromeStyle === "subtle" ? "subtle" : "default";
|
|
1272
|
-
const showInlineStatus = statusPlacement === "inline" && isConversationBusy;
|
|
1273
|
-
const showComposerStatus = statusPlacement === "composer" && isConversationBusy;
|
|
1274
|
-
const resolvedHeaderBadge = badge === undefined
|
|
1275
|
-
? _jsx(LemmaMarkIcon, { className: "lemma-assistant-experience-header-badge-icon" })
|
|
1276
|
-
: badge;
|
|
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) => {
|
|
1278
|
-
const isActive = conversation.id === controller.activeConversationId;
|
|
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));
|
|
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) => {
|
|
1288
|
-
const fileKey = `${file.name}:${file.size}:${file.lastModified}`;
|
|
1289
|
-
return (_jsx("div", { children: renderPendingFile({
|
|
1290
|
-
file,
|
|
1291
|
-
remove: () => controller.removePendingFile(fileKey),
|
|
1292
|
-
}) }, fileKey));
|
|
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 ? "■" : "→" }) })] }) })) })] })] }));
|
|
1294
|
-
}
|