lemma-sdk 0.2.28 → 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.
Files changed (89) hide show
  1. package/README.md +113 -233
  2. package/bin/lemma-sdk.js +108 -0
  3. package/dist/browser/lemma-client.js +125 -4
  4. package/dist/client.d.ts +2 -0
  5. package/dist/client.js +3 -0
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.js +2 -2
  8. package/dist/datastore-query.d.ts +54 -0
  9. package/dist/datastore-query.js +157 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +3 -0
  12. package/dist/namespaces/datastore.d.ts +9 -0
  13. package/dist/namespaces/datastore.js +13 -0
  14. package/dist/namespaces/records.d.ts +1 -1
  15. package/dist/openapi_client/index.d.ts +4 -0
  16. package/dist/openapi_client/index.js +1 -0
  17. package/dist/openapi_client/models/ConvertedArtifactResponse.d.ts +6 -0
  18. package/dist/openapi_client/models/ConvertedArtifactResponse.js +1 -0
  19. package/dist/openapi_client/models/ConvertedFileResponse.d.ts +10 -0
  20. package/dist/openapi_client/models/ConvertedFileResponse.js +1 -0
  21. package/dist/openapi_client/models/CreateFolderRequest.d.ts +3 -1
  22. package/dist/openapi_client/models/FlowRunEntity.d.ts +1 -0
  23. package/dist/openapi_client/models/ScheduledFlowStart.d.ts +3 -6
  24. package/dist/openapi_client/models/ScheduledFlowStartType.d.ts +4 -0
  25. package/dist/openapi_client/models/ScheduledFlowStartType.js +9 -0
  26. package/dist/openapi_client/models/WorkflowInstallRequest.d.ts +5 -0
  27. package/dist/openapi_client/models/WorkflowTimeInstallConfig.d.ts +19 -0
  28. package/dist/openapi_client/models/WorkflowTimeInstallConfig.js +1 -0
  29. package/dist/openapi_client/services/FilesService.d.ts +27 -1
  30. package/dist/openapi_client/services/FilesService.js +69 -1
  31. package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
  32. package/dist/openapi_client/services/WorkflowsService.js +1 -1
  33. package/dist/react/assistant-output.d.ts +6 -0
  34. package/dist/react/assistant-output.js +90 -0
  35. package/dist/react/index.d.ts +42 -8
  36. package/dist/react/index.js +21 -4
  37. package/dist/react/useAgentRun.d.ts +17 -0
  38. package/dist/react/useAgentRun.js +58 -0
  39. package/dist/react/useAssistantRun.d.ts +9 -0
  40. package/dist/react/useAssistantRun.js +19 -9
  41. package/dist/react/useAssistantSession.d.ts +5 -0
  42. package/dist/react/useAssistantSession.js +123 -70
  43. package/dist/react/useBulkRecords.d.ts +20 -0
  44. package/dist/react/useBulkRecords.js +72 -0
  45. package/dist/react/useConversation.d.ts +18 -0
  46. package/dist/react/useConversation.js +59 -0
  47. package/dist/react/useConversationMessages.d.ts +59 -0
  48. package/dist/react/useConversationMessages.js +167 -0
  49. package/dist/react/useConversations.d.ts +48 -0
  50. package/dist/react/useConversations.js +182 -0
  51. package/dist/react/useCreateRecord.d.ts +18 -0
  52. package/dist/react/useCreateRecord.js +58 -0
  53. package/dist/react/useDeleteRecord.d.ts +21 -0
  54. package/dist/react/useDeleteRecord.js +59 -0
  55. package/dist/react/useForeignKeyOptions.d.ts +31 -0
  56. package/dist/react/useForeignKeyOptions.js +150 -0
  57. package/dist/react/useJoinedRecords.d.ts +18 -0
  58. package/dist/react/useJoinedRecords.js +79 -0
  59. package/dist/react/useMembers.d.ts +22 -0
  60. package/dist/react/useMembers.js +59 -0
  61. package/dist/react/useRecord.d.ts +18 -0
  62. package/dist/react/useRecord.js +64 -0
  63. package/dist/react/useRecordForm.d.ts +42 -0
  64. package/dist/react/useRecordForm.js +238 -0
  65. package/dist/react/useRecordSchema.d.ts +20 -0
  66. package/dist/react/useRecordSchema.js +24 -0
  67. package/dist/react/useRecords.d.ts +18 -0
  68. package/dist/react/useRecords.js +106 -0
  69. package/dist/react/useRelatedRecords.d.ts +43 -0
  70. package/dist/react/useRelatedRecords.js +232 -0
  71. package/dist/react/useReverseRelatedRecords.d.ts +47 -0
  72. package/dist/react/useReverseRelatedRecords.js +226 -0
  73. package/dist/react/useSchemaForm.d.ts +24 -0
  74. package/dist/react/useSchemaForm.js +116 -0
  75. package/dist/react/useTable.d.ts +16 -0
  76. package/dist/react/useTable.js +59 -0
  77. package/dist/react/useTables.d.ts +22 -0
  78. package/dist/react/useTables.js +71 -0
  79. package/dist/react/useUpdateRecord.d.ts +21 -0
  80. package/dist/react/useUpdateRecord.js +62 -0
  81. package/dist/react/useWorkflowStart.d.ts +33 -0
  82. package/dist/react/useWorkflowStart.js +155 -0
  83. package/dist/record-form.d.ts +30 -0
  84. package/dist/record-form.js +199 -0
  85. package/dist/schema-form.d.ts +41 -0
  86. package/dist/schema-form.js +200 -0
  87. package/dist/types.d.ts +5 -1
  88. package/package.json +10 -5
  89. package/dist/react/styles.css +0 -2407
