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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
3
|
+
export function useAgentRuns({ client, podId, agentName, enabled = true, autoLoad = true, limit = 100, pageToken, initialTaskId = null, }) {
|
|
4
|
+
const [runs, setRuns] = useState([]);
|
|
5
|
+
const [total, setTotal] = useState(0);
|
|
6
|
+
const [nextPageToken, setNextPageToken] = useState(null);
|
|
7
|
+
const [selectedTaskId, setSelectedTaskId] = useState(initialTaskId);
|
|
8
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
9
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const refresh = useCallback(async (overrides = {}, signal) => {
|
|
12
|
+
if (!enabled) {
|
|
13
|
+
setRuns([]);
|
|
14
|
+
setTotal(0);
|
|
15
|
+
setNextPageToken(null);
|
|
16
|
+
setError(null);
|
|
17
|
+
setIsLoading(false);
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
24
|
+
const response = await scopedClient.tasks.list({
|
|
25
|
+
agent_name: agentName,
|
|
26
|
+
limit: overrides.limit ?? limit,
|
|
27
|
+
page_token: overrides.pageToken ?? pageToken,
|
|
28
|
+
});
|
|
29
|
+
if (signal?.aborted)
|
|
30
|
+
return [];
|
|
31
|
+
const nextRuns = response.items ?? [];
|
|
32
|
+
setRuns(nextRuns);
|
|
33
|
+
setTotal(response.total ?? nextRuns.length);
|
|
34
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
35
|
+
setSelectedTaskId((current) => (current && nextRuns.some((run) => run.id === current) ? current : initialTaskId));
|
|
36
|
+
return nextRuns;
|
|
37
|
+
}
|
|
38
|
+
catch (refreshError) {
|
|
39
|
+
if (signal?.aborted)
|
|
40
|
+
return [];
|
|
41
|
+
const normalized = normalizeError(refreshError, "Failed to load agent runs.");
|
|
42
|
+
setError(normalized);
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (!signal?.aborted)
|
|
47
|
+
setIsLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}, [agentName, client, enabled, initialTaskId, limit, pageToken, podId]);
|
|
50
|
+
const loadMore = useCallback(async (overrides = {}) => {
|
|
51
|
+
if (!enabled || !nextPageToken || isLoading || isLoadingMore) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
setIsLoadingMore(true);
|
|
55
|
+
setError(null);
|
|
56
|
+
try {
|
|
57
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
58
|
+
const response = await scopedClient.tasks.list({
|
|
59
|
+
agent_name: agentName,
|
|
60
|
+
limit: overrides.limit ?? limit,
|
|
61
|
+
page_token: nextPageToken,
|
|
62
|
+
});
|
|
63
|
+
const moreRuns = response.items ?? [];
|
|
64
|
+
setRuns((previous) => [...previous, ...moreRuns]);
|
|
65
|
+
setTotal(response.total ?? runs.length + moreRuns.length);
|
|
66
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
67
|
+
return moreRuns;
|
|
68
|
+
}
|
|
69
|
+
catch (loadError) {
|
|
70
|
+
const normalized = normalizeError(loadError, "Failed to load more agent runs.");
|
|
71
|
+
setError(normalized);
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
setIsLoadingMore(false);
|
|
76
|
+
}
|
|
77
|
+
}, [agentName, client, enabled, isLoading, isLoadingMore, limit, nextPageToken, podId]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setSelectedTaskId(initialTaskId);
|
|
80
|
+
}, [initialTaskId]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!enabled) {
|
|
83
|
+
setRuns([]);
|
|
84
|
+
setTotal(0);
|
|
85
|
+
setNextPageToken(null);
|
|
86
|
+
setError(null);
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
setIsLoadingMore(false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!autoLoad)
|
|
92
|
+
return;
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
let cancelled = false;
|
|
95
|
+
(async () => {
|
|
96
|
+
try {
|
|
97
|
+
await refresh({}, controller.signal);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
if (!cancelled) {
|
|
101
|
+
setError(normalizeError(new Error("Failed to load agent runs."), "Failed to load agent runs."));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
controller.abort();
|
|
108
|
+
};
|
|
109
|
+
}, [autoLoad, enabled, refresh]);
|
|
110
|
+
const selectRun = useCallback((taskId) => {
|
|
111
|
+
setSelectedTaskId(taskId);
|
|
112
|
+
}, []);
|
|
113
|
+
const clearSelection = useCallback(() => {
|
|
114
|
+
setSelectedTaskId(null);
|
|
115
|
+
}, []);
|
|
116
|
+
return useMemo(() => {
|
|
117
|
+
const effectiveSelectedTaskId = selectedTaskId ?? runs[0]?.id ?? null;
|
|
118
|
+
const selectedRun = effectiveSelectedTaskId
|
|
119
|
+
? runs.find((run) => run.id === effectiveSelectedTaskId) ?? null
|
|
120
|
+
: null;
|
|
121
|
+
return {
|
|
122
|
+
runs,
|
|
123
|
+
total,
|
|
124
|
+
nextPageToken,
|
|
125
|
+
selectedTaskId,
|
|
126
|
+
effectiveSelectedTaskId,
|
|
127
|
+
selectedRun,
|
|
128
|
+
isLoading,
|
|
129
|
+
isLoadingMore,
|
|
130
|
+
error,
|
|
131
|
+
selectRun,
|
|
132
|
+
clearSelection,
|
|
133
|
+
refresh,
|
|
134
|
+
loadMore,
|
|
135
|
+
};
|
|
136
|
+
}, [
|
|
137
|
+
clearSelection,
|
|
138
|
+
error,
|
|
139
|
+
isLoading,
|
|
140
|
+
isLoadingMore,
|
|
141
|
+
loadMore,
|
|
142
|
+
nextPageToken,
|
|
143
|
+
refresh,
|
|
144
|
+
runs,
|
|
145
|
+
selectRun,
|
|
146
|
+
selectedTaskId,
|
|
147
|
+
total,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
1
2
|
import { useConversationMessages } from "./useConversationMessages.js";
|
|
2
3
|
function requireConversationId(conversationId) {
|
|
3
4
|
if (!conversationId) {
|
|
@@ -15,19 +16,19 @@ export function useAssistantRun({ client, podId, conversationId, onEvent, onErro
|
|
|
15
16
|
onEvent,
|
|
16
17
|
onError,
|
|
17
18
|
});
|
|
18
|
-
const sendMessage = async (content) => {
|
|
19
|
+
const sendMessage = useCallback(async (content) => {
|
|
19
20
|
await messages.sendMessage(content, {
|
|
20
21
|
conversationId: requireConversationId(conversationId ?? messages.conversationId),
|
|
21
22
|
createIfMissing: false,
|
|
22
23
|
});
|
|
23
|
-
};
|
|
24
|
-
const resume = async () => {
|
|
24
|
+
}, [conversationId, messages]);
|
|
25
|
+
const resume = useCallback(async () => {
|
|
25
26
|
await messages.resume(requireConversationId(conversationId ?? messages.conversationId));
|
|
26
|
-
};
|
|
27
|
-
const stop = async () => {
|
|
27
|
+
}, [conversationId, messages]);
|
|
28
|
+
const stop = useCallback(async () => {
|
|
28
29
|
await messages.stop(requireConversationId(conversationId ?? messages.conversationId));
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
30
|
+
}, [conversationId, messages]);
|
|
31
|
+
return useMemo(() => ({
|
|
31
32
|
isStreaming: messages.isStreaming,
|
|
32
33
|
error: messages.error,
|
|
33
34
|
status: messages.status,
|
|
@@ -37,10 +38,10 @@ export function useAssistantRun({ client, podId, conversationId, onEvent, onErro
|
|
|
37
38
|
finalOutput: messages.finalOutput,
|
|
38
39
|
finalOutputText: messages.finalOutputText,
|
|
39
40
|
latestAssistantMessage: messages.latestAssistantMessage,
|
|
40
|
-
refresh:
|
|
41
|
+
refresh: messages.refresh,
|
|
41
42
|
sendMessage,
|
|
42
43
|
resume,
|
|
43
44
|
stop,
|
|
44
45
|
cancel: messages.cancel,
|
|
45
|
-
};
|
|
46
|
+
}), [messages, sendMessage, resume, stop]);
|
|
46
47
|
}
|
|
@@ -2,15 +2,13 @@ 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
4
|
import { extractConversationMessageText, getLatestAssistantMessage, } from "./assistant-output.js";
|
|
5
|
-
|
|
6
|
-
return podId ?? client.podId;
|
|
7
|
-
}
|
|
5
|
+
import { normalizeError } from "./utils.js";
|
|
8
6
|
function applyPodScope(client, podId) {
|
|
9
|
-
const resolvedPodId =
|
|
10
|
-
if (resolvedPodId) {
|
|
11
|
-
client.
|
|
7
|
+
const resolvedPodId = podId ?? client.podId;
|
|
8
|
+
if (resolvedPodId && resolvedPodId !== client.podId) {
|
|
9
|
+
return client.withPod(resolvedPodId);
|
|
12
10
|
}
|
|
13
|
-
return
|
|
11
|
+
return client;
|
|
14
12
|
}
|
|
15
13
|
function requireConversationId(conversationId) {
|
|
16
14
|
if (!conversationId) {
|
|
@@ -18,11 +16,6 @@ function requireConversationId(conversationId) {
|
|
|
18
16
|
}
|
|
19
17
|
return conversationId;
|
|
20
18
|
}
|
|
21
|
-
function normalizeError(error, fallback) {
|
|
22
|
-
if (error instanceof Error)
|
|
23
|
-
return error;
|
|
24
|
-
return new Error(fallback);
|
|
25
|
-
}
|
|
26
19
|
function normalizeScope(client, defaults, override) {
|
|
27
20
|
const resolvedAssistantName = override?.assistantName
|
|
28
21
|
?? override?.assistantId
|
|
@@ -144,8 +137,8 @@ export function useAssistantSession(options) {
|
|
|
144
137
|
setError(null);
|
|
145
138
|
try {
|
|
146
139
|
const scope = normalizeScope(client, defaultScope, input.scope);
|
|
147
|
-
applyPodScope(client, scope.podId);
|
|
148
|
-
const response = await
|
|
140
|
+
const scopedClient = applyPodScope(client, scope.podId);
|
|
141
|
+
const response = await scopedClient.conversations.list({
|
|
149
142
|
pod_id: scope.podId ?? undefined,
|
|
150
143
|
assistant_name: scope.assistantName ?? scope.assistantId ?? undefined,
|
|
151
144
|
organization_id: scope.organizationId ?? undefined,
|
|
@@ -156,6 +149,7 @@ export function useAssistantSession(options) {
|
|
|
156
149
|
items: response.items ?? [],
|
|
157
150
|
limit: response.limit ?? input.limit ?? 20,
|
|
158
151
|
next_page_token: response.next_page_token,
|
|
152
|
+
total: response.total,
|
|
159
153
|
};
|
|
160
154
|
}
|
|
161
155
|
catch (listError) {
|
|
@@ -172,10 +166,10 @@ export function useAssistantSession(options) {
|
|
|
172
166
|
const createConversation = useCallback(async (input = {}) => {
|
|
173
167
|
setError(null);
|
|
174
168
|
try {
|
|
175
|
-
applyPodScope(client, input.podId ?? defaultPodId ?? null);
|
|
169
|
+
const scopedClient = applyPodScope(client, input.podId ?? defaultPodId ?? null);
|
|
176
170
|
const payload = {
|
|
177
171
|
title: input.title ?? undefined,
|
|
178
|
-
pod_id: input.podId ?? defaultPodId ??
|
|
172
|
+
pod_id: input.podId ?? defaultPodId ?? scopedClient.podId ?? undefined,
|
|
179
173
|
assistant_name: input.assistantName
|
|
180
174
|
?? input.assistantId
|
|
181
175
|
?? defaultAssistantName
|
|
@@ -186,7 +180,7 @@ export function useAssistantSession(options) {
|
|
|
186
180
|
? undefined
|
|
187
181
|
: input.model,
|
|
188
182
|
};
|
|
189
|
-
const created = await
|
|
183
|
+
const created = await scopedClient.conversations.create(payload);
|
|
190
184
|
if (input.setActive !== false) {
|
|
191
185
|
setConversationIdState(created.id);
|
|
192
186
|
setConversation(created);
|
|
@@ -219,8 +213,8 @@ export function useAssistantSession(options) {
|
|
|
219
213
|
setError(null);
|
|
220
214
|
try {
|
|
221
215
|
const scope = normalizeScope(client, defaultScope);
|
|
222
|
-
applyPodScope(client, scope.podId);
|
|
223
|
-
const nextConversation = await
|
|
216
|
+
const scopedClient = applyPodScope(client, scope.podId);
|
|
217
|
+
const nextConversation = await scopedClient.conversations.get(id, {
|
|
224
218
|
pod_id: scope.podId ?? undefined,
|
|
225
219
|
});
|
|
226
220
|
setConversation(nextConversation);
|
|
@@ -372,8 +366,8 @@ export function useAssistantSession(options) {
|
|
|
372
366
|
const controller = new AbortController();
|
|
373
367
|
abortRef.current = controller;
|
|
374
368
|
const scope = normalizeScope(client, defaultScope, input.createConversation);
|
|
375
|
-
applyPodScope(client, scope.podId);
|
|
376
|
-
const stream = await
|
|
369
|
+
const scopedClient = applyPodScope(client, scope.podId);
|
|
370
|
+
const stream = await scopedClient.conversations.sendMessageStream(resolvedConversationId, { content }, {
|
|
377
371
|
pod_id: scope.podId ?? undefined,
|
|
378
372
|
signal: controller.signal,
|
|
379
373
|
});
|
|
@@ -405,8 +399,8 @@ export function useAssistantSession(options) {
|
|
|
405
399
|
const controller = new AbortController();
|
|
406
400
|
abortRef.current = controller;
|
|
407
401
|
const scope = normalizeScope(client, defaultScope);
|
|
408
|
-
applyPodScope(client, scope.podId);
|
|
409
|
-
const stream = await
|
|
402
|
+
const scopedClient = applyPodScope(client, scope.podId);
|
|
403
|
+
const stream = await scopedClient.conversations.resumeStream(id, {
|
|
410
404
|
pod_id: scope.podId ?? undefined,
|
|
411
405
|
signal: controller.signal,
|
|
412
406
|
});
|
|
@@ -464,8 +458,8 @@ export function useAssistantSession(options) {
|
|
|
464
458
|
try {
|
|
465
459
|
const id = requireConversationId(explicitConversationId ?? conversationId);
|
|
466
460
|
const scope = normalizeScope(client, defaultScope);
|
|
467
|
-
applyPodScope(client, scope.podId);
|
|
468
|
-
await
|
|
461
|
+
const scopedClient = applyPodScope(client, scope.podId);
|
|
462
|
+
await scopedClient.conversations.stopRun(id, {
|
|
469
463
|
pod_id: scope.podId ?? undefined,
|
|
470
464
|
});
|
|
471
465
|
setConversationStatus("WAITING");
|
|
@@ -493,6 +487,7 @@ export function useAssistantSession(options) {
|
|
|
493
487
|
if (!autoLoad || !conversationId) {
|
|
494
488
|
return;
|
|
495
489
|
}
|
|
490
|
+
const controller = new AbortController();
|
|
496
491
|
let cancelled = false;
|
|
497
492
|
const bootstrapConversation = async () => {
|
|
498
493
|
const latestConversation = await refreshConversation(conversationId);
|
|
@@ -511,6 +506,7 @@ export function useAssistantSession(options) {
|
|
|
511
506
|
void bootstrapConversation();
|
|
512
507
|
return () => {
|
|
513
508
|
cancelled = true;
|
|
509
|
+
controller.abort();
|
|
514
510
|
};
|
|
515
511
|
}, [autoLoad, autoResume, conversationId, loadMessages, refreshConversation, resumeIfRunning]);
|
|
516
512
|
const latestAssistantMessage = useMemo(() => getLatestAssistantMessage(messages), [messages]);
|
|
@@ -1,24 +1,17 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from "react";
|
|
2
|
-
|
|
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
|
-
}
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
12
3
|
export function useBulkRecords({ client, podId, tableName, enabled = true, onSuccess, onError, }) {
|
|
13
4
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
14
5
|
const [error, setError] = useState(null);
|
|
15
6
|
const [lastMessage, setLastMessage] = useState(null);
|
|
7
|
+
const onSuccessRef = useRef(onSuccess);
|
|
8
|
+
const onErrorRef = useRef(onError);
|
|
9
|
+
useEffect(() => { onSuccessRef.current = onSuccess; }, [onSuccess]);
|
|
10
|
+
useEffect(() => { onErrorRef.current = onError; }, [onError]);
|
|
16
11
|
const trimmedTableName = tableName.trim();
|
|
17
12
|
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
18
13
|
const runBulkOperation = useCallback(async (action, fallbackError) => {
|
|
19
14
|
if (!isEnabled) {
|
|
20
|
-
const disabledError = new Error("Bulk record operations are disabled.");
|
|
21
|
-
setError(disabledError);
|
|
22
15
|
return null;
|
|
23
16
|
}
|
|
24
17
|
setIsSubmitting(true);
|
|
@@ -27,19 +20,19 @@ export function useBulkRecords({ client, podId, tableName, enabled = true, onSuc
|
|
|
27
20
|
const scopedClient = resolvePodClient(client, podId);
|
|
28
21
|
const response = await action(scopedClient);
|
|
29
22
|
setLastMessage(response.message ?? null);
|
|
30
|
-
|
|
23
|
+
onSuccessRef.current?.(response);
|
|
31
24
|
return response;
|
|
32
25
|
}
|
|
33
26
|
catch (mutationError) {
|
|
34
27
|
const normalized = normalizeError(mutationError, fallbackError);
|
|
35
28
|
setError(normalized);
|
|
36
|
-
|
|
29
|
+
onErrorRef.current?.(mutationError);
|
|
37
30
|
return null;
|
|
38
31
|
}
|
|
39
32
|
finally {
|
|
40
33
|
setIsSubmitting(false);
|
|
41
34
|
}
|
|
42
|
-
}, [client, isEnabled,
|
|
35
|
+
}, [client, isEnabled, podId]);
|
|
43
36
|
const createMany = useCallback(async (records) => {
|
|
44
37
|
if (records.length === 0)
|
|
45
38
|
return null;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
|
|
3
|
-
if (error instanceof Error)
|
|
4
|
-
return error;
|
|
5
|
-
return new Error(fallback);
|
|
6
|
-
}
|
|
2
|
+
import { normalizeError } from "./utils.js";
|
|
7
3
|
export function useConversation({ client, podId, conversationId = null, enabled = true, autoLoad = true, }) {
|
|
8
4
|
const [conversation, setConversation] = useState(null);
|
|
9
5
|
const [isLoading, setIsLoading] = useState(false);
|
|
10
6
|
const [error, setError] = useState(null);
|
|
11
7
|
const trimmedConversationId = typeof conversationId === "string" ? conversationId.trim() : "";
|
|
12
8
|
const isEnabled = enabled && trimmedConversationId.length > 0;
|
|
13
|
-
const refresh = useCallback(async (overrides = {}) => {
|
|
9
|
+
const refresh = useCallback(async (overrides = {}, signal) => {
|
|
14
10
|
const nextConversationId = typeof overrides.conversationId === "string"
|
|
15
11
|
? overrides.conversationId.trim()
|
|
16
12
|
: trimmedConversationId;
|
|
@@ -27,16 +23,21 @@ export function useConversation({ client, podId, conversationId = null, enabled
|
|
|
27
23
|
const nextConversation = await scopedClient.conversations.get(nextConversationId, {
|
|
28
24
|
pod_id: podId,
|
|
29
25
|
});
|
|
26
|
+
if (signal?.aborted)
|
|
27
|
+
return null;
|
|
30
28
|
setConversation(nextConversation);
|
|
31
29
|
return nextConversation;
|
|
32
30
|
}
|
|
33
31
|
catch (refreshError) {
|
|
32
|
+
if (signal?.aborted)
|
|
33
|
+
return null;
|
|
34
34
|
const normalized = normalizeError(refreshError, "Failed to load conversation.");
|
|
35
35
|
setError(normalized);
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
finally {
|
|
39
|
-
|
|
39
|
+
if (!signal?.aborted)
|
|
40
|
+
setIsLoading(false);
|
|
40
41
|
}
|
|
41
42
|
}, [client, enabled, podId, trimmedConversationId]);
|
|
42
43
|
useEffect(() => {
|
|
@@ -48,7 +49,22 @@ export function useConversation({ client, podId, conversationId = null, enabled
|
|
|
48
49
|
}
|
|
49
50
|
if (!autoLoad)
|
|
50
51
|
return;
|
|
51
|
-
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await refresh({}, controller.signal);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
if (!cancelled) {
|
|
60
|
+
setError(normalizeError(new Error("Failed to load conversation."), "Failed to load conversation."));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return () => {
|
|
65
|
+
cancelled = true;
|
|
66
|
+
controller.abort();
|
|
67
|
+
};
|
|
52
68
|
}, [autoLoad, isEnabled, refresh]);
|
|
53
69
|
return useMemo(() => ({
|
|
54
70
|
conversation,
|
|
@@ -33,6 +33,7 @@ export interface UseConversationsResult {
|
|
|
33
33
|
effectiveSelectedConversationId: string | null;
|
|
34
34
|
selectedConversation: Conversation | null;
|
|
35
35
|
isLoading: boolean;
|
|
36
|
+
isLoadingMore: boolean;
|
|
36
37
|
error: Error | null;
|
|
37
38
|
selectConversation: (conversationId: string | null) => void;
|
|
38
39
|
clearSelection: () => void;
|
|
@@ -41,6 +42,9 @@ export interface UseConversationsResult {
|
|
|
41
42
|
limit?: number;
|
|
42
43
|
pageToken?: string;
|
|
43
44
|
}) => Promise<Conversation[]>;
|
|
45
|
+
loadMore: (overrides?: {
|
|
46
|
+
limit?: number;
|
|
47
|
+
}) => Promise<Conversation[]>;
|
|
44
48
|
createConversation: (input?: CreateConversationInput) => Promise<Conversation>;
|
|
45
49
|
createAndSelectConversation: (input?: Omit<CreateConversationInput, "setActive">) => Promise<Conversation>;
|
|
46
50
|
ensureConversation: (input?: Omit<CreateConversationInput, "setActive">) => Promise<Conversation>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { useAssistantSession, } from "./useAssistantSession.js";
|
|
3
|
+
import { normalizeError } from "./utils.js";
|
|
3
4
|
function sortConversationsByUpdatedAt(conversations) {
|
|
4
5
|
return [...conversations].sort((a, b) => {
|
|
5
6
|
const aTime = new Date(a.updated_at || a.created_at).getTime();
|
|
@@ -13,6 +14,7 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
13
14
|
const [nextPageToken, setNextPageToken] = useState(null);
|
|
14
15
|
const [selectedConversationId, setSelectedConversationId] = useState(initialConversationId);
|
|
15
16
|
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
16
18
|
const scopeKey = useMemo(() => JSON.stringify({
|
|
17
19
|
podId: podId ?? null,
|
|
18
20
|
assistantName: assistantName ?? assistantId ?? null,
|
|
@@ -43,7 +45,7 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
43
45
|
});
|
|
44
46
|
const nextConversations = sortConversationsByUpdatedAt(response.items ?? []);
|
|
45
47
|
setConversations(nextConversations);
|
|
46
|
-
setTotal(nextConversations.length);
|
|
48
|
+
setTotal(response.total ?? nextConversations.length);
|
|
47
49
|
setNextPageToken(response.next_page_token ?? null);
|
|
48
50
|
setSelectedConversationId((current) => {
|
|
49
51
|
if (current)
|
|
@@ -60,6 +62,30 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
60
62
|
setIsLoading(false);
|
|
61
63
|
}
|
|
62
64
|
}, [autoSelectFirst, enabled, initialConversationId, limit, listConversations, pageToken]);
|
|
65
|
+
const loadMore = useCallback(async (overrides = {}) => {
|
|
66
|
+
if (!enabled || !nextPageToken || isLoading || isLoadingMore) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
setIsLoadingMore(true);
|
|
70
|
+
try {
|
|
71
|
+
const response = await listConversations({
|
|
72
|
+
limit: overrides.limit ?? limit,
|
|
73
|
+
pageToken: nextPageToken,
|
|
74
|
+
});
|
|
75
|
+
const moreConversations = sortConversationsByUpdatedAt(response.items ?? []);
|
|
76
|
+
setConversations((previous) => [...previous, ...moreConversations]);
|
|
77
|
+
setTotal(response.total ?? conversations.length + moreConversations.length);
|
|
78
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
79
|
+
return moreConversations;
|
|
80
|
+
}
|
|
81
|
+
catch (loadError) {
|
|
82
|
+
const normalized = normalizeError(loadError, "Failed to load more conversations.");
|
|
83
|
+
throw normalized;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
setIsLoadingMore(false);
|
|
87
|
+
}
|
|
88
|
+
}, [conversations.length, enabled, isLoading, isLoadingMore, limit, listConversations, nextPageToken]);
|
|
63
89
|
const createConversation = useCallback(async (input = {}) => {
|
|
64
90
|
const createdConversation = await sessionCreateConversation({
|
|
65
91
|
...input,
|
|
@@ -108,6 +134,7 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
108
134
|
setNextPageToken(null);
|
|
109
135
|
setSelectedConversationId(initialConversationId);
|
|
110
136
|
setIsLoading(false);
|
|
137
|
+
setIsLoadingMore(false);
|
|
111
138
|
return;
|
|
112
139
|
}
|
|
113
140
|
setConversations([]);
|
|
@@ -116,7 +143,22 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
116
143
|
setSelectedConversationId(initialConversationId);
|
|
117
144
|
if (!autoLoad)
|
|
118
145
|
return;
|
|
119
|
-
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
let cancelled = false;
|
|
148
|
+
(async () => {
|
|
149
|
+
try {
|
|
150
|
+
await refresh();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
if (!cancelled) {
|
|
154
|
+
// refresh handles errors internally
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
return () => {
|
|
159
|
+
cancelled = true;
|
|
160
|
+
controller.abort();
|
|
161
|
+
};
|
|
120
162
|
}, [autoLoad, enabled, initialConversationId, refresh, scopeKey]);
|
|
121
163
|
const effectiveSelectedConversationId = useMemo(() => {
|
|
122
164
|
if (selectedConversationId)
|
|
@@ -154,11 +196,13 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
154
196
|
effectiveSelectedConversationId,
|
|
155
197
|
selectedConversation,
|
|
156
198
|
isLoading,
|
|
199
|
+
isLoadingMore,
|
|
157
200
|
error,
|
|
158
201
|
selectConversation,
|
|
159
202
|
clearSelection,
|
|
160
203
|
selectLatestConversation,
|
|
161
204
|
refresh,
|
|
205
|
+
loadMore,
|
|
162
206
|
createConversation,
|
|
163
207
|
createAndSelectConversation,
|
|
164
208
|
ensureConversation,
|
|
@@ -168,7 +212,10 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
168
212
|
createAndSelectConversation,
|
|
169
213
|
createConversation,
|
|
170
214
|
effectiveSelectedConversationId,
|
|
215
|
+
error,
|
|
171
216
|
isLoading,
|
|
217
|
+
isLoadingMore,
|
|
218
|
+
loadMore,
|
|
172
219
|
nextPageToken,
|
|
173
220
|
refresh,
|
|
174
221
|
ensureConversation,
|
|
@@ -176,7 +223,6 @@ export function useConversations({ client, podId, assistantName, assistantId, or
|
|
|
176
223
|
selectLatestConversation,
|
|
177
224
|
selectedConversation,
|
|
178
225
|
selectedConversationId,
|
|
179
|
-
error,
|
|
180
226
|
total,
|
|
181
227
|
]);
|
|
182
228
|
}
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import type { LemmaClient } from "../client.js";
|
|
2
|
-
import type { RecordResponse } from "../types.js";
|
|
2
|
+
import type { FunctionRun, RecordResponse } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* React hook for creating a single record. Manages loading/error state and
|
|
5
|
+
* exposes a `create` function you can call from event handlers.
|
|
6
|
+
*
|
|
7
|
+
* Supports two modes:
|
|
8
|
+
* - `"direct"` (default): calls `records.create` directly.
|
|
9
|
+
* - `"function"`: calls `functions.runs.create`, routing the create through
|
|
10
|
+
* a pod function that may enforce business logic.
|
|
11
|
+
*
|
|
12
|
+
* @example Direct create
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { create, isSubmitting } = useCreateRecord({ client, tableName: "comments" });
|
|
15
|
+
* await create({ body: "Hello", issue_id: "123" });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example Function-backed create
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { create, isSubmitting } = useCreateRecord({
|
|
21
|
+
* client,
|
|
22
|
+
* tableName: "issues",
|
|
23
|
+
* createVia: "function",
|
|
24
|
+
* createFunctionName: "create-issue",
|
|
25
|
+
* });
|
|
26
|
+
* await create({ title: "Bug", team_id: "team_1" });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
3
29
|
export interface UseCreateRecordOptions {
|
|
4
30
|
client: LemmaClient;
|
|
5
31
|
podId?: string;
|
|
6
32
|
tableName: string;
|
|
7
33
|
enabled?: boolean;
|
|
8
|
-
|
|
34
|
+
/** How the record is created. `"direct"` calls `records.create`. `"function"` calls `functions.runs.create`. */
|
|
35
|
+
createVia?: "direct" | "function";
|
|
36
|
+
/** Function name to run when `createVia` is `"function"`. Falls back to `tableName` if omitted. */
|
|
37
|
+
createFunctionName?: string;
|
|
38
|
+
onSuccess?: (record: Record<string, unknown>, response: RecordResponse | FunctionRun) => void;
|
|
9
39
|
onError?: (error: unknown) => void;
|
|
10
40
|
}
|
|
11
41
|
export interface UseCreateRecordResult<TRecord extends Record<string, unknown> = Record<string, unknown>> {
|
|
@@ -15,4 +45,4 @@ export interface UseCreateRecordResult<TRecord extends Record<string, unknown> =
|
|
|
15
45
|
create: (data: Record<string, unknown>) => Promise<TRecord | null>;
|
|
16
46
|
reset: () => void;
|
|
17
47
|
}
|
|
18
|
-
export declare function useCreateRecord<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, enabled, onSuccess, onError, }: UseCreateRecordOptions): UseCreateRecordResult<TRecord>;
|
|
48
|
+
export declare function useCreateRecord<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, enabled, createVia, createFunctionName, onSuccess, onError, }: UseCreateRecordOptions): UseCreateRecordResult<TRecord>;
|