@melony/react 0.1.21 → 0.1.23

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,26 @@ 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;
2365
+ const allDefaultSelectedIds = React10.useMemo(() => {
2366
+ const defaultSelectedIdsFromOptions = options?.flatMap(
2367
+ (group) => group.defaultSelectedIds ?? []
2368
+ ) ?? [];
2369
+ return [
2370
+ .../* @__PURE__ */ new Set([...defaultSelectedIdsFromOptions, ...defaultSelectedIds ?? []])
2371
+ ];
2372
+ }, [options, defaultSelectedIds]);
2347
2373
  const [input, setInput] = React10.useState("");
2348
2374
  const messagesEndRef = React10.useRef(null);
2349
2375
  React10.useEffect(() => {
@@ -2359,7 +2385,7 @@ function Thread({
2359
2385
  role: "user",
2360
2386
  data: { content: text }
2361
2387
  },
2362
- { state }
2388
+ { state: { ...state, threadId: activeThreadId ?? void 0 } }
2363
2389
  );
2364
2390
  };
2365
2391
  const handleStarterPromptClick = (prompt) => {
@@ -2369,21 +2395,21 @@ function Thread({
2369
2395
  handleSubmit(void 0, prompt);
2370
2396
  }
2371
2397
  };
2372
- const showStarterPrompts = messages.length === 0 && starterPrompts && starterPrompts.length > 0;
2398
+ const showStarterPrompts = messages.length === 0 && starterPrompts && starterPrompts.length > 0 && !isLoadingEvents;
2373
2399
  return /* @__PURE__ */ jsxRuntime.jsxs(
2374
2400
  "div",
2375
2401
  {
2376
2402
  className: cn("relative flex flex-col h-full bg-background", className),
2377
2403
  children: [
2378
2404
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-4 pb-36", children: [
2379
- /* @__PURE__ */ jsxRuntime.jsxs(
2405
+ /* @__PURE__ */ jsxRuntime.jsx(
2380
2406
  "div",
2381
2407
  {
2382
2408
  className: cn(
2383
2409
  "max-w-4xl mx-auto w-full p-4",
2384
2410
  showStarterPrompts && "min-h-full flex flex-col"
2385
2411
  ),
2386
- children: [
2412
+ 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
2413
  showStarterPrompts && /* @__PURE__ */ jsxRuntime.jsx(
2388
2414
  StarterPrompts,
2389
2415
  {
@@ -2400,7 +2426,7 @@ function Thread({
2400
2426
  loadingStatus
2401
2427
  }
2402
2428
  )
2403
- ]
2429
+ ] })
2404
2430
  }
2405
2431
  ),
2406
2432
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
@@ -2415,7 +2441,7 @@ function Thread({
2415
2441
  isLoading,
2416
2442
  options,
2417
2443
  autoFocus,
2418
- defaultSelectedIds
2444
+ defaultSelectedIds: allDefaultSelectedIds
2419
2445
  }
2420
2446
  ) }) })
2421
2447
  ]
@@ -2443,7 +2469,14 @@ var ThreadList = ({
2443
2469
  emptyState,
2444
2470
  onThreadSelect
2445
2471
  }) => {
2446
- const { threads, activeThreadId, selectThread, createThread, deleteThread } = useThreads();
2472
+ const {
2473
+ threads,
2474
+ activeThreadId,
2475
+ selectThread,
2476
+ createThread,
2477
+ deleteThread,
2478
+ isLoading
2479
+ } = useThreads();
2447
2480
  const handleThreadClick = (threadId) => {
2448
2481
  if (threadId !== activeThreadId) {
2449
2482
  selectThread(threadId);
@@ -2494,7 +2527,7 @@ var ThreadList = ({
2494
2527
  ]
2495
2528
  }
2496
2529
  ) }),
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: [
2530
+ /* @__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
2531
  /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconMessage, { className: "size-8 mx-auto opacity-50" }),
2499
2532
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No threads yet" }),
2500
2533
  /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: handleNewThread, children: "Start a conversation" })