@@ -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
+ }
@@ -0,0 +1,48 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { Conversation } from "../types.js";
3
+ import { type CreateConversationInput } from "./useAssistantSession.js";
4
+ export interface UseConversationsOptions {
5
+ client: LemmaClient;
6
+ podId?: string;
7
+ assistantName?: string;
8
+ /**
9
+ * @deprecated Use assistantName instead.
10
+ */
11
+ assistantId?: string;
12
+ organizationId?: string;
13
+ enabled?: boolean;
14
+ autoLoad?: boolean;
15
+ autoSelectFirst?: boolean;
16
+ limit?: number;
17
+ pageToken?: string;
18
+ initialConversationId?: string | null;
19
+ }
20
+ export interface UseConversationsResult {
21
+ conversations: Conversation[];
22
+ total: number;
23
+ nextPageToken: string | null;
24
+ /**
25
+ * @deprecated Use selectedConversationId instead.
26
+ */
27
+ activeConversationId: string | null;
28
+ /**
29
+ * @deprecated Use selectedConversation instead.
30
+ */
31
+ activeConversation: Conversation | null;
32
+ selectedConversationId: string | null;
33
+ effectiveSelectedConversationId: string | null;
34
+ selectedConversation: Conversation | null;
35
+ isLoading: boolean;
36
+ error: Error | null;
37
+ selectConversation: (conversationId: string | null) => void;
38
+ clearSelection: () => void;
39
+ selectLatestConversation: () => string | null;
40
+ refresh: (overrides?: {
41
+ limit?: number;
42
+ pageToken?: string;
43
+ }) => Promise<Conversation[]>;
44
+ createConversation: (input?: CreateConversationInput) => Promise<Conversation>;
45
+ createAndSelectConversation: (input?: Omit<CreateConversationInput, "setActive">) => Promise<Conversation>;
46
+ ensureConversation: (input?: Omit<CreateConversationInput, "setActive">) => Promise<Conversation>;
47
+ }
48
+ export declare function useConversations({ client, podId, assistantName, assistantId, organizationId, enabled, autoLoad, autoSelectFirst, limit, pageToken, initialConversationId, }: UseConversationsOptions): UseConversationsResult;
@@ -0,0 +1,182 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { useAssistantSession, } from "./useAssistantSession.js";
3
+ function sortConversationsByUpdatedAt(conversations) {
4
+ return [...conversations].sort((a, b) => {
5
+ const aTime = new Date(a.updated_at || a.created_at).getTime();
6
+ const bTime = new Date(b.updated_at || b.created_at).getTime();
7
+ return bTime - aTime;
8
+ });
9
+ }
10
+ export function useConversations({ client, podId, assistantName, assistantId, organizationId, enabled = true, autoLoad = true, autoSelectFirst = true, limit = 20, pageToken, initialConversationId = null, }) {
11
+ const [conversations, setConversations] = useState([]);
12
+ const [total, setTotal] = useState(0);
13
+ const [nextPageToken, setNextPageToken] = useState(null);
14
+ const [selectedConversationId, setSelectedConversationId] = useState(initialConversationId);
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const scopeKey = useMemo(() => JSON.stringify({
17
+ podId: podId ?? null,
18
+ assistantName: assistantName ?? assistantId ?? null,
19
+ assistantId: assistantId ?? null,
20
+ organizationId: organizationId ?? null,
21
+ }), [assistantId, assistantName, organizationId, podId]);
22
+ const { error, listConversations, createConversation: sessionCreateConversation, } = useAssistantSession({
23
+ client,
24
+ podId,
25
+ assistantName,
26
+ assistantId,
27
+ organizationId,
28
+ autoLoad: false,
29
+ });
30
+ const refresh = useCallback(async (overrides = {}) => {
31
+ if (!enabled) {
32
+ setConversations([]);
33
+ setTotal(0);
34
+ setNextPageToken(null);
35
+ setIsLoading(false);
36
+ return [];
37
+ }
38
+ setIsLoading(true);
39
+ try {
40
+ const response = await listConversations({
41
+ limit: overrides.limit ?? limit,
42
+ pageToken: overrides.pageToken ?? pageToken,
43
+ });
44
+ const nextConversations = sortConversationsByUpdatedAt(response.items ?? []);
45
+ setConversations(nextConversations);
46
+ setTotal(nextConversations.length);
47
+ setNextPageToken(response.next_page_token ?? null);
48
+ setSelectedConversationId((current) => {
49
+ if (current)
50
+ return current;
51
+ if (initialConversationId)
52
+ return initialConversationId;
53
+ if (!autoSelectFirst)
54
+ return null;
55
+ return nextConversations[0]?.id ?? null;
56
+ });
57
+ return nextConversations;
58
+ }
59
+ finally {
60
+ setIsLoading(false);
61
+ }
62
+ }, [autoSelectFirst, enabled, initialConversationId, limit, listConversations, pageToken]);
63
+ const createConversation = useCallback(async (input = {}) => {
64
+ const createdConversation = await sessionCreateConversation({
65
+ ...input,
66
+ podId: input.podId ?? podId ?? undefined,
67
+ assistantName: input.assistantName ?? assistantName ?? assistantId ?? undefined,
68
+ organizationId: input.organizationId ?? organizationId ?? undefined,
69
+ setActive: input.setActive ?? true,
70
+ });
71
+ setConversations((previous) => {
72
+ const nextConversations = sortConversationsByUpdatedAt([
73
+ createdConversation,
74
+ ...previous.filter((conversation) => conversation.id !== createdConversation.id),
75
+ ]);
76
+ setTotal(nextConversations.length);
77
+ return nextConversations;
78
+ });
79
+ if (input.setActive !== false) {
80
+ setSelectedConversationId(createdConversation.id);
81
+ }
82
+ return createdConversation;
83
+ }, [assistantId, assistantName, organizationId, podId, sessionCreateConversation]);
84
+ const createAndSelectConversation = useCallback(async (input = {}) => {
85
+ return createConversation({
86
+ ...input,
87
+ setActive: true,
88
+ });
89
+ }, [createConversation]);
90
+ const selectConversation = useCallback((conversationId) => {
91
+ setSelectedConversationId(conversationId);
92
+ }, []);
93
+ const clearSelection = useCallback(() => {
94
+ setSelectedConversationId(null);
95
+ }, []);
96
+ const selectLatestConversation = useCallback(() => {
97
+ const latestConversationId = conversations[0]?.id ?? null;
98
+ setSelectedConversationId(latestConversationId);
99
+ return latestConversationId;
100
+ }, []);
101
+ useEffect(() => {
102
+ setSelectedConversationId(initialConversationId);
103
+ }, [initialConversationId]);
104
+ useEffect(() => {
105
+ if (!enabled) {
106
+ setConversations([]);
107
+ setTotal(0);
108
+ setNextPageToken(null);
109
+ setSelectedConversationId(initialConversationId);
110
+ setIsLoading(false);
111
+ return;
112
+ }
113
+ setConversations([]);
114
+ setTotal(0);
115
+ setNextPageToken(null);
116
+ setSelectedConversationId(initialConversationId);
117
+ if (!autoLoad)
118
+ return;
119
+ void refresh();
120
+ }, [autoLoad, enabled, initialConversationId, refresh, scopeKey]);
121
+ const effectiveSelectedConversationId = useMemo(() => {
122
+ if (selectedConversationId)
123
+ return selectedConversationId;
124
+ if (!autoSelectFirst)
125
+ return null;
126
+ return conversations[0]?.id ?? null;
127
+ }, [autoSelectFirst, conversations, selectedConversationId]);
128
+ const selectedConversation = useMemo(() => conversations.find((conversation) => conversation.id === effectiveSelectedConversationId) ?? null, [conversations, effectiveSelectedConversationId]);
129
+ const ensureConversation = useCallback(async (input = {}) => {
130
+ const existingConversation = selectedConversation
131
+ ?? conversations.find((conversation) => conversation.id === effectiveSelectedConversationId)
132
+ ?? null;
133
+ if (existingConversation) {
134
+ if (selectedConversationId !== existingConversation.id) {
135
+ setSelectedConversationId(existingConversation.id);
136
+ }
137
+ return existingConversation;
138
+ }
139
+ return createAndSelectConversation(input);
140
+ }, [
141
+ conversations,
142
+ createAndSelectConversation,
143
+ effectiveSelectedConversationId,
144
+ selectedConversation,
145
+ selectedConversationId,
146
+ ]);
147
+ return useMemo(() => ({
148
+ conversations,
149
+ total,
150
+ nextPageToken,
151
+ activeConversationId: selectedConversationId,
152
+ activeConversation: selectedConversation,
153
+ selectedConversationId,
154
+ effectiveSelectedConversationId,
155
+ selectedConversation,
156
+ isLoading,
157
+ error,
158
+ selectConversation,
159
+ clearSelection,
160
+ selectLatestConversation,
161
+ refresh,
162
+ createConversation,
163
+ createAndSelectConversation,
164
+ ensureConversation,
165
+ }), [
166
+ clearSelection,
167
+ conversations,
168
+ createAndSelectConversation,
169
+ createConversation,
170
+ effectiveSelectedConversationId,
171
+ isLoading,
172
+ nextPageToken,
173
+ refresh,
174
+ ensureConversation,
175
+ selectConversation,
176
+ selectLatestConversation,
177
+ selectedConversation,
178
+ selectedConversationId,
179
+ error,
180
+ total,
181
+ ]);
182
+ }
@@ -0,0 +1,18 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { RecordResponse } from "../types.js";
3
+ export interface UseCreateRecordOptions {
4
+ client: LemmaClient;
5
+ podId?: string;
6
+ tableName: string;
7
+ enabled?: boolean;
8
+ onSuccess?: (record: Record<string, unknown>, response: RecordResponse) => void;
9
+ onError?: (error: unknown) => void;
10
+ }
11
+ export interface UseCreateRecordResult<TRecord extends Record<string, unknown> = Record<string, unknown>> {
12
+ createdRecord: TRecord | null;
13
+ isSubmitting: boolean;
14
+ error: Error | null;
15
+ create: (data: Record<string, unknown>) => Promise<TRecord | null>;
16
+ reset: () => void;
17
+ }
18
+ export declare function useCreateRecord<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, enabled, onSuccess, onError, }: UseCreateRecordOptions): UseCreateRecordResult<TRecord>;
@@ -0,0 +1,58 @@
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 useCreateRecord({ client, podId, tableName, enabled = true, onSuccess, onError, }) {
13
+ const [createdRecord, setCreatedRecord] = useState(null);
14
+ const [isSubmitting, setIsSubmitting] = useState(false);
15
+ const [error, setError] = useState(null);
16
+ const trimmedTableName = tableName.trim();
17
+ const isEnabled = enabled && trimmedTableName.length > 0;
18
+ const create = useCallback(async (data) => {
19
+ if (!isEnabled) {
20
+ const disabledError = new Error("Record creation is 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 scopedClient.records.create(trimmedTableName, data);
29
+ const nextRecord = (response.data ?? null);
30
+ setCreatedRecord(nextRecord);
31
+ if (nextRecord) {
32
+ onSuccess?.(nextRecord, response);
33
+ }
34
+ return nextRecord;
35
+ }
36
+ catch (mutationError) {
37
+ const normalized = normalizeError(mutationError, "Failed to create record.");
38
+ setError(normalized);
39
+ onError?.(mutationError);
40
+ return null;
41
+ }
42
+ finally {
43
+ setIsSubmitting(false);
44
+ }
45
+ }, [client, isEnabled, onError, onSuccess, podId, trimmedTableName]);
46
+ const reset = useCallback(() => {
47
+ setCreatedRecord(null);
48
+ setError(null);
49
+ setIsSubmitting(false);
50
+ }, []);
51
+ return useMemo(() => ({
52
+ createdRecord,
53
+ isSubmitting,
54
+ error,
55
+ create,
56
+ reset,
57
+ }), [create, createdRecord, error, isSubmitting, reset]);
58
+ }