@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 +131 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +133 -108
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
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
|
|
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
|
-
|
|
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 [
|
|
188
|
-
|
|
189
|
-
initialThreadId
|
|
230
|
+
const [activeThreadId, setActiveThreadId] = nuqs.useQueryState(
|
|
231
|
+
"threadId",
|
|
232
|
+
nuqs.parseAsString.withDefault(initialThreadId)
|
|
190
233
|
);
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
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 {
|
|
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.
|
|
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 {
|
|
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" })
|