lemma-sdk 0.2.30 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +211 -51
  2. package/dist/react/index.d.ts +20 -0
  3. package/dist/react/index.js +10 -0
  4. package/dist/react/useAgentInputSchema.d.ts +19 -0
  5. package/dist/react/useAgentInputSchema.js +73 -0
  6. package/dist/react/useAgentRun.js +18 -20
  7. package/dist/react/useAgentRuns.d.ts +33 -0
  8. package/dist/react/useAgentRuns.js +149 -0
  9. package/dist/react/useAssistantRun.js +10 -9
  10. package/dist/react/useAssistantSession.js +21 -25
  11. package/dist/react/useBulkRecords.js +9 -16
  12. package/dist/react/useConversation.js +24 -8
  13. package/dist/react/useConversations.d.ts +4 -0
  14. package/dist/react/useConversations.js +49 -3
  15. package/dist/react/useCreateRecord.js +9 -16
  16. package/dist/react/useCurrentUser.d.ts +14 -0
  17. package/dist/react/useCurrentUser.js +68 -0
  18. package/dist/react/useDeleteRecord.js +9 -16
  19. package/dist/react/useFlowRunHistory.js +1 -5
  20. package/dist/react/useFlowSession.js +41 -33
  21. package/dist/react/useForeignKeyOptions.js +26 -15
  22. package/dist/react/useFunctionRun.d.ts +19 -0
  23. package/dist/react/useFunctionRun.js +30 -0
  24. package/dist/react/useFunctionRuns.d.ts +33 -0
  25. package/dist/react/useFunctionRuns.js +149 -0
  26. package/dist/react/useFunctionSession.js +37 -29
  27. package/dist/react/useJoinedRecords.js +24 -23
  28. package/dist/react/useMembers.d.ts +4 -0
  29. package/dist/react/useMembers.js +55 -16
  30. package/dist/react/useOrganizationMembers.d.ts +26 -0
  31. package/dist/react/useOrganizationMembers.js +113 -0
  32. package/dist/react/usePodAccess.d.ts +22 -0
  33. package/dist/react/usePodAccess.js +128 -0
  34. package/dist/react/useRecord.js +24 -13
  35. package/dist/react/useRecordForm.js +1 -18
  36. package/dist/react/useRecords.d.ts +2 -0
  37. package/dist/react/useRecords.js +62 -22
  38. package/dist/react/useRelatedRecords.js +28 -21
  39. package/dist/react/useReverseRelatedRecords.js +30 -21
  40. package/dist/react/useSchemaForm.js +1 -13
  41. package/dist/react/useTable.js +24 -13
  42. package/dist/react/useTables.d.ts +4 -0
  43. package/dist/react/useTables.js +57 -15
  44. package/dist/react/useTaskSession.js +11 -22
  45. package/dist/react/useUpdateRecord.js +9 -16
  46. package/dist/react/useWorkflowResume.d.ts +18 -0
  47. package/dist/react/useWorkflowResume.js +45 -0
  48. package/dist/react/useWorkflowRun.d.ts +21 -0
  49. package/dist/react/useWorkflowRun.js +49 -0
  50. package/dist/react/useWorkflowRuns.d.ts +33 -0
  51. package/dist/react/useWorkflowRuns.js +149 -0
  52. package/dist/react/useWorkflowStart.js +20 -27
  53. package/dist/react/utils.d.ts +5 -0
  54. package/dist/react/utils.js +25 -0
  55. package/dist/types.d.ts +1 -0
  56. package/package.json +2 -4
  57. package/dist/react/components/AssistantChrome.d.ts +0 -86
  58. package/dist/react/components/AssistantChrome.js +0 -48
  59. package/dist/react/components/AssistantEmbedded.d.ts +0 -10
  60. package/dist/react/components/AssistantEmbedded.js +0 -15
  61. package/dist/react/components/AssistantExperience.d.ts +0 -96
  62. package/dist/react/components/AssistantExperience.js +0 -1294
  63. package/dist/react/components/assistant-types.d.ts +0 -80
  64. 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: () => messages.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
- function resolveOptionalPodId(client, podId) {
6
- return podId ?? client.podId;
7
- }
5
+ import { normalizeError } from "./utils.js";
8
6
  function applyPodScope(client, podId) {
9
- const resolvedPodId = resolveOptionalPodId(client, podId);
10
- if (resolvedPodId) {
11
- client.setPodId(resolvedPodId);
7
+ const resolvedPodId = podId ?? client.podId;
8
+ if (resolvedPodId && resolvedPodId !== client.podId) {
9
+ return client.withPod(resolvedPodId);
12
10
  }
13
- return resolvedPodId;
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 client.conversations.list({
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 ?? client.podId ?? undefined,
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 client.conversations.create(payload);
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 client.conversations.get(id, {
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 client.conversations.sendMessageStream(resolvedConversationId, { content }, {
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 client.conversations.resumeStream(id, {
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 client.conversations.stopRun(id, {
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
- 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
- }
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
- onSuccess?.(response);
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
- onError?.(mutationError);
29
+ onErrorRef.current?.(mutationError);
37
30
  return null;
38
31
  }
39
32
  finally {
40
33
  setIsSubmitting(false);
41
34
  }
42
- }, [client, isEnabled, onError, onSuccess, podId]);
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
- function normalizeError(error, fallback) {
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
- setIsLoading(false);
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
- void refresh();
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
- void refresh();
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,24 +1,17 @@
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
- }
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
12
3
  export function useCreateRecord({ client, podId, tableName, enabled = true, onSuccess, onError, }) {
13
4
  const [createdRecord, setCreatedRecord] = useState(null);
14
5
  const [isSubmitting, setIsSubmitting] = useState(false);
15
6
  const [error, setError] = 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 create = useCallback(async (data) => {
19
14
  if (!isEnabled) {
20
- const disabledError = new Error("Record creation is disabled.");
21
- setError(disabledError);
22
15
  return null;
23
16
  }
24
17
  setIsSubmitting(true);
@@ -29,20 +22,20 @@ export function useCreateRecord({ client, podId, tableName, enabled = true, onSu
29
22
  const nextRecord = (response.data ?? null);
30
23
  setCreatedRecord(nextRecord);
31
24
  if (nextRecord) {
32
- onSuccess?.(nextRecord, response);
25
+ onSuccessRef.current?.(nextRecord, response);
33
26
  }
34
27
  return nextRecord;
35
28
  }
36
29
  catch (mutationError) {
37
30
  const normalized = normalizeError(mutationError, "Failed to create record.");
38
31
  setError(normalized);
39
- onError?.(mutationError);
32
+ onErrorRef.current?.(mutationError);
40
33
  return null;
41
34
  }
42
35
  finally {
43
36
  setIsSubmitting(false);
44
37
  }
45
- }, [client, isEnabled, onError, onSuccess, podId, trimmedTableName]);
38
+ }, [client, isEnabled, podId, trimmedTableName]);
46
39
  const reset = useCallback(() => {
47
40
  setCreatedRecord(null);
48
41
  setError(null);
@@ -0,0 +1,14 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { User } from "../types.js";
3
+ export interface UseCurrentUserOptions {
4
+ client: LemmaClient;
5
+ enabled?: boolean;
6
+ autoLoad?: boolean;
7
+ }
8
+ export interface UseCurrentUserResult {
9
+ user: User | null;
10
+ isLoading: boolean;
11
+ error: Error | null;
12
+ refresh: () => Promise<User | null>;
13
+ }
14
+ export declare function useCurrentUser({ client, enabled, autoLoad, }: UseCurrentUserOptions): UseCurrentUserResult;