@melony/react 0.2.5 → 0.2.6
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 +23 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.js +24 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -84,25 +84,32 @@ var MelonyProvider = ({
|
|
|
84
84
|
() => convertEventsToAggregatedMessages(state.events, aggregationOptions),
|
|
85
85
|
[state.events, aggregationOptions]
|
|
86
86
|
);
|
|
87
|
+
const send = react.useCallback(async (event, additionalBody) => {
|
|
88
|
+
const handler = eventHandlers?.[event.type];
|
|
89
|
+
if (handler) {
|
|
90
|
+
await handler(event, { client });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });
|
|
94
|
+
for await (const _ of generator) {
|
|
95
|
+
}
|
|
96
|
+
}, [client, eventHandlers, initialAdditionalBody]);
|
|
97
|
+
const reset = react.useCallback((events = []) => {
|
|
98
|
+
client.reset(events);
|
|
99
|
+
}, [client]);
|
|
100
|
+
const stop = react.useCallback(() => {
|
|
101
|
+
client.stop();
|
|
102
|
+
}, [client]);
|
|
87
103
|
const contextValue = react.useMemo(
|
|
88
104
|
() => ({
|
|
89
105
|
...state,
|
|
90
106
|
messages,
|
|
91
|
-
send
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
await handler(event, { client });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });
|
|
98
|
-
for await (const _ of generator) {
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
reset: client.reset.bind(client),
|
|
102
|
-
stop: client.stop.bind(client),
|
|
107
|
+
send,
|
|
108
|
+
reset,
|
|
109
|
+
stop,
|
|
103
110
|
client
|
|
104
111
|
}),
|
|
105
|
-
[state, messages, client]
|
|
112
|
+
[state, messages, send, reset, stop, client]
|
|
106
113
|
);
|
|
107
114
|
return /* @__PURE__ */ jsxRuntime.jsx(MelonyContext.Provider, { value: contextValue, children });
|
|
108
115
|
};
|
|
@@ -117,6 +124,7 @@ function useMelonyInit(url, params = {}) {
|
|
|
117
124
|
const [data, setData] = react.useState(null);
|
|
118
125
|
const [loading, setLoading] = react.useState(true);
|
|
119
126
|
const [error, setError] = react.useState(null);
|
|
127
|
+
const { client } = useMelony();
|
|
120
128
|
react.useEffect(() => {
|
|
121
129
|
let isMounted = true;
|
|
122
130
|
async function init() {
|
|
@@ -135,9 +143,9 @@ function useMelonyInit(url, params = {}) {
|
|
|
135
143
|
if (!response.ok) {
|
|
136
144
|
throw new Error(`Failed to initialize: ${response.statusText}`);
|
|
137
145
|
}
|
|
138
|
-
const
|
|
146
|
+
const data2 = await response.json();
|
|
139
147
|
if (isMounted) {
|
|
140
|
-
setData(
|
|
148
|
+
setData(data2);
|
|
141
149
|
}
|
|
142
150
|
} catch (err) {
|
|
143
151
|
if (isMounted) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/message-converter.ts","../src/providers/melony-provider.tsx","../src/hooks/use-melony.ts","../src/hooks/use-melony-init.ts"],"names":["createContext","useState","useEffect","useMemo","useContext"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Event, RuntimeContext } from \"melony\";\n\n/**\n * A message aggregated from multiple events.\n */\nexport interface AggregatedMessage {\n /** The role of the sender (user, assistant, or system) */\n role: string;\n /** The content of the message, containing the events */\n content: Event[];\n /** The unique ID for the run that produced this message */\n runId?: string;\n /** The ID of the thread this message belongs to */\n threadId?: string;\n}\n\n/**\n * Options for configuring how events are aggregated into messages.\n */\nexport interface AggregateOptions<TEvent extends Event = Event> {\n /**\n * Custom logic to extract the role from an event.\n * Defaults to event.meta.role or 'assistant'.\n */\n getRole?: (event: TEvent) => string;\n\n /**\n * Custom logic to extract the runId from an event.\n * Defaults to event.meta.runId.\n */\n getRunId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to extract the threadId from an event.\n * Defaults to event.meta.threadId.\n */\n getThreadId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to determine if a new message should be started.\n * By default, starts a new message if:\n * 1. There is no current message.\n * 2. The role of the event is different from the current message.\n * 3. The runId of the event is different from the current message's runId.\n */\n shouldStartNewMessage?: (\n event: TEvent,\n currentMessage: AggregatedMessage | null,\n options: { getRole: (e: TEvent) => string; getRunId: (e: TEvent) => string | undefined }\n ) => boolean;\n\n /**\n * Custom logic to process an event and update the current message.\n * By default, events are simply added to the message's content array.\n */\n processEvent?: (\n event: TEvent,\n currentMessage: AggregatedMessage,\n ) => void;\n}\n\n/**\n * Default implementation for extracting role from an event.\n */\nexport const defaultGetRole = <T extends Event>(e: T): string => e.type === \"user:text\" ? \"user\" : \"assistant\";\n\n/**\n * Default implementation for extracting runId from an event.\n */\nexport const defaultGetRunId = <T extends Event>(e: T, context?: RuntimeContext<any, T>): string | undefined => {\n return context?.runId;\n};\n\n/**\n * Default implementation for extracting threadId from an event.\n */\nexport const defaultGetThreadId = <T extends Event>(e: T): string | undefined => e.data?.threadId;\n\n/**\n * Default logic for determining if a new message should start.\n */\nexport const defaultShouldStartNewMessage = <T extends Event>(\n event: T,\n current: AggregatedMessage | null,\n utils: { getRole: (e: T) => string; getRunId: (e: T) => string | undefined; getThreadId: (e: T) => string | undefined }\n): boolean => {\n if (!current) return true;\n const role = utils.getRole(event);\n const runId = utils.getRunId(event);\n \n // Start new message if role changes\n if (current.role !== role) return true;\n \n // Start new message if runId changes (and both have runIds)\n if (runId && current.runId && runId !== current.runId) return true;\n \n return false;\n};\n\n/**\n * Default logic for processing an event into a message.\n */\nexport const defaultProcessEvent = <T extends Event>(event: T, current: AggregatedMessage): void => {\n current.content.push(event);\n};\n\n/**\n * Helper to aggregate a list of events into a list of messages.\n * This is useful for rendering a chat-like interface from a raw event stream.\n * \n * @param events The list of events to aggregate.\n * @param options Configuration for aggregation logic.\n * @returns An array of aggregated messages.\n */\nexport function convertEventsToAggregatedMessages<TEvent extends Event = Event>(\n events: TEvent[],\n options: AggregateOptions<TEvent> = {}\n): AggregatedMessage[] {\n const getRole = options.getRole || defaultGetRole;\n const getRunId = options.getRunId || defaultGetRunId;\n const getThreadId = options.getThreadId || defaultGetThreadId;\n const shouldStartNewMessage = options.shouldStartNewMessage || defaultShouldStartNewMessage;\n const processEvent = options.processEvent || defaultProcessEvent;\n\n if (events.length === 0) return [];\n\n const messages: AggregatedMessage[] = [];\n let currentMessage: AggregatedMessage | null = null;\n\n for (const event of events) {\n if (shouldStartNewMessage(event, currentMessage, { getRole, getRunId, getThreadId })) {\n currentMessage = {\n role: getRole(event),\n content: [],\n runId: getRunId(event),\n threadId: getThreadId(event),\n };\n messages.push(currentMessage);\n }\n\n if (currentMessage) {\n processEvent(event, currentMessage);\n \n // If current message didn't have a runId but this event does, update it\n if (!currentMessage.runId) {\n const runId = getRunId(event);\n if (runId) {\n currentMessage.runId = runId;\n }\n }\n\n // If current message didn't have a threadId but this event does, update it\n if (!currentMessage.threadId) {\n const threadId = getThreadId(event);\n if (threadId) {\n currentMessage.threadId = threadId;\n }\n }\n }\n }\n\n return messages;\n}\n","import React, {\n createContext,\n useEffect,\n useState,\n useMemo,\n ReactNode,\n} from \"react\";\nimport { MelonyClient, ClientState } from \"melony/client\";\nimport {\n Config,\n Event,\n} from \"melony\";\nimport {\n AggregatedMessage,\n AggregateOptions,\n convertEventsToAggregatedMessages\n} from \"../utils/message-converter\";\n\nexport interface MelonyContextValue extends ClientState {\n send: (event: Event, additionalBody?: Record<string, any>) => Promise<void>;\n reset: (events?: Event[]) => void;\n stop: () => void;\n client: MelonyClient;\n config?: Config;\n messages: AggregatedMessage[];\n}\n\nexport const MelonyContext = createContext<MelonyContextValue | undefined>(\n undefined,\n);\n\nexport type ClientHandler<TEvent extends Event = Event> = (\n event: TEvent,\n context: { client: MelonyClient<TEvent> }\n) => void | Promise<void>;\n\nexport interface MelonyProviderProps {\n children: ReactNode;\n client: MelonyClient;\n initialEvents?: Event[];\n initialAdditionalBody?: Record<string, any>;\n aggregationOptions?: AggregateOptions;\n eventHandlers?: Record<string, ClientHandler>;\n}\n\nexport const MelonyProvider: React.FC<MelonyProviderProps> = ({\n children,\n client,\n initialEvents,\n initialAdditionalBody,\n aggregationOptions,\n eventHandlers,\n}) => {\n const [state, setState] = useState<ClientState>(client.getState());\n\n // Handle initial events on mount only\n useEffect(() => {\n if (initialEvents?.length && client.getState().events.length === 0) {\n client.reset(initialEvents);\n }\n }, []); // Empty deps - run once on mount\n\n // Subscribe to state changes\n useEffect(() => {\n const unsubscribe = client.subscribe(setState);\n return unsubscribe;\n }, [client]);\n\n const messages = useMemo(\n () => convertEventsToAggregatedMessages(state.events, aggregationOptions),\n [state.events, aggregationOptions]\n );\n\n const contextValue = useMemo(\n () => ({\n ...state,\n messages,\n send: async (event: Event, additionalBody?: Record<string, any>) => {\n const handler = eventHandlers?.[event.type];\n if (handler) {\n await handler(event, { client });\n return;\n }\n\n const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });\n // Consume the generator to ensure event processing completes\n for await (const _ of generator) {\n // Events are handled by the client subscription\n }\n },\n reset: client.reset.bind(client),\n stop: client.stop.bind(client),\n client,\n }),\n [state, messages, client],\n );\n\n return (\n <MelonyContext.Provider value={contextValue}>\n {children}\n </MelonyContext.Provider>\n );\n};","import { useContext } from \"react\";\nimport { MelonyContext, MelonyContextValue } from \"../providers/melony-provider\";\n\nexport const useMelony = (): MelonyContextValue => {\n const context = useContext(MelonyContext);\n if (context === undefined) {\n throw new Error(\"useMelony must be used within a MelonyProvider\");\n }\n\n return context;\n};\n","import { useEffect, useState } from \"react\";\n\n/**\n * A hook to initialize a Melony application by fetching the initial UI state.\n * \n * @param url The API endpoint to fetch the initial state from.\n * @param params Optional query parameters to include in the request.\n * @returns An object containing the UI data, loading state, and any error encountered.\n */\nexport function useMelonyInit<T = any>(\n url: string, \n params: Record<string, string | number | boolean | undefined> = {}\n) {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let isMounted = true;\n\n async function init() {\n try {\n setLoading(true);\n setError(null);\n\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n \n const queryString = searchParams.toString();\n const fullUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n \n const response = await fetch(fullUrl);\n if (!response.ok) {\n throw new Error(`Failed to initialize: ${response.statusText}`);\n }\n const json = await response.json();\n \n if (isMounted) {\n setData(json);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n }\n\n init();\n\n return () => {\n isMounted = false;\n };\n }, [url, JSON.stringify(params)]);\n\n return { data, loading, error };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/message-converter.ts","../src/providers/melony-provider.tsx","../src/hooks/use-melony.ts","../src/hooks/use-melony-init.ts"],"names":["createContext","useState","useEffect","useMemo","useCallback","useContext","data"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Event, RuntimeContext } from \"melony\";\n\n/**\n * A message aggregated from multiple events.\n */\nexport interface AggregatedMessage {\n /** The role of the sender (user, assistant, or system) */\n role: string;\n /** The content of the message, containing the events */\n content: Event[];\n /** The unique ID for the run that produced this message */\n runId?: string;\n /** The ID of the thread this message belongs to */\n threadId?: string;\n}\n\n/**\n * Options for configuring how events are aggregated into messages.\n */\nexport interface AggregateOptions<TEvent extends Event = Event> {\n /**\n * Custom logic to extract the role from an event.\n * Defaults to event.meta.role or 'assistant'.\n */\n getRole?: (event: TEvent) => string;\n\n /**\n * Custom logic to extract the runId from an event.\n * Defaults to event.meta.runId.\n */\n getRunId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to extract the threadId from an event.\n * Defaults to event.meta.threadId.\n */\n getThreadId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to determine if a new message should be started.\n * By default, starts a new message if:\n * 1. There is no current message.\n * 2. The role of the event is different from the current message.\n * 3. The runId of the event is different from the current message's runId.\n */\n shouldStartNewMessage?: (\n event: TEvent,\n currentMessage: AggregatedMessage | null,\n options: { getRole: (e: TEvent) => string; getRunId: (e: TEvent) => string | undefined }\n ) => boolean;\n\n /**\n * Custom logic to process an event and update the current message.\n * By default, events are simply added to the message's content array.\n */\n processEvent?: (\n event: TEvent,\n currentMessage: AggregatedMessage,\n ) => void;\n}\n\n/**\n * Default implementation for extracting role from an event.\n */\nexport const defaultGetRole = <T extends Event>(e: T): string => e.type === \"user:text\" ? \"user\" : \"assistant\";\n\n/**\n * Default implementation for extracting runId from an event.\n */\nexport const defaultGetRunId = <T extends Event>(e: T, context?: RuntimeContext<any, T>): string | undefined => {\n return context?.runId;\n};\n\n/**\n * Default implementation for extracting threadId from an event.\n */\nexport const defaultGetThreadId = <T extends Event>(e: T): string | undefined => e.data?.threadId;\n\n/**\n * Default logic for determining if a new message should start.\n */\nexport const defaultShouldStartNewMessage = <T extends Event>(\n event: T,\n current: AggregatedMessage | null,\n utils: { getRole: (e: T) => string; getRunId: (e: T) => string | undefined; getThreadId: (e: T) => string | undefined }\n): boolean => {\n if (!current) return true;\n const role = utils.getRole(event);\n const runId = utils.getRunId(event);\n \n // Start new message if role changes\n if (current.role !== role) return true;\n \n // Start new message if runId changes (and both have runIds)\n if (runId && current.runId && runId !== current.runId) return true;\n \n return false;\n};\n\n/**\n * Default logic for processing an event into a message.\n */\nexport const defaultProcessEvent = <T extends Event>(event: T, current: AggregatedMessage): void => {\n current.content.push(event);\n};\n\n/**\n * Helper to aggregate a list of events into a list of messages.\n * This is useful for rendering a chat-like interface from a raw event stream.\n * \n * @param events The list of events to aggregate.\n * @param options Configuration for aggregation logic.\n * @returns An array of aggregated messages.\n */\nexport function convertEventsToAggregatedMessages<TEvent extends Event = Event>(\n events: TEvent[],\n options: AggregateOptions<TEvent> = {}\n): AggregatedMessage[] {\n const getRole = options.getRole || defaultGetRole;\n const getRunId = options.getRunId || defaultGetRunId;\n const getThreadId = options.getThreadId || defaultGetThreadId;\n const shouldStartNewMessage = options.shouldStartNewMessage || defaultShouldStartNewMessage;\n const processEvent = options.processEvent || defaultProcessEvent;\n\n if (events.length === 0) return [];\n\n const messages: AggregatedMessage[] = [];\n let currentMessage: AggregatedMessage | null = null;\n\n for (const event of events) {\n if (shouldStartNewMessage(event, currentMessage, { getRole, getRunId, getThreadId })) {\n currentMessage = {\n role: getRole(event),\n content: [],\n runId: getRunId(event),\n threadId: getThreadId(event),\n };\n messages.push(currentMessage);\n }\n\n if (currentMessage) {\n processEvent(event, currentMessage);\n \n // If current message didn't have a runId but this event does, update it\n if (!currentMessage.runId) {\n const runId = getRunId(event);\n if (runId) {\n currentMessage.runId = runId;\n }\n }\n\n // If current message didn't have a threadId but this event does, update it\n if (!currentMessage.threadId) {\n const threadId = getThreadId(event);\n if (threadId) {\n currentMessage.threadId = threadId;\n }\n }\n }\n }\n\n return messages;\n}\n","import React, {\n createContext,\n useEffect,\n useState,\n useMemo,\n useCallback,\n ReactNode,\n} from \"react\";\nimport { MelonyClient, ClientState } from \"melony/client\";\nimport {\n Config,\n Event,\n} from \"melony\";\nimport {\n AggregatedMessage,\n AggregateOptions,\n convertEventsToAggregatedMessages\n} from \"../utils/message-converter\";\n\nexport interface MelonyContextValue extends ClientState {\n send: (event: Event, additionalBody?: Record<string, any>) => Promise<void>;\n reset: (events?: Event[]) => void;\n stop: () => void;\n client: MelonyClient;\n config?: Config;\n messages: AggregatedMessage[];\n}\n\nexport const MelonyContext = createContext<MelonyContextValue | undefined>(\n undefined,\n);\n\nexport type ClientHandler<TEvent extends Event = Event> = (\n event: TEvent,\n context: { client: MelonyClient<TEvent> }\n) => void | Promise<void>;\n\nexport interface MelonyProviderProps {\n children: ReactNode;\n client: MelonyClient;\n initialEvents?: Event[];\n initialAdditionalBody?: Record<string, any>;\n aggregationOptions?: AggregateOptions;\n eventHandlers?: Record<string, ClientHandler>;\n}\n\nexport const MelonyProvider: React.FC<MelonyProviderProps> = ({\n children,\n client,\n initialEvents,\n initialAdditionalBody,\n aggregationOptions,\n eventHandlers,\n}) => {\n const [state, setState] = useState<ClientState>(client.getState());\n\n // Handle initial events on mount only\n useEffect(() => {\n if (initialEvents?.length && client.getState().events.length === 0) {\n client.reset(initialEvents);\n }\n }, []); // Empty deps - run once on mount\n\n // Subscribe to state changes\n useEffect(() => {\n const unsubscribe = client.subscribe(setState);\n return unsubscribe;\n }, [client]);\n\n const messages = useMemo(\n () => convertEventsToAggregatedMessages(state.events, aggregationOptions),\n [state.events, aggregationOptions]\n );\n\n const send = useCallback(async (event: Event, additionalBody?: Record<string, any>) => {\n const handler = eventHandlers?.[event.type];\n if (handler) {\n await handler(event, { client });\n return;\n }\n\n const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });\n // Consume the generator to ensure event processing completes\n for await (const _ of generator) {\n // Events are handled by the client subscription\n }\n }, [client, eventHandlers, initialAdditionalBody]);\n\n const reset = useCallback((events: Event[] = []) => {\n client.reset(events);\n }, [client]);\n\n const stop = useCallback(() => {\n client.stop();\n }, [client]);\n\n const contextValue = useMemo(\n () => ({\n ...state,\n messages,\n send,\n reset,\n stop,\n client,\n }),\n [state, messages, send, reset, stop, client],\n );\n\n return (\n <MelonyContext.Provider value={contextValue}>\n {children}\n </MelonyContext.Provider>\n );\n};","import { useContext } from \"react\";\nimport { MelonyContext, MelonyContextValue } from \"../providers/melony-provider\";\n\nexport const useMelony = (): MelonyContextValue => {\n const context = useContext(MelonyContext);\n if (context === undefined) {\n throw new Error(\"useMelony must be used within a MelonyProvider\");\n }\n\n return context;\n};\n","import { useEffect, useState } from \"react\";\nimport { useMelony } from \"./use-melony\";\n\n/**\n * Fetches the initial UI state from a jsonResponse endpoint.\n */\nexport function useMelonyInit<T = any>(\n url: string,\n params: Record<string, string | number | boolean | undefined> = {}\n) {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const { client } = useMelony();\n\n useEffect(() => {\n let isMounted = true;\n\n async function init() {\n try {\n setLoading(true);\n setError(null);\n\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n\n const queryString = searchParams.toString();\n const fullUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n\n const response = await fetch(fullUrl);\n if (!response.ok) {\n throw new Error(`Failed to initialize: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (isMounted) {\n setData(data);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n }\n\n init();\n\n return () => {\n isMounted = false;\n };\n }, [url, JSON.stringify(params)]);\n\n return { data, loading, error };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -110,11 +110,7 @@ declare const MelonyProvider: React.FC<MelonyProviderProps>;
|
|
|
110
110
|
declare const useMelony: () => MelonyContextValue;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @param url The API endpoint to fetch the initial state from.
|
|
116
|
-
* @param params Optional query parameters to include in the request.
|
|
117
|
-
* @returns An object containing the UI data, loading state, and any error encountered.
|
|
113
|
+
* Fetches the initial UI state from a jsonResponse endpoint.
|
|
118
114
|
*/
|
|
119
115
|
declare function useMelonyInit<T = any>(url: string, params?: Record<string, string | number | boolean | undefined>): {
|
|
120
116
|
data: T | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -110,11 +110,7 @@ declare const MelonyProvider: React.FC<MelonyProviderProps>;
|
|
|
110
110
|
declare const useMelony: () => MelonyContextValue;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @param url The API endpoint to fetch the initial state from.
|
|
116
|
-
* @param params Optional query parameters to include in the request.
|
|
117
|
-
* @returns An object containing the UI data, loading state, and any error encountered.
|
|
113
|
+
* Fetches the initial UI state from a jsonResponse endpoint.
|
|
118
114
|
*/
|
|
119
115
|
declare function useMelonyInit<T = any>(url: string, params?: Record<string, string | number | boolean | undefined>): {
|
|
120
116
|
data: T | null;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useState, useEffect, useMemo, useContext } from 'react';
|
|
1
|
+
import { createContext, useState, useEffect, useMemo, useCallback, useContext } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/providers/melony-provider.tsx
|
|
@@ -82,25 +82,32 @@ var MelonyProvider = ({
|
|
|
82
82
|
() => convertEventsToAggregatedMessages(state.events, aggregationOptions),
|
|
83
83
|
[state.events, aggregationOptions]
|
|
84
84
|
);
|
|
85
|
+
const send = useCallback(async (event, additionalBody) => {
|
|
86
|
+
const handler = eventHandlers?.[event.type];
|
|
87
|
+
if (handler) {
|
|
88
|
+
await handler(event, { client });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });
|
|
92
|
+
for await (const _ of generator) {
|
|
93
|
+
}
|
|
94
|
+
}, [client, eventHandlers, initialAdditionalBody]);
|
|
95
|
+
const reset = useCallback((events = []) => {
|
|
96
|
+
client.reset(events);
|
|
97
|
+
}, [client]);
|
|
98
|
+
const stop = useCallback(() => {
|
|
99
|
+
client.stop();
|
|
100
|
+
}, [client]);
|
|
85
101
|
const contextValue = useMemo(
|
|
86
102
|
() => ({
|
|
87
103
|
...state,
|
|
88
104
|
messages,
|
|
89
|
-
send
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await handler(event, { client });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });
|
|
96
|
-
for await (const _ of generator) {
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
reset: client.reset.bind(client),
|
|
100
|
-
stop: client.stop.bind(client),
|
|
105
|
+
send,
|
|
106
|
+
reset,
|
|
107
|
+
stop,
|
|
101
108
|
client
|
|
102
109
|
}),
|
|
103
|
-
[state, messages, client]
|
|
110
|
+
[state, messages, send, reset, stop, client]
|
|
104
111
|
);
|
|
105
112
|
return /* @__PURE__ */ jsx(MelonyContext.Provider, { value: contextValue, children });
|
|
106
113
|
};
|
|
@@ -115,6 +122,7 @@ function useMelonyInit(url, params = {}) {
|
|
|
115
122
|
const [data, setData] = useState(null);
|
|
116
123
|
const [loading, setLoading] = useState(true);
|
|
117
124
|
const [error, setError] = useState(null);
|
|
125
|
+
const { client } = useMelony();
|
|
118
126
|
useEffect(() => {
|
|
119
127
|
let isMounted = true;
|
|
120
128
|
async function init() {
|
|
@@ -133,9 +141,9 @@ function useMelonyInit(url, params = {}) {
|
|
|
133
141
|
if (!response.ok) {
|
|
134
142
|
throw new Error(`Failed to initialize: ${response.statusText}`);
|
|
135
143
|
}
|
|
136
|
-
const
|
|
144
|
+
const data2 = await response.json();
|
|
137
145
|
if (isMounted) {
|
|
138
|
-
setData(
|
|
146
|
+
setData(data2);
|
|
139
147
|
}
|
|
140
148
|
} catch (err) {
|
|
141
149
|
if (isMounted) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/message-converter.ts","../src/providers/melony-provider.tsx","../src/hooks/use-melony.ts","../src/hooks/use-melony-init.ts"],"names":["useState","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { Event, RuntimeContext } from \"melony\";\n\n/**\n * A message aggregated from multiple events.\n */\nexport interface AggregatedMessage {\n /** The role of the sender (user, assistant, or system) */\n role: string;\n /** The content of the message, containing the events */\n content: Event[];\n /** The unique ID for the run that produced this message */\n runId?: string;\n /** The ID of the thread this message belongs to */\n threadId?: string;\n}\n\n/**\n * Options for configuring how events are aggregated into messages.\n */\nexport interface AggregateOptions<TEvent extends Event = Event> {\n /**\n * Custom logic to extract the role from an event.\n * Defaults to event.meta.role or 'assistant'.\n */\n getRole?: (event: TEvent) => string;\n\n /**\n * Custom logic to extract the runId from an event.\n * Defaults to event.meta.runId.\n */\n getRunId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to extract the threadId from an event.\n * Defaults to event.meta.threadId.\n */\n getThreadId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to determine if a new message should be started.\n * By default, starts a new message if:\n * 1. There is no current message.\n * 2. The role of the event is different from the current message.\n * 3. The runId of the event is different from the current message's runId.\n */\n shouldStartNewMessage?: (\n event: TEvent,\n currentMessage: AggregatedMessage | null,\n options: { getRole: (e: TEvent) => string; getRunId: (e: TEvent) => string | undefined }\n ) => boolean;\n\n /**\n * Custom logic to process an event and update the current message.\n * By default, events are simply added to the message's content array.\n */\n processEvent?: (\n event: TEvent,\n currentMessage: AggregatedMessage,\n ) => void;\n}\n\n/**\n * Default implementation for extracting role from an event.\n */\nexport const defaultGetRole = <T extends Event>(e: T): string => e.type === \"user:text\" ? \"user\" : \"assistant\";\n\n/**\n * Default implementation for extracting runId from an event.\n */\nexport const defaultGetRunId = <T extends Event>(e: T, context?: RuntimeContext<any, T>): string | undefined => {\n return context?.runId;\n};\n\n/**\n * Default implementation for extracting threadId from an event.\n */\nexport const defaultGetThreadId = <T extends Event>(e: T): string | undefined => e.data?.threadId;\n\n/**\n * Default logic for determining if a new message should start.\n */\nexport const defaultShouldStartNewMessage = <T extends Event>(\n event: T,\n current: AggregatedMessage | null,\n utils: { getRole: (e: T) => string; getRunId: (e: T) => string | undefined; getThreadId: (e: T) => string | undefined }\n): boolean => {\n if (!current) return true;\n const role = utils.getRole(event);\n const runId = utils.getRunId(event);\n \n // Start new message if role changes\n if (current.role !== role) return true;\n \n // Start new message if runId changes (and both have runIds)\n if (runId && current.runId && runId !== current.runId) return true;\n \n return false;\n};\n\n/**\n * Default logic for processing an event into a message.\n */\nexport const defaultProcessEvent = <T extends Event>(event: T, current: AggregatedMessage): void => {\n current.content.push(event);\n};\n\n/**\n * Helper to aggregate a list of events into a list of messages.\n * This is useful for rendering a chat-like interface from a raw event stream.\n * \n * @param events The list of events to aggregate.\n * @param options Configuration for aggregation logic.\n * @returns An array of aggregated messages.\n */\nexport function convertEventsToAggregatedMessages<TEvent extends Event = Event>(\n events: TEvent[],\n options: AggregateOptions<TEvent> = {}\n): AggregatedMessage[] {\n const getRole = options.getRole || defaultGetRole;\n const getRunId = options.getRunId || defaultGetRunId;\n const getThreadId = options.getThreadId || defaultGetThreadId;\n const shouldStartNewMessage = options.shouldStartNewMessage || defaultShouldStartNewMessage;\n const processEvent = options.processEvent || defaultProcessEvent;\n\n if (events.length === 0) return [];\n\n const messages: AggregatedMessage[] = [];\n let currentMessage: AggregatedMessage | null = null;\n\n for (const event of events) {\n if (shouldStartNewMessage(event, currentMessage, { getRole, getRunId, getThreadId })) {\n currentMessage = {\n role: getRole(event),\n content: [],\n runId: getRunId(event),\n threadId: getThreadId(event),\n };\n messages.push(currentMessage);\n }\n\n if (currentMessage) {\n processEvent(event, currentMessage);\n \n // If current message didn't have a runId but this event does, update it\n if (!currentMessage.runId) {\n const runId = getRunId(event);\n if (runId) {\n currentMessage.runId = runId;\n }\n }\n\n // If current message didn't have a threadId but this event does, update it\n if (!currentMessage.threadId) {\n const threadId = getThreadId(event);\n if (threadId) {\n currentMessage.threadId = threadId;\n }\n }\n }\n }\n\n return messages;\n}\n","import React, {\n createContext,\n useEffect,\n useState,\n useMemo,\n ReactNode,\n} from \"react\";\nimport { MelonyClient, ClientState } from \"melony/client\";\nimport {\n Config,\n Event,\n} from \"melony\";\nimport {\n AggregatedMessage,\n AggregateOptions,\n convertEventsToAggregatedMessages\n} from \"../utils/message-converter\";\n\nexport interface MelonyContextValue extends ClientState {\n send: (event: Event, additionalBody?: Record<string, any>) => Promise<void>;\n reset: (events?: Event[]) => void;\n stop: () => void;\n client: MelonyClient;\n config?: Config;\n messages: AggregatedMessage[];\n}\n\nexport const MelonyContext = createContext<MelonyContextValue | undefined>(\n undefined,\n);\n\nexport type ClientHandler<TEvent extends Event = Event> = (\n event: TEvent,\n context: { client: MelonyClient<TEvent> }\n) => void | Promise<void>;\n\nexport interface MelonyProviderProps {\n children: ReactNode;\n client: MelonyClient;\n initialEvents?: Event[];\n initialAdditionalBody?: Record<string, any>;\n aggregationOptions?: AggregateOptions;\n eventHandlers?: Record<string, ClientHandler>;\n}\n\nexport const MelonyProvider: React.FC<MelonyProviderProps> = ({\n children,\n client,\n initialEvents,\n initialAdditionalBody,\n aggregationOptions,\n eventHandlers,\n}) => {\n const [state, setState] = useState<ClientState>(client.getState());\n\n // Handle initial events on mount only\n useEffect(() => {\n if (initialEvents?.length && client.getState().events.length === 0) {\n client.reset(initialEvents);\n }\n }, []); // Empty deps - run once on mount\n\n // Subscribe to state changes\n useEffect(() => {\n const unsubscribe = client.subscribe(setState);\n return unsubscribe;\n }, [client]);\n\n const messages = useMemo(\n () => convertEventsToAggregatedMessages(state.events, aggregationOptions),\n [state.events, aggregationOptions]\n );\n\n const contextValue = useMemo(\n () => ({\n ...state,\n messages,\n send: async (event: Event, additionalBody?: Record<string, any>) => {\n const handler = eventHandlers?.[event.type];\n if (handler) {\n await handler(event, { client });\n return;\n }\n\n const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });\n // Consume the generator to ensure event processing completes\n for await (const _ of generator) {\n // Events are handled by the client subscription\n }\n },\n reset: client.reset.bind(client),\n stop: client.stop.bind(client),\n client,\n }),\n [state, messages, client],\n );\n\n return (\n <MelonyContext.Provider value={contextValue}>\n {children}\n </MelonyContext.Provider>\n );\n};","import { useContext } from \"react\";\nimport { MelonyContext, MelonyContextValue } from \"../providers/melony-provider\";\n\nexport const useMelony = (): MelonyContextValue => {\n const context = useContext(MelonyContext);\n if (context === undefined) {\n throw new Error(\"useMelony must be used within a MelonyProvider\");\n }\n\n return context;\n};\n","import { useEffect, useState } from \"react\";\n\n/**\n * A hook to initialize a Melony application by fetching the initial UI state.\n * \n * @param url The API endpoint to fetch the initial state from.\n * @param params Optional query parameters to include in the request.\n * @returns An object containing the UI data, loading state, and any error encountered.\n */\nexport function useMelonyInit<T = any>(\n url: string, \n params: Record<string, string | number | boolean | undefined> = {}\n) {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let isMounted = true;\n\n async function init() {\n try {\n setLoading(true);\n setError(null);\n\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n \n const queryString = searchParams.toString();\n const fullUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n \n const response = await fetch(fullUrl);\n if (!response.ok) {\n throw new Error(`Failed to initialize: ${response.statusText}`);\n }\n const json = await response.json();\n \n if (isMounted) {\n setData(json);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n }\n\n init();\n\n return () => {\n isMounted = false;\n };\n }, [url, JSON.stringify(params)]);\n\n return { data, loading, error };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/message-converter.ts","../src/providers/melony-provider.tsx","../src/hooks/use-melony.ts","../src/hooks/use-melony-init.ts"],"names":["useState","useEffect","data"],"mappings":"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","file":"index.js","sourcesContent":["import { Event, RuntimeContext } from \"melony\";\n\n/**\n * A message aggregated from multiple events.\n */\nexport interface AggregatedMessage {\n /** The role of the sender (user, assistant, or system) */\n role: string;\n /** The content of the message, containing the events */\n content: Event[];\n /** The unique ID for the run that produced this message */\n runId?: string;\n /** The ID of the thread this message belongs to */\n threadId?: string;\n}\n\n/**\n * Options for configuring how events are aggregated into messages.\n */\nexport interface AggregateOptions<TEvent extends Event = Event> {\n /**\n * Custom logic to extract the role from an event.\n * Defaults to event.meta.role or 'assistant'.\n */\n getRole?: (event: TEvent) => string;\n\n /**\n * Custom logic to extract the runId from an event.\n * Defaults to event.meta.runId.\n */\n getRunId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to extract the threadId from an event.\n * Defaults to event.meta.threadId.\n */\n getThreadId?: (event: TEvent) => string | undefined;\n\n /**\n * Custom logic to determine if a new message should be started.\n * By default, starts a new message if:\n * 1. There is no current message.\n * 2. The role of the event is different from the current message.\n * 3. The runId of the event is different from the current message's runId.\n */\n shouldStartNewMessage?: (\n event: TEvent,\n currentMessage: AggregatedMessage | null,\n options: { getRole: (e: TEvent) => string; getRunId: (e: TEvent) => string | undefined }\n ) => boolean;\n\n /**\n * Custom logic to process an event and update the current message.\n * By default, events are simply added to the message's content array.\n */\n processEvent?: (\n event: TEvent,\n currentMessage: AggregatedMessage,\n ) => void;\n}\n\n/**\n * Default implementation for extracting role from an event.\n */\nexport const defaultGetRole = <T extends Event>(e: T): string => e.type === \"user:text\" ? \"user\" : \"assistant\";\n\n/**\n * Default implementation for extracting runId from an event.\n */\nexport const defaultGetRunId = <T extends Event>(e: T, context?: RuntimeContext<any, T>): string | undefined => {\n return context?.runId;\n};\n\n/**\n * Default implementation for extracting threadId from an event.\n */\nexport const defaultGetThreadId = <T extends Event>(e: T): string | undefined => e.data?.threadId;\n\n/**\n * Default logic for determining if a new message should start.\n */\nexport const defaultShouldStartNewMessage = <T extends Event>(\n event: T,\n current: AggregatedMessage | null,\n utils: { getRole: (e: T) => string; getRunId: (e: T) => string | undefined; getThreadId: (e: T) => string | undefined }\n): boolean => {\n if (!current) return true;\n const role = utils.getRole(event);\n const runId = utils.getRunId(event);\n \n // Start new message if role changes\n if (current.role !== role) return true;\n \n // Start new message if runId changes (and both have runIds)\n if (runId && current.runId && runId !== current.runId) return true;\n \n return false;\n};\n\n/**\n * Default logic for processing an event into a message.\n */\nexport const defaultProcessEvent = <T extends Event>(event: T, current: AggregatedMessage): void => {\n current.content.push(event);\n};\n\n/**\n * Helper to aggregate a list of events into a list of messages.\n * This is useful for rendering a chat-like interface from a raw event stream.\n * \n * @param events The list of events to aggregate.\n * @param options Configuration for aggregation logic.\n * @returns An array of aggregated messages.\n */\nexport function convertEventsToAggregatedMessages<TEvent extends Event = Event>(\n events: TEvent[],\n options: AggregateOptions<TEvent> = {}\n): AggregatedMessage[] {\n const getRole = options.getRole || defaultGetRole;\n const getRunId = options.getRunId || defaultGetRunId;\n const getThreadId = options.getThreadId || defaultGetThreadId;\n const shouldStartNewMessage = options.shouldStartNewMessage || defaultShouldStartNewMessage;\n const processEvent = options.processEvent || defaultProcessEvent;\n\n if (events.length === 0) return [];\n\n const messages: AggregatedMessage[] = [];\n let currentMessage: AggregatedMessage | null = null;\n\n for (const event of events) {\n if (shouldStartNewMessage(event, currentMessage, { getRole, getRunId, getThreadId })) {\n currentMessage = {\n role: getRole(event),\n content: [],\n runId: getRunId(event),\n threadId: getThreadId(event),\n };\n messages.push(currentMessage);\n }\n\n if (currentMessage) {\n processEvent(event, currentMessage);\n \n // If current message didn't have a runId but this event does, update it\n if (!currentMessage.runId) {\n const runId = getRunId(event);\n if (runId) {\n currentMessage.runId = runId;\n }\n }\n\n // If current message didn't have a threadId but this event does, update it\n if (!currentMessage.threadId) {\n const threadId = getThreadId(event);\n if (threadId) {\n currentMessage.threadId = threadId;\n }\n }\n }\n }\n\n return messages;\n}\n","import React, {\n createContext,\n useEffect,\n useState,\n useMemo,\n useCallback,\n ReactNode,\n} from \"react\";\nimport { MelonyClient, ClientState } from \"melony/client\";\nimport {\n Config,\n Event,\n} from \"melony\";\nimport {\n AggregatedMessage,\n AggregateOptions,\n convertEventsToAggregatedMessages\n} from \"../utils/message-converter\";\n\nexport interface MelonyContextValue extends ClientState {\n send: (event: Event, additionalBody?: Record<string, any>) => Promise<void>;\n reset: (events?: Event[]) => void;\n stop: () => void;\n client: MelonyClient;\n config?: Config;\n messages: AggregatedMessage[];\n}\n\nexport const MelonyContext = createContext<MelonyContextValue | undefined>(\n undefined,\n);\n\nexport type ClientHandler<TEvent extends Event = Event> = (\n event: TEvent,\n context: { client: MelonyClient<TEvent> }\n) => void | Promise<void>;\n\nexport interface MelonyProviderProps {\n children: ReactNode;\n client: MelonyClient;\n initialEvents?: Event[];\n initialAdditionalBody?: Record<string, any>;\n aggregationOptions?: AggregateOptions;\n eventHandlers?: Record<string, ClientHandler>;\n}\n\nexport const MelonyProvider: React.FC<MelonyProviderProps> = ({\n children,\n client,\n initialEvents,\n initialAdditionalBody,\n aggregationOptions,\n eventHandlers,\n}) => {\n const [state, setState] = useState<ClientState>(client.getState());\n\n // Handle initial events on mount only\n useEffect(() => {\n if (initialEvents?.length && client.getState().events.length === 0) {\n client.reset(initialEvents);\n }\n }, []); // Empty deps - run once on mount\n\n // Subscribe to state changes\n useEffect(() => {\n const unsubscribe = client.subscribe(setState);\n return unsubscribe;\n }, [client]);\n\n const messages = useMemo(\n () => convertEventsToAggregatedMessages(state.events, aggregationOptions),\n [state.events, aggregationOptions]\n );\n\n const send = useCallback(async (event: Event, additionalBody?: Record<string, any>) => {\n const handler = eventHandlers?.[event.type];\n if (handler) {\n await handler(event, { client });\n return;\n }\n\n const generator = client.send(event, { ...initialAdditionalBody, ...additionalBody });\n // Consume the generator to ensure event processing completes\n for await (const _ of generator) {\n // Events are handled by the client subscription\n }\n }, [client, eventHandlers, initialAdditionalBody]);\n\n const reset = useCallback((events: Event[] = []) => {\n client.reset(events);\n }, [client]);\n\n const stop = useCallback(() => {\n client.stop();\n }, [client]);\n\n const contextValue = useMemo(\n () => ({\n ...state,\n messages,\n send,\n reset,\n stop,\n client,\n }),\n [state, messages, send, reset, stop, client],\n );\n\n return (\n <MelonyContext.Provider value={contextValue}>\n {children}\n </MelonyContext.Provider>\n );\n};","import { useContext } from \"react\";\nimport { MelonyContext, MelonyContextValue } from \"../providers/melony-provider\";\n\nexport const useMelony = (): MelonyContextValue => {\n const context = useContext(MelonyContext);\n if (context === undefined) {\n throw new Error(\"useMelony must be used within a MelonyProvider\");\n }\n\n return context;\n};\n","import { useEffect, useState } from \"react\";\nimport { useMelony } from \"./use-melony\";\n\n/**\n * Fetches the initial UI state from a jsonResponse endpoint.\n */\nexport function useMelonyInit<T = any>(\n url: string,\n params: Record<string, string | number | boolean | undefined> = {}\n) {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const { client } = useMelony();\n\n useEffect(() => {\n let isMounted = true;\n\n async function init() {\n try {\n setLoading(true);\n setError(null);\n\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n\n const queryString = searchParams.toString();\n const fullUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n\n const response = await fetch(fullUrl);\n if (!response.ok) {\n throw new Error(`Failed to initialize: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (isMounted) {\n setData(data);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setLoading(false);\n }\n }\n }\n\n init();\n\n return () => {\n isMounted = false;\n };\n }, [url, JSON.stringify(params)]);\n\n return { data, loading, error };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melony/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"react": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"melony": "^0.2.
|
|
36
|
+
"melony": "^0.2.7"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^19.1.12",
|