lemma-sdk 0.2.27 → 0.2.30
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 +113 -233
- package/bin/lemma-sdk.js +108 -0
- package/dist/browser/lemma-client.js +125 -4
- package/dist/client.d.ts +2 -0
- package/dist/client.js +3 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/datastore-query.d.ts +54 -0
- package/dist/datastore-query.js +157 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/namespaces/datastore.d.ts +9 -0
- package/dist/namespaces/datastore.js +13 -0
- package/dist/namespaces/records.d.ts +1 -1
- package/dist/openapi_client/index.d.ts +4 -0
- package/dist/openapi_client/index.js +1 -0
- package/dist/openapi_client/models/ConvertedArtifactResponse.d.ts +6 -0
- package/dist/openapi_client/models/ConvertedArtifactResponse.js +1 -0
- package/dist/openapi_client/models/ConvertedFileResponse.d.ts +10 -0
- package/dist/openapi_client/models/ConvertedFileResponse.js +1 -0
- package/dist/openapi_client/models/CreateFolderRequest.d.ts +3 -1
- package/dist/openapi_client/models/FlowRunEntity.d.ts +1 -0
- package/dist/openapi_client/models/ScheduledFlowStart.d.ts +3 -6
- package/dist/openapi_client/models/ScheduledFlowStartType.d.ts +4 -0
- package/dist/openapi_client/models/ScheduledFlowStartType.js +9 -0
- package/dist/openapi_client/models/WorkflowInstallRequest.d.ts +5 -0
- package/dist/openapi_client/models/WorkflowTimeInstallConfig.d.ts +19 -0
- package/dist/openapi_client/models/WorkflowTimeInstallConfig.js +1 -0
- package/dist/openapi_client/services/FilesService.d.ts +27 -1
- package/dist/openapi_client/services/FilesService.js +69 -1
- package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
- package/dist/openapi_client/services/WorkflowsService.js +1 -1
- package/dist/react/assistant-output.d.ts +6 -0
- package/dist/react/assistant-output.js +90 -0
- package/dist/react/components/AssistantExperience.js +6 -4
- package/dist/react/index.d.ts +42 -8
- package/dist/react/index.js +21 -4
- package/dist/react/useAgentRun.d.ts +17 -0
- package/dist/react/useAgentRun.js +58 -0
- package/dist/react/useAssistantRun.d.ts +9 -0
- package/dist/react/useAssistantRun.js +19 -9
- package/dist/react/useAssistantSession.d.ts +5 -0
- package/dist/react/useAssistantSession.js +123 -70
- package/dist/react/useBulkRecords.d.ts +20 -0
- package/dist/react/useBulkRecords.js +72 -0
- package/dist/react/useConversation.d.ts +18 -0
- package/dist/react/useConversation.js +59 -0
- package/dist/react/useConversationMessages.d.ts +59 -0
- package/dist/react/useConversationMessages.js +167 -0
- package/dist/react/useConversations.d.ts +48 -0
- package/dist/react/useConversations.js +182 -0
- package/dist/react/useCreateRecord.d.ts +18 -0
- package/dist/react/useCreateRecord.js +58 -0
- package/dist/react/useDeleteRecord.d.ts +21 -0
- package/dist/react/useDeleteRecord.js +59 -0
- package/dist/react/useForeignKeyOptions.d.ts +31 -0
- package/dist/react/useForeignKeyOptions.js +150 -0
- package/dist/react/useJoinedRecords.d.ts +18 -0
- package/dist/react/useJoinedRecords.js +79 -0
- package/dist/react/useMembers.d.ts +22 -0
- package/dist/react/useMembers.js +59 -0
- package/dist/react/useRecord.d.ts +18 -0
- package/dist/react/useRecord.js +64 -0
- package/dist/react/useRecordForm.d.ts +42 -0
- package/dist/react/useRecordForm.js +238 -0
- package/dist/react/useRecordSchema.d.ts +20 -0
- package/dist/react/useRecordSchema.js +24 -0
- package/dist/react/useRecords.d.ts +18 -0
- package/dist/react/useRecords.js +106 -0
- package/dist/react/useRelatedRecords.d.ts +43 -0
- package/dist/react/useRelatedRecords.js +232 -0
- package/dist/react/useReverseRelatedRecords.d.ts +47 -0
- package/dist/react/useReverseRelatedRecords.js +226 -0
- package/dist/react/useSchemaForm.d.ts +24 -0
- package/dist/react/useSchemaForm.js +116 -0
- package/dist/react/useTable.d.ts +16 -0
- package/dist/react/useTable.js +59 -0
- package/dist/react/useTables.d.ts +22 -0
- package/dist/react/useTables.js +71 -0
- package/dist/react/useUpdateRecord.d.ts +21 -0
- package/dist/react/useUpdateRecord.js +62 -0
- package/dist/react/useWorkflowStart.d.ts +33 -0
- package/dist/react/useWorkflowStart.js +155 -0
- package/dist/record-form.d.ts +30 -0
- package/dist/record-form.js +199 -0
- package/dist/schema-form.d.ts +41 -0
- package/dist/schema-form.js +200 -0
- package/dist/types.d.ts +5 -1
- package/package.json +10 -5
- package/dist/react/styles.css +0 -2401
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { parseSSEJson, readSSE } from "../streams.js";
|
|
3
3
|
import { parseAssistantStreamEvent, upsertConversationMessage } from "../assistant-events.js";
|
|
4
|
+
import { extractConversationMessageText, getLatestAssistantMessage, } from "./assistant-output.js";
|
|
4
5
|
function resolveOptionalPodId(client, podId) {
|
|
5
6
|
return podId ?? client.podId;
|
|
6
7
|
}
|
|
@@ -140,6 +141,7 @@ export function useAssistantSession(options) {
|
|
|
140
141
|
organizationId: defaultOrganizationId ?? null,
|
|
141
142
|
}), [defaultAssistantId, defaultAssistantName, defaultOrganizationId, defaultPodId]);
|
|
142
143
|
const listConversations = useCallback(async (input = {}) => {
|
|
144
|
+
setError(null);
|
|
143
145
|
try {
|
|
144
146
|
const scope = normalizeScope(client, defaultScope, input.scope);
|
|
145
147
|
applyPodScope(client, scope.podId);
|
|
@@ -168,30 +170,39 @@ export function useAssistantSession(options) {
|
|
|
168
170
|
}
|
|
169
171
|
}, [client, defaultScope]);
|
|
170
172
|
const createConversation = useCallback(async (input = {}) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
: input.model
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
173
|
+
setError(null);
|
|
174
|
+
try {
|
|
175
|
+
applyPodScope(client, input.podId ?? defaultPodId ?? null);
|
|
176
|
+
const payload = {
|
|
177
|
+
title: input.title ?? undefined,
|
|
178
|
+
pod_id: input.podId ?? defaultPodId ?? client.podId ?? undefined,
|
|
179
|
+
assistant_name: input.assistantName
|
|
180
|
+
?? input.assistantId
|
|
181
|
+
?? defaultAssistantName
|
|
182
|
+
?? defaultAssistantId
|
|
183
|
+
?? undefined,
|
|
184
|
+
organization_id: input.organizationId ?? defaultOrganizationId ?? undefined,
|
|
185
|
+
model: typeof input.model === "undefined"
|
|
186
|
+
? undefined
|
|
187
|
+
: input.model,
|
|
188
|
+
};
|
|
189
|
+
const created = await client.conversations.create(payload);
|
|
190
|
+
if (input.setActive !== false) {
|
|
191
|
+
setConversationIdState(created.id);
|
|
192
|
+
setConversation(created);
|
|
193
|
+
setConversationStatus(created.status);
|
|
194
|
+
setMessages([]);
|
|
195
|
+
clearStreamingText();
|
|
196
|
+
autoResumedKeyRef.current = null;
|
|
197
|
+
}
|
|
198
|
+
return created;
|
|
199
|
+
}
|
|
200
|
+
catch (createError) {
|
|
201
|
+
const normalized = normalizeError(createError, "Failed to create conversation.");
|
|
202
|
+
setError(normalized);
|
|
203
|
+
onErrorRef.current?.(createError);
|
|
204
|
+
throw normalized;
|
|
193
205
|
}
|
|
194
|
-
return created;
|
|
195
206
|
}, [
|
|
196
207
|
clearStreamingText,
|
|
197
208
|
client,
|
|
@@ -205,6 +216,7 @@ export function useAssistantSession(options) {
|
|
|
205
216
|
const id = explicitConversationId ?? conversationId;
|
|
206
217
|
if (!id)
|
|
207
218
|
return null;
|
|
219
|
+
setError(null);
|
|
208
220
|
try {
|
|
209
221
|
const scope = normalizeScope(client, defaultScope);
|
|
210
222
|
applyPodScope(client, scope.podId);
|
|
@@ -230,6 +242,7 @@ export function useAssistantSession(options) {
|
|
|
230
242
|
if (!id) {
|
|
231
243
|
return { items: [], limit: input.limit ?? 20, next_page_token: null };
|
|
232
244
|
}
|
|
245
|
+
setError(null);
|
|
233
246
|
try {
|
|
234
247
|
const response = await client.conversations.messages.list(id, {
|
|
235
248
|
limit: input.limit,
|
|
@@ -351,48 +364,66 @@ export function useAssistantSession(options) {
|
|
|
351
364
|
});
|
|
352
365
|
}, [conversation, conversationId, createConversation, refreshConversation]);
|
|
353
366
|
const sendMessage = useCallback(async (content, input = {}) => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
367
|
+
setError(null);
|
|
368
|
+
try {
|
|
369
|
+
const resolvedConversation = await ensureConversation(input.conversationId, input);
|
|
370
|
+
const resolvedConversationId = requireConversationId(resolvedConversation.id);
|
|
371
|
+
cancel();
|
|
372
|
+
const controller = new AbortController();
|
|
373
|
+
abortRef.current = controller;
|
|
374
|
+
const scope = normalizeScope(client, defaultScope, input.createConversation);
|
|
375
|
+
applyPodScope(client, scope.podId);
|
|
376
|
+
const stream = await client.conversations.sendMessageStream(resolvedConversationId, { content }, {
|
|
377
|
+
pod_id: scope.podId ?? undefined,
|
|
378
|
+
signal: controller.signal,
|
|
379
|
+
});
|
|
380
|
+
setConversationStatus("RUNNING");
|
|
381
|
+
await consume({
|
|
382
|
+
stream,
|
|
383
|
+
controller,
|
|
384
|
+
streamConversationId: resolvedConversationId,
|
|
385
|
+
syncAfterStream: input.syncOnTurnEnd,
|
|
386
|
+
});
|
|
387
|
+
return resolvedConversation;
|
|
388
|
+
}
|
|
389
|
+
catch (sendError) {
|
|
390
|
+
const normalized = normalizeError(sendError, "Failed to send assistant message.");
|
|
391
|
+
setError(normalized);
|
|
392
|
+
onErrorRef.current?.(sendError);
|
|
393
|
+
throw normalized;
|
|
394
|
+
}
|
|
373
395
|
}, [cancel, client, consume, defaultScope, ensureConversation, setConversationStatus]);
|
|
374
396
|
const resume = useCallback(async (input) => {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
397
|
+
setError(null);
|
|
398
|
+
try {
|
|
399
|
+
const resumeInput = resolveResumeInput(input);
|
|
400
|
+
const id = requireConversationId(resumeInput.conversationId ?? conversationId);
|
|
401
|
+
if (resumeInput.onlyIfRunning && !isConversationRunningStatus(statusRef.current)) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
cancel();
|
|
405
|
+
const controller = new AbortController();
|
|
406
|
+
abortRef.current = controller;
|
|
407
|
+
const scope = normalizeScope(client, defaultScope);
|
|
408
|
+
applyPodScope(client, scope.podId);
|
|
409
|
+
const stream = await client.conversations.resumeStream(id, {
|
|
410
|
+
pod_id: scope.podId ?? undefined,
|
|
411
|
+
signal: controller.signal,
|
|
412
|
+
});
|
|
413
|
+
setConversationStatus("RUNNING");
|
|
414
|
+
await consume({
|
|
415
|
+
stream,
|
|
416
|
+
controller,
|
|
417
|
+
streamConversationId: id,
|
|
418
|
+
syncAfterStream: resumeInput.syncOnTurnEnd,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
catch (resumeError) {
|
|
422
|
+
const normalized = normalizeError(resumeError, "Failed to resume assistant run.");
|
|
423
|
+
setError(normalized);
|
|
424
|
+
onErrorRef.current?.(resumeError);
|
|
425
|
+
throw normalized;
|
|
379
426
|
}
|
|
380
|
-
cancel();
|
|
381
|
-
const controller = new AbortController();
|
|
382
|
-
abortRef.current = controller;
|
|
383
|
-
const scope = normalizeScope(client, defaultScope);
|
|
384
|
-
applyPodScope(client, scope.podId);
|
|
385
|
-
const stream = await client.conversations.resumeStream(id, {
|
|
386
|
-
pod_id: scope.podId ?? undefined,
|
|
387
|
-
signal: controller.signal,
|
|
388
|
-
});
|
|
389
|
-
setConversationStatus("RUNNING");
|
|
390
|
-
await consume({
|
|
391
|
-
stream,
|
|
392
|
-
controller,
|
|
393
|
-
streamConversationId: id,
|
|
394
|
-
syncAfterStream: resumeInput.syncOnTurnEnd,
|
|
395
|
-
});
|
|
396
427
|
}, [cancel, client, consume, conversationId, defaultScope, setConversationStatus]);
|
|
397
428
|
const resumeIfRunning = useCallback(async (explicitConversationId) => {
|
|
398
429
|
const id = explicitConversationId ?? conversationId;
|
|
@@ -429,14 +460,23 @@ export function useAssistantSession(options) {
|
|
|
429
460
|
}
|
|
430
461
|
}, [conversationId, isStreaming, refreshConversation, resume]);
|
|
431
462
|
const stop = useCallback(async (explicitConversationId) => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
463
|
+
setError(null);
|
|
464
|
+
try {
|
|
465
|
+
const id = requireConversationId(explicitConversationId ?? conversationId);
|
|
466
|
+
const scope = normalizeScope(client, defaultScope);
|
|
467
|
+
applyPodScope(client, scope.podId);
|
|
468
|
+
await client.conversations.stopRun(id, {
|
|
469
|
+
pod_id: scope.podId ?? undefined,
|
|
470
|
+
});
|
|
471
|
+
setConversationStatus("WAITING");
|
|
472
|
+
clearStreamingText();
|
|
473
|
+
}
|
|
474
|
+
catch (stopError) {
|
|
475
|
+
const normalized = normalizeError(stopError, "Failed to stop assistant run.");
|
|
476
|
+
setError(normalized);
|
|
477
|
+
onErrorRef.current?.(stopError);
|
|
478
|
+
throw normalized;
|
|
479
|
+
}
|
|
440
480
|
}, [client, conversationId, defaultScope]);
|
|
441
481
|
const clearMessages = useCallback(() => {
|
|
442
482
|
setMessages([]);
|
|
@@ -473,11 +513,24 @@ export function useAssistantSession(options) {
|
|
|
473
513
|
cancelled = true;
|
|
474
514
|
};
|
|
475
515
|
}, [autoLoad, autoResume, conversationId, loadMessages, refreshConversation, resumeIfRunning]);
|
|
516
|
+
const latestAssistantMessage = useMemo(() => getLatestAssistantMessage(messages), [messages]);
|
|
517
|
+
const output = latestAssistantMessage?.content ?? null;
|
|
518
|
+
const latestAssistantText = latestAssistantMessage
|
|
519
|
+
? extractConversationMessageText(latestAssistantMessage.content)
|
|
520
|
+
: "";
|
|
521
|
+
const outputText = streamingText.trim() || latestAssistantText;
|
|
522
|
+
const finalOutput = !isStreaming && !isConversationRunningStatus(status) ? output : null;
|
|
523
|
+
const finalOutputText = !isStreaming && !isConversationRunningStatus(status) ? latestAssistantText : "";
|
|
476
524
|
return {
|
|
477
525
|
conversationId,
|
|
478
526
|
conversation,
|
|
479
527
|
status,
|
|
480
528
|
messages,
|
|
529
|
+
latestAssistantMessage,
|
|
530
|
+
output,
|
|
531
|
+
outputText,
|
|
532
|
+
finalOutput,
|
|
533
|
+
finalOutputText,
|
|
481
534
|
streamingText,
|
|
482
535
|
isStreaming,
|
|
483
536
|
error,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { DatastoreMessageResponse } from "../types.js";
|
|
3
|
+
export interface UseBulkRecordsOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
tableName: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
onSuccess?: (response: DatastoreMessageResponse) => void;
|
|
9
|
+
onError?: (error: unknown) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseBulkRecordsResult {
|
|
12
|
+
isSubmitting: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
lastMessage: string | null;
|
|
15
|
+
createMany: (records: Record<string, unknown>[]) => Promise<DatastoreMessageResponse | null>;
|
|
16
|
+
updateMany: (records: Record<string, unknown>[]) => Promise<DatastoreMessageResponse | null>;
|
|
17
|
+
deleteMany: (recordIds: Array<string | number>) => Promise<DatastoreMessageResponse | null>;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function useBulkRecords({ client, podId, tableName, enabled, onSuccess, onError, }: UseBulkRecordsOptions): UseBulkRecordsResult;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
function resolvePodClient(client, podId) {
|
|
3
|
+
if (!podId || podId === client.podId)
|
|
4
|
+
return client;
|
|
5
|
+
return client.withPod(podId);
|
|
6
|
+
}
|
|
7
|
+
function normalizeError(error, fallback) {
|
|
8
|
+
if (error instanceof Error)
|
|
9
|
+
return error;
|
|
10
|
+
return new Error(fallback);
|
|
11
|
+
}
|
|
12
|
+
export function useBulkRecords({ client, podId, tableName, enabled = true, onSuccess, onError, }) {
|
|
13
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [lastMessage, setLastMessage] = useState(null);
|
|
16
|
+
const trimmedTableName = tableName.trim();
|
|
17
|
+
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
18
|
+
const runBulkOperation = useCallback(async (action, fallbackError) => {
|
|
19
|
+
if (!isEnabled) {
|
|
20
|
+
const disabledError = new Error("Bulk record operations are disabled.");
|
|
21
|
+
setError(disabledError);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
setIsSubmitting(true);
|
|
25
|
+
setError(null);
|
|
26
|
+
try {
|
|
27
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
28
|
+
const response = await action(scopedClient);
|
|
29
|
+
setLastMessage(response.message ?? null);
|
|
30
|
+
onSuccess?.(response);
|
|
31
|
+
return response;
|
|
32
|
+
}
|
|
33
|
+
catch (mutationError) {
|
|
34
|
+
const normalized = normalizeError(mutationError, fallbackError);
|
|
35
|
+
setError(normalized);
|
|
36
|
+
onError?.(mutationError);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
setIsSubmitting(false);
|
|
41
|
+
}
|
|
42
|
+
}, [client, isEnabled, onError, onSuccess, podId]);
|
|
43
|
+
const createMany = useCallback(async (records) => {
|
|
44
|
+
if (records.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
return runBulkOperation((scopedClient) => scopedClient.records.bulk.create(trimmedTableName, records), "Failed to bulk create records.");
|
|
47
|
+
}, [runBulkOperation, trimmedTableName]);
|
|
48
|
+
const updateMany = useCallback(async (records) => {
|
|
49
|
+
if (records.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
return runBulkOperation((scopedClient) => scopedClient.records.bulk.update(trimmedTableName, records), "Failed to bulk update records.");
|
|
52
|
+
}, [runBulkOperation, trimmedTableName]);
|
|
53
|
+
const deleteMany = useCallback(async (recordIds) => {
|
|
54
|
+
if (recordIds.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
return runBulkOperation((scopedClient) => scopedClient.records.bulk.delete(trimmedTableName, recordIds), "Failed to bulk delete records.");
|
|
57
|
+
}, [runBulkOperation, trimmedTableName]);
|
|
58
|
+
const reset = useCallback(() => {
|
|
59
|
+
setError(null);
|
|
60
|
+
setIsSubmitting(false);
|
|
61
|
+
setLastMessage(null);
|
|
62
|
+
}, []);
|
|
63
|
+
return useMemo(() => ({
|
|
64
|
+
isSubmitting,
|
|
65
|
+
error,
|
|
66
|
+
lastMessage,
|
|
67
|
+
createMany,
|
|
68
|
+
updateMany,
|
|
69
|
+
deleteMany,
|
|
70
|
+
reset,
|
|
71
|
+
}), [createMany, deleteMany, error, isSubmitting, lastMessage, reset, updateMany]);
|
|
72
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { Conversation } from "../types.js";
|
|
3
|
+
export interface UseConversationOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
conversationId?: string | null;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseConversationResult {
|
|
11
|
+
conversation: Conversation | null;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
refresh: (overrides?: {
|
|
15
|
+
conversationId?: string | null;
|
|
16
|
+
}) => Promise<Conversation | null>;
|
|
17
|
+
}
|
|
18
|
+
export declare function useConversation({ client, podId, conversationId, enabled, autoLoad, }: UseConversationOptions): UseConversationResult;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
function normalizeError(error, fallback) {
|
|
3
|
+
if (error instanceof Error)
|
|
4
|
+
return error;
|
|
5
|
+
return new Error(fallback);
|
|
6
|
+
}
|
|
7
|
+
export function useConversation({ client, podId, conversationId = null, enabled = true, autoLoad = true, }) {
|
|
8
|
+
const [conversation, setConversation] = useState(null);
|
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const trimmedConversationId = typeof conversationId === "string" ? conversationId.trim() : "";
|
|
12
|
+
const isEnabled = enabled && trimmedConversationId.length > 0;
|
|
13
|
+
const refresh = useCallback(async (overrides = {}) => {
|
|
14
|
+
const nextConversationId = typeof overrides.conversationId === "string"
|
|
15
|
+
? overrides.conversationId.trim()
|
|
16
|
+
: trimmedConversationId;
|
|
17
|
+
if (!enabled || nextConversationId.length === 0) {
|
|
18
|
+
setConversation(null);
|
|
19
|
+
setError(null);
|
|
20
|
+
setIsLoading(false);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
try {
|
|
26
|
+
const scopedClient = podId ? client.withPod(podId) : client;
|
|
27
|
+
const nextConversation = await scopedClient.conversations.get(nextConversationId, {
|
|
28
|
+
pod_id: podId,
|
|
29
|
+
});
|
|
30
|
+
setConversation(nextConversation);
|
|
31
|
+
return nextConversation;
|
|
32
|
+
}
|
|
33
|
+
catch (refreshError) {
|
|
34
|
+
const normalized = normalizeError(refreshError, "Failed to load conversation.");
|
|
35
|
+
setError(normalized);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
setIsLoading(false);
|
|
40
|
+
}
|
|
41
|
+
}, [client, enabled, podId, trimmedConversationId]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!isEnabled) {
|
|
44
|
+
setConversation(null);
|
|
45
|
+
setError(null);
|
|
46
|
+
setIsLoading(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!autoLoad)
|
|
50
|
+
return;
|
|
51
|
+
void refresh();
|
|
52
|
+
}, [autoLoad, isEnabled, refresh]);
|
|
53
|
+
return useMemo(() => ({
|
|
54
|
+
conversation,
|
|
55
|
+
isLoading,
|
|
56
|
+
error,
|
|
57
|
+
refresh,
|
|
58
|
+
}), [conversation, error, isLoading, refresh]);
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { SseRawEvent } from "../streams.js";
|
|
3
|
+
import type { Conversation, ConversationMessage } from "../types.js";
|
|
4
|
+
import { type CreateConversationInput, type ResumeAssistantOptions, type SendAssistantMessageOptions } from "./useAssistantSession.js";
|
|
5
|
+
export interface UseConversationMessagesOptions {
|
|
6
|
+
client: LemmaClient;
|
|
7
|
+
podId?: string;
|
|
8
|
+
assistantName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated Use assistantName instead.
|
|
11
|
+
*/
|
|
12
|
+
assistantId?: string;
|
|
13
|
+
organizationId?: string;
|
|
14
|
+
conversationId?: string | null;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
autoLoad?: boolean;
|
|
17
|
+
autoResume?: boolean;
|
|
18
|
+
limit?: number;
|
|
19
|
+
syncOnTurnEnd?: boolean;
|
|
20
|
+
onEvent?: (event: SseRawEvent, payload: unknown | null) => void;
|
|
21
|
+
onStatus?: (status: string) => void;
|
|
22
|
+
onMessage?: (message: ConversationMessage) => void;
|
|
23
|
+
onError?: (error: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface UseConversationMessagesResult {
|
|
26
|
+
conversationId: string | null;
|
|
27
|
+
conversation: Conversation | null;
|
|
28
|
+
messages: ConversationMessage[];
|
|
29
|
+
status?: string;
|
|
30
|
+
isRunning: boolean;
|
|
31
|
+
isStreaming: boolean;
|
|
32
|
+
isLoading: boolean;
|
|
33
|
+
isLoadingOlder: boolean;
|
|
34
|
+
hasOlderMessages: boolean;
|
|
35
|
+
nextPageToken: string | null;
|
|
36
|
+
streamingText: string;
|
|
37
|
+
latestAssistantMessage: ConversationMessage | null;
|
|
38
|
+
output: ConversationMessage["content"] | null;
|
|
39
|
+
outputText: string;
|
|
40
|
+
finalOutput: ConversationMessage["content"] | null;
|
|
41
|
+
finalOutputText: string;
|
|
42
|
+
error: Error | null;
|
|
43
|
+
refresh: (options?: {
|
|
44
|
+
conversationId?: string | null;
|
|
45
|
+
limit?: number;
|
|
46
|
+
pageToken?: string;
|
|
47
|
+
}) => Promise<ConversationMessage[]>;
|
|
48
|
+
loadOlder: (options?: {
|
|
49
|
+
limit?: number;
|
|
50
|
+
}) => Promise<ConversationMessage[]>;
|
|
51
|
+
sendMessage: (content: string, options?: SendAssistantMessageOptions) => Promise<Conversation>;
|
|
52
|
+
resume: (conversationId?: string | null | ResumeAssistantOptions) => Promise<void>;
|
|
53
|
+
resumeIfRunning: (conversationId?: string | null) => Promise<boolean>;
|
|
54
|
+
stop: (conversationId?: string | null) => Promise<void>;
|
|
55
|
+
cancel: () => void;
|
|
56
|
+
clearMessages: () => void;
|
|
57
|
+
createConversation: (input?: CreateConversationInput) => Promise<Conversation>;
|
|
58
|
+
}
|
|
59
|
+
export declare function useConversationMessages({ client, podId, assistantName, assistantId, organizationId, conversationId, enabled, autoLoad, autoResume, limit, syncOnTurnEnd, onEvent, onStatus, onMessage, onError, }: UseConversationMessagesOptions): UseConversationMessagesResult;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { extractConversationMessageText, getLatestAssistantMessage, isConversationRunningStatus, normalizeConversationStatus, sortConversationMessagesByCreatedAt, } from "./assistant-output.js";
|
|
3
|
+
import { useAssistantSession, } from "./useAssistantSession.js";
|
|
4
|
+
function resolveConversationId(preferred, fallback) {
|
|
5
|
+
return preferred ?? fallback ?? null;
|
|
6
|
+
}
|
|
7
|
+
function isSettledStatus(status, isStreaming) {
|
|
8
|
+
if (isStreaming)
|
|
9
|
+
return false;
|
|
10
|
+
const normalized = normalizeConversationStatus(status);
|
|
11
|
+
if (!normalized)
|
|
12
|
+
return true;
|
|
13
|
+
return !isConversationRunningStatus(normalized);
|
|
14
|
+
}
|
|
15
|
+
export function useConversationMessages({ client, podId, assistantName, assistantId, organizationId, conversationId = null, enabled = true, autoLoad = true, autoResume = false, limit = 100, syncOnTurnEnd = false, onEvent, onStatus, onMessage, onError, }) {
|
|
16
|
+
const [nextPageToken, setNextPageToken] = useState(null);
|
|
17
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
18
|
+
const [isLoadingOlder, setIsLoadingOlder] = useState(false);
|
|
19
|
+
const { conversation: sessionConversation, conversationId: sessionConversationId, messages: sessionMessages, status, streamingText, isStreaming, error, refreshConversation, loadMessages, sendMessage, resume, resumeIfRunning, stop, cancel, clearMessages: clearSessionMessages, createConversation, } = useAssistantSession({
|
|
20
|
+
client,
|
|
21
|
+
podId,
|
|
22
|
+
assistantName,
|
|
23
|
+
assistantId,
|
|
24
|
+
organizationId,
|
|
25
|
+
conversationId: conversationId ?? undefined,
|
|
26
|
+
autoLoad: false,
|
|
27
|
+
autoResume: false,
|
|
28
|
+
syncOnTurnEnd,
|
|
29
|
+
onEvent,
|
|
30
|
+
onStatus,
|
|
31
|
+
onMessage,
|
|
32
|
+
onError,
|
|
33
|
+
});
|
|
34
|
+
const refresh = useCallback(async (options = {}) => {
|
|
35
|
+
const targetConversationId = resolveConversationId(options.conversationId, sessionConversationId);
|
|
36
|
+
if (!enabled || !targetConversationId) {
|
|
37
|
+
setNextPageToken(null);
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
setIsLoading(true);
|
|
42
|
+
try {
|
|
43
|
+
await refreshConversation(targetConversationId);
|
|
44
|
+
const response = await loadMessages({
|
|
45
|
+
conversationId: targetConversationId,
|
|
46
|
+
limit: options.limit ?? limit,
|
|
47
|
+
pageToken: options.pageToken,
|
|
48
|
+
});
|
|
49
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
50
|
+
return sortConversationMessagesByCreatedAt(response.items ?? []);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}, [enabled, limit, loadMessages, refreshConversation, sessionConversationId]);
|
|
56
|
+
const loadOlder = useCallback(async (options = {}) => {
|
|
57
|
+
const targetConversationId = sessionConversationId;
|
|
58
|
+
if (!enabled || !targetConversationId || !nextPageToken || isLoading || isLoadingOlder) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
setIsLoadingOlder(true);
|
|
62
|
+
try {
|
|
63
|
+
const response = await loadMessages({
|
|
64
|
+
conversationId: targetConversationId,
|
|
65
|
+
limit: options.limit ?? limit,
|
|
66
|
+
pageToken: nextPageToken,
|
|
67
|
+
});
|
|
68
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
69
|
+
return sortConversationMessagesByCreatedAt(response.items ?? []);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setIsLoadingOlder(false);
|
|
73
|
+
}
|
|
74
|
+
}, [enabled, isLoading, isLoadingOlder, limit, loadMessages, nextPageToken, sessionConversationId]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!enabled || !conversationId) {
|
|
77
|
+
setNextPageToken(null);
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
setIsLoadingOlder(false);
|
|
80
|
+
cancel();
|
|
81
|
+
clearSessionMessages();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setNextPageToken(null);
|
|
85
|
+
if (!autoLoad)
|
|
86
|
+
return;
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
const bootstrap = async () => {
|
|
89
|
+
await refresh({ conversationId, limit });
|
|
90
|
+
if (cancelled || !autoResume)
|
|
91
|
+
return;
|
|
92
|
+
await resumeIfRunning(conversationId);
|
|
93
|
+
};
|
|
94
|
+
void bootstrap();
|
|
95
|
+
return () => {
|
|
96
|
+
cancelled = true;
|
|
97
|
+
};
|
|
98
|
+
}, [autoLoad, autoResume, cancel, clearSessionMessages, conversationId, enabled, limit, refresh, resumeIfRunning]);
|
|
99
|
+
const messages = useMemo(() => sortConversationMessagesByCreatedAt(sessionMessages), [sessionMessages]);
|
|
100
|
+
const latestAssistantMessage = useMemo(() => getLatestAssistantMessage(messages), [messages]);
|
|
101
|
+
const output = latestAssistantMessage?.content ?? null;
|
|
102
|
+
const latestAssistantText = latestAssistantMessage
|
|
103
|
+
? extractConversationMessageText(latestAssistantMessage.content)
|
|
104
|
+
: "";
|
|
105
|
+
const outputText = streamingText.trim() || latestAssistantText;
|
|
106
|
+
const finalOutput = isSettledStatus(status, isStreaming) ? output : null;
|
|
107
|
+
const finalOutputText = isSettledStatus(status, isStreaming) ? latestAssistantText : "";
|
|
108
|
+
const isRunning = isConversationRunningStatus(status) || isStreaming;
|
|
109
|
+
const clearMessages = useCallback(() => {
|
|
110
|
+
clearSessionMessages();
|
|
111
|
+
setNextPageToken(null);
|
|
112
|
+
}, [clearSessionMessages]);
|
|
113
|
+
return useMemo(() => ({
|
|
114
|
+
conversationId: sessionConversationId,
|
|
115
|
+
conversation: sessionConversation,
|
|
116
|
+
messages,
|
|
117
|
+
status,
|
|
118
|
+
isRunning,
|
|
119
|
+
isStreaming,
|
|
120
|
+
isLoading,
|
|
121
|
+
isLoadingOlder,
|
|
122
|
+
hasOlderMessages: !!nextPageToken,
|
|
123
|
+
nextPageToken,
|
|
124
|
+
streamingText,
|
|
125
|
+
latestAssistantMessage,
|
|
126
|
+
output,
|
|
127
|
+
outputText,
|
|
128
|
+
finalOutput,
|
|
129
|
+
finalOutputText,
|
|
130
|
+
error,
|
|
131
|
+
refresh,
|
|
132
|
+
loadOlder,
|
|
133
|
+
sendMessage,
|
|
134
|
+
resume,
|
|
135
|
+
resumeIfRunning,
|
|
136
|
+
stop,
|
|
137
|
+
cancel,
|
|
138
|
+
clearMessages,
|
|
139
|
+
createConversation,
|
|
140
|
+
}), [
|
|
141
|
+
cancel,
|
|
142
|
+
clearMessages,
|
|
143
|
+
createConversation,
|
|
144
|
+
error,
|
|
145
|
+
finalOutput,
|
|
146
|
+
finalOutputText,
|
|
147
|
+
isLoading,
|
|
148
|
+
isLoadingOlder,
|
|
149
|
+
isRunning,
|
|
150
|
+
isStreaming,
|
|
151
|
+
latestAssistantMessage,
|
|
152
|
+
loadOlder,
|
|
153
|
+
messages,
|
|
154
|
+
nextPageToken,
|
|
155
|
+
output,
|
|
156
|
+
outputText,
|
|
157
|
+
refresh,
|
|
158
|
+
resume,
|
|
159
|
+
resumeIfRunning,
|
|
160
|
+
sendMessage,
|
|
161
|
+
sessionConversation,
|
|
162
|
+
sessionConversationId,
|
|
163
|
+
status,
|
|
164
|
+
stop,
|
|
165
|
+
streamingText,
|
|
166
|
+
]);
|
|
167
|
+
}
|