@melony/react 0.1.21 → 0.1.22

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/dist/index.cjs CHANGED
@@ -1,8 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var React10 = require('react');
4
+ var react = require('nuqs/adapters/react');
5
+ var reactQuery = require('@tanstack/react-query');
4
6
  var jsxRuntime = require('react/jsx-runtime');
5
7
  var client = require('melony/client');
8
+ var nuqs = require('nuqs');
6
9
  var clsx = require('clsx');
7
10
  var tailwindMerge = require('tailwind-merge');
8
11
  var button = require('@base-ui/react/button');
@@ -69,12 +72,28 @@ function groupEventsToMessages(events) {
69
72
  var MelonyContext = React10.createContext(
70
73
  void 0
71
74
  );
72
- var MelonyClientProvider = ({
75
+ var defaultQueryClient = new reactQuery.QueryClient({
76
+ defaultOptions: {
77
+ queries: {
78
+ retry: false,
79
+ refetchOnWindowFocus: false
80
+ }
81
+ }
82
+ });
83
+ var MelonyContextProviderInner = ({
73
84
  children,
74
85
  client,
75
- initialEvents
86
+ initialEvents,
87
+ configApi,
88
+ setContextValue
76
89
  }) => {
77
90
  const [state, setState] = React10.useState(client.getState());
91
+ const { data: config } = reactQuery.useQuery({
92
+ queryKey: ["melony-config", configApi],
93
+ queryFn: () => client.getConfig(configApi),
94
+ enabled: !!configApi,
95
+ staleTime: Infinity
96
+ });
78
97
  React10.useEffect(() => {
79
98
  if (initialEvents && initialEvents.length > 0 && client.getState().events.length === 0) {
80
99
  client.reset(initialEvents);
@@ -105,11 +124,34 @@ var MelonyClientProvider = ({
105
124
  messages: groupEventsToMessages(state.events),
106
125
  sendEvent,
107
126
  reset,
108
- client
127
+ client,
128
+ config
109
129
  }),
110
- [state, sendEvent, reset, client]
130
+ [state, sendEvent, reset, client, config]
111
131
  );
112
- return /* @__PURE__ */ jsxRuntime.jsx(MelonyContext.Provider, { value, children });
132
+ React10.useEffect(() => {
133
+ setContextValue(value);
134
+ }, [value, setContextValue]);
135
+ return /* @__PURE__ */ jsxRuntime.jsx(react.NuqsAdapter, { children });
136
+ };
137
+ var MelonyClientProvider = ({
138
+ children,
139
+ client,
140
+ initialEvents,
141
+ queryClient = defaultQueryClient,
142
+ configApi
143
+ }) => {
144
+ const [contextValue, setContextValue] = React10.useState(void 0);
145
+ return /* @__PURE__ */ jsxRuntime.jsx(MelonyContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(
146
+ MelonyContextProviderInner,
147
+ {
148
+ client,
149
+ initialEvents,
150
+ configApi,
151
+ setContextValue,
152
+ children
153
+ }
154
+ ) }) });
113
155
  };
114
156
  var AuthContext = React10.createContext(
115
157
  void 0
@@ -182,111 +224,75 @@ var ThreadProvider = ({
182
224
  service,
183
225
  initialThreadId: providedInitialThreadId
184
226
  }) => {
227
+ const queryClient = reactQuery.useQueryClient();
185
228
  const defaultInitialThreadId = React10.useMemo(() => client.generateId(), []);
186
229
  const initialThreadId = providedInitialThreadId || defaultInitialThreadId;
187
- const [threads, setThreads] = React10.useState([]);
188
- const [activeThreadId, setActiveThreadId] = React10.useState(
189
- initialThreadId
230
+ const [activeThreadId, setActiveThreadId] = nuqs.useQueryState(
231
+ "threadId",
232
+ nuqs.parseAsString.withDefault(initialThreadId)
190
233
  );
191
- const [isLoading, setIsLoading] = React10.useState(true);
192
- const [error, setError] = React10.useState(null);
193
- const [threadEvents, setThreadEvents] = React10.useState([]);
194
- const [isLoadingEvents, setIsLoadingEvents] = React10.useState(false);
195
- const fetchThreads = React10.useCallback(async () => {
196
- setIsLoading(true);
197
- setError(null);
198
- try {
199
- const processedThreads = await service.getThreads();
200
- setThreads(processedThreads);
201
- } catch (err) {
202
- const error2 = err instanceof Error ? err : new Error("Failed to fetch threads");
203
- setError(error2);
204
- console.error("Failed to fetch threads:", error2);
205
- } finally {
206
- setIsLoading(false);
234
+ const {
235
+ data: threads = [],
236
+ isLoading,
237
+ error: threadsError,
238
+ refetch: refreshThreads
239
+ } = reactQuery.useQuery({
240
+ queryKey: ["threads"],
241
+ queryFn: () => service.getThreads()
242
+ });
243
+ const { data: threadEvents = [], isLoading: isLoadingEvents } = reactQuery.useQuery({
244
+ queryKey: ["threads", activeThreadId, "events"],
245
+ queryFn: () => service.getEvents(activeThreadId),
246
+ enabled: !!activeThreadId
247
+ });
248
+ const createMutation = reactQuery.useMutation({
249
+ mutationFn: async () => {
250
+ const newId = service.createThread ? await service.createThread() : client.generateId();
251
+ return newId;
252
+ },
253
+ onSuccess: async (newId) => {
254
+ await queryClient.invalidateQueries({ queryKey: ["threads"] });
255
+ await setActiveThreadId(newId);
207
256
  }
208
- }, [service]);
209
- React10.useEffect(() => {
210
- fetchThreads();
211
- }, [fetchThreads]);
212
- const selectThread = React10.useCallback((threadId) => {
213
- setActiveThreadId(threadId);
214
- }, []);
257
+ });
258
+ const deleteMutation = reactQuery.useMutation({
259
+ mutationFn: (threadId) => service.deleteThread(threadId),
260
+ onSuccess: async (_, threadId) => {
261
+ await queryClient.invalidateQueries({ queryKey: ["threads"] });
262
+ if (activeThreadId === threadId) {
263
+ const remainingThreads = threads.filter((t) => t.id !== threadId);
264
+ const nextId = remainingThreads.length > 0 ? remainingThreads[0].id : null;
265
+ await setActiveThreadId(nextId);
266
+ }
267
+ }
268
+ });
269
+ const selectThread = React10.useCallback(
270
+ (threadId) => {
271
+ setActiveThreadId(threadId);
272
+ },
273
+ [setActiveThreadId]
274
+ );
215
275
  const createThread = React10.useCallback(async () => {
216
- const newId = service.createThread ? await service.createThread() : client.generateId();
217
- const newThread = {
218
- id: newId,
219
- updatedAt: /* @__PURE__ */ new Date()
220
- };
221
- setThreads((prev) => [newThread, ...prev]);
222
- setActiveThreadId(newId);
223
- return newId;
224
- }, [service]);
276
+ return createMutation.mutateAsync();
277
+ }, [createMutation]);
225
278
  const deleteThread = React10.useCallback(
226
279
  async (threadId) => {
227
- try {
228
- await service.deleteThread(threadId);
229
- setThreads((prev) => {
230
- const remainingThreads = prev.filter((t) => t.id !== threadId);
231
- setActiveThreadId((current) => {
232
- if (current === threadId) {
233
- return remainingThreads.length > 0 ? remainingThreads[0].id : null;
234
- }
235
- return current;
236
- });
237
- return remainingThreads;
238
- });
239
- } catch (err) {
240
- const error2 = err instanceof Error ? err : new Error("Failed to delete thread");
241
- setError(error2);
242
- throw error2;
243
- }
280
+ return deleteMutation.mutateAsync(threadId);
244
281
  },
245
- [service]
282
+ [deleteMutation]
246
283
  );
247
- const refreshThreads = React10.useCallback(async () => {
248
- await fetchThreads();
249
- }, [fetchThreads]);
250
- React10.useEffect(() => {
251
- if (!activeThreadId) {
252
- setThreadEvents([]);
253
- setIsLoadingEvents(false);
254
- return;
255
- }
256
- let cancelled = false;
257
- const fetchEvents = async () => {
258
- setIsLoadingEvents(true);
259
- try {
260
- const events = await service.getEvents(activeThreadId);
261
- if (!cancelled) {
262
- setThreadEvents(events);
263
- }
264
- } catch (err) {
265
- if (!cancelled) {
266
- console.error("Failed to fetch events:", err);
267
- setThreadEvents([]);
268
- }
269
- } finally {
270
- if (!cancelled) {
271
- setIsLoadingEvents(false);
272
- }
273
- }
274
- };
275
- fetchEvents();
276
- return () => {
277
- cancelled = true;
278
- };
279
- }, [activeThreadId, service]);
280
284
  const value = React10.useMemo(
281
285
  () => ({
282
286
  threads,
283
287
  activeThreadId,
284
288
  isLoading,
285
- error,
289
+ error: threadsError || null,
286
290
  selectThread,
287
291
  createThread,
288
292
  deleteThread,
289
- refreshThreads,
293
+ refreshThreads: async () => {
294
+ await refreshThreads();
295
+ },
290
296
  threadEvents,
291
297
  isLoadingEvents
292
298
  }),
@@ -294,7 +300,7 @@ var ThreadProvider = ({
294
300
  threads,
295
301
  activeThreadId,
296
302
  isLoading,
297
- error,
303
+ threadsError,
298
304
  selectThread,
299
305
  createThread,
300
306
  deleteThread,
@@ -364,9 +370,16 @@ var useMelony = (options) => {
364
370
  }
365
371
  const { client, reset } = context;
366
372
  const { initialEvents } = options || {};
373
+ const prevInitialEventsRef = React10.useRef(void 0);
367
374
  React10.useEffect(() => {
368
- if (initialEvents && initialEvents.length > 0 && client.getState().events.length === 0) {
369
- reset(initialEvents);
375
+ const currentSerialized = initialEvents ? JSON.stringify(initialEvents) : void 0;
376
+ if (currentSerialized !== prevInitialEventsRef.current) {
377
+ if (initialEvents) {
378
+ reset(initialEvents);
379
+ } else {
380
+ reset([]);
381
+ }
382
+ prevInitialEventsRef.current = currentSerialized;
370
383
  }
371
384
  }, [client, initialEvents, reset]);
372
385
  return context;
@@ -2337,13 +2350,18 @@ function MessageList({
2337
2350
  function Thread({
2338
2351
  className,
2339
2352
  placeholder = "Type a message...",
2340
- starterPrompts,
2353
+ starterPrompts: localStarterPrompts,
2341
2354
  onStarterPromptClick,
2342
- options,
2355
+ options: localOptions,
2343
2356
  autoFocus = false,
2344
2357
  defaultSelectedIds
2345
2358
  }) {
2346
- const { messages, isLoading, error, sendEvent, loadingStatus } = useMelony();
2359
+ const { activeThreadId, threadEvents, isLoadingEvents } = useThreads();
2360
+ const { messages, isLoading, error, sendEvent, loadingStatus, config } = useMelony({
2361
+ initialEvents: threadEvents
2362
+ });
2363
+ const starterPrompts = localStarterPrompts ?? config?.starterPrompts;
2364
+ const options = localOptions ?? config?.options;
2347
2365
  const [input, setInput] = React10.useState("");
2348
2366
  const messagesEndRef = React10.useRef(null);
2349
2367
  React10.useEffect(() => {
@@ -2359,7 +2377,7 @@ function Thread({
2359
2377
  role: "user",
2360
2378
  data: { content: text }
2361
2379
  },
2362
- { state }
2380
+ { state: { ...state, threadId: activeThreadId ?? void 0 } }
2363
2381
  );
2364
2382
  };
2365
2383
  const handleStarterPromptClick = (prompt) => {
@@ -2369,21 +2387,21 @@ function Thread({
2369
2387
  handleSubmit(void 0, prompt);
2370
2388
  }
2371
2389
  };
2372
- const showStarterPrompts = messages.length === 0 && starterPrompts && starterPrompts.length > 0;
2390
+ const showStarterPrompts = messages.length === 0 && starterPrompts && starterPrompts.length > 0 && !isLoadingEvents;
2373
2391
  return /* @__PURE__ */ jsxRuntime.jsxs(
2374
2392
  "div",
2375
2393
  {
2376
2394
  className: cn("relative flex flex-col h-full bg-background", className),
2377
2395
  children: [
2378
2396
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 pb-36", children: [
2379
- /* @__PURE__ */ jsxRuntime.jsxs(
2397
+ /* @__PURE__ */ jsxRuntime.jsx(
2380
2398
  "div",
2381
2399
  {
2382
2400
  className: cn(
2383
2401
  "max-w-4xl mx-auto w-full p-4",
2384
2402
  showStarterPrompts && "min-h-full flex flex-col"
2385
2403
  ),
2386
- children: [
2404
+ children: isLoadingEvents && messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator, { status: { message: "Loading messages..." } }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2387
2405
  showStarterPrompts && /* @__PURE__ */ jsxRuntime.jsx(
2388
2406
  StarterPrompts,
2389
2407
  {
@@ -2400,7 +2418,7 @@ function Thread({
2400
2418
  loadingStatus
2401
2419
  }
2402
2420
  )
2403
- ]
2421
+ ] })
2404
2422
  }
2405
2423
  ),
2406
2424
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
@@ -2443,7 +2461,14 @@ var ThreadList = ({
2443
2461
  emptyState,
2444
2462
  onThreadSelect
2445
2463
  }) => {
2446
- const { threads, activeThreadId, selectThread, createThread, deleteThread } = useThreads();
2464
+ const {
2465
+ threads,
2466
+ activeThreadId,
2467
+ selectThread,
2468
+ createThread,
2469
+ deleteThread,
2470
+ isLoading
2471
+ } = useThreads();
2447
2472
  const handleThreadClick = (threadId) => {
2448
2473
  if (threadId !== activeThreadId) {
2449
2474
  selectThread(threadId);
@@ -2494,7 +2519,7 @@ var ThreadList = ({
2494
2519
  ]
2495
2520
  }
2496
2521
  ) }),
2497
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: threads.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-muted-foreground", children: emptyState || /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2522
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading && threads.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconLoader2, { className: "size-5 animate-spin text-muted-foreground" }) }) : threads.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-muted-foreground", children: emptyState || /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2498
2523
  /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconMessage, { className: "size-8 mx-auto opacity-50" }),
2499
2524
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No threads yet" }),
2500
2525
  /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: handleNewThread, children: "Start a conversation" })