@deltakit/react 0.1.4 → 0.2.0
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 +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
fromAgnoAgents: () => import_core2.fromAgnoAgents,
|
|
23
24
|
fromOpenAiAgents: () => import_core2.fromOpenAiAgents,
|
|
24
25
|
parseSSEStream: () => import_core2.parseSSEStream,
|
|
25
26
|
useAutoScroll: () => useAutoScroll,
|
|
@@ -276,6 +277,7 @@ function useStreamChat(options) {
|
|
|
276
277
|
}
|
|
277
278
|
// Annotate the CommonJS export names for ESM import in node:
|
|
278
279
|
0 && (module.exports = {
|
|
280
|
+
fromAgnoAgents,
|
|
279
281
|
fromOpenAiAgents,
|
|
280
282
|
parseSSEStream,
|
|
281
283
|
useAutoScroll,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/use-auto-scroll.ts","../src/use-stream-chat.ts"],"sourcesContent":["// Re-export core types so consumers only need to import from @deltakit/react\nexport type {\n\tContentPart,\n\tMessage,\n\tReasoningPart,\n\tSSEEvent,\n\tTextDeltaEvent,\n\tTextPart,\n\tToolCallEvent,\n\tToolCallPart,\n\tToolResultEvent,\n} from \"@deltakit/core\";\nexport { fromOpenAiAgents, parseSSEStream } from \"@deltakit/core\";\n\nexport type {\n\tEventHelpers,\n\tUseAutoScrollOptions,\n\tUseAutoScrollReturn,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\nexport { useAutoScroll } from \"./use-auto-scroll\";\nexport { useStreamChat } from \"./use-stream-chat\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { UseAutoScrollOptions, UseAutoScrollReturn } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_THRESHOLD = 50;\n\n// ---------------------------------------------------------------------------\n// useAutoScroll\n// ---------------------------------------------------------------------------\n\nexport function useAutoScroll<T extends HTMLElement = HTMLDivElement>(\n\tdependencies: unknown[],\n\toptions?: UseAutoScrollOptions,\n): UseAutoScrollReturn<T> {\n\tconst {\n\t\tbehavior = \"instant\",\n\t\tenabled = true,\n\t\tthreshold = DEFAULT_THRESHOLD,\n\t} = options ?? {};\n\n\tconst ref = useRef<T | null>(null);\n\tconst isAtBottomRef = useRef(true);\n\tconst [isAtBottom, setIsAtBottom] = useState(true);\n\n\t// A single rAF id shared across all scroll sources — ensures we never\n\t// call scrollTo() more than once per frame, no matter how many\n\t// MutationObserver / ResizeObserver callbacks fire.\n\tconst rafRef = useRef<number | null>(null);\n\n\tconst scheduleScroll = useCallback(() => {\n\t\tif (rafRef.current != null) return;\n\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\trafRef.current = null;\n\t\t\tconst el = ref.current;\n\t\t\tif (el && isAtBottomRef.current) {\n\t\t\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t\t\t}\n\t\t});\n\t}, [behavior]);\n\n\t// -----------------------------------------------------------------------\n\t// Track whether the user is near the bottom via scroll events.\n\t// Only triggers a React re-render when the boolean actually changes.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst handleScroll = () => {\n\t\t\tconst atBottom =\n\t\t\t\tel.scrollHeight - el.scrollTop - el.clientHeight <= threshold;\n\t\t\tisAtBottomRef.current = atBottom;\n\t\t\tsetIsAtBottom((prev) => (prev === atBottom ? prev : atBottom));\n\t\t};\n\n\t\tel.addEventListener(\"scroll\", handleScroll, { passive: true });\n\t\treturn () => el.removeEventListener(\"scroll\", handleScroll);\n\t}, [enabled, threshold]);\n\n\t// -----------------------------------------------------------------------\n\t// Scroll to bottom when dependencies change (if pinned).\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tif (!enabled || !isAtBottomRef.current) return;\n\t\tscheduleScroll();\n\t\t// biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed dynamically by the consumer\n\t}, dependencies);\n\n\t// -----------------------------------------------------------------------\n\t// MutationObserver + ResizeObserver — catch content changes during\n\t// streaming that happen between React re-renders (e.g. DOM mutations\n\t// from markdown renderers). Scroll calls are batched via rAF so we\n\t// scroll at most once per frame.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst resizeObserver = new ResizeObserver(scheduleScroll);\n\n\t\t// Observe existing children for size changes.\n\t\tfor (const child of el.children) {\n\t\t\tresizeObserver.observe(child);\n\t\t}\n\n\t\t// Watch for new children added to the container.\n\t\tconst mutationObserver = new MutationObserver((mutations) => {\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tfor (const node of mutation.addedNodes) {\n\t\t\t\t\tif (node instanceof Element) {\n\t\t\t\t\t\tresizeObserver.observe(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tscheduleScroll();\n\t\t});\n\n\t\tmutationObserver.observe(el, { childList: true, subtree: true });\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t\tmutationObserver.disconnect();\n\t\t};\n\t}, [enabled, scheduleScroll]);\n\n\t// -----------------------------------------------------------------------\n\t// Cancel any pending rAF on unmount.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current != null) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// scrollToBottom — imperative function that scrolls to the bottom and\n\t// re-pins auto-scroll.\n\t// -----------------------------------------------------------------------\n\n\tconst scrollToBottom = useCallback(() => {\n\t\tconst el = ref.current;\n\t\tif (!el) return;\n\n\t\tisAtBottomRef.current = true;\n\t\tsetIsAtBottom(true);\n\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t}, [behavior]);\n\n\treturn { ref, scrollToBottom, isAtBottom };\n}\n","import type { ContentPart, Message, SSEEvent } from \"@deltakit/core\";\nimport { parseSSEStream } from \"@deltakit/core\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n\tEventHelpers,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nlet counter = 0;\n\nfunction generateId(): string {\n\treturn `msg_${Date.now()}_${++counter}`;\n}\n\nfunction createMessage<TPart extends { type: string }>(\n\trole: Message[\"role\"],\n\tparts: TPart[],\n): Message<TPart> {\n\treturn { id: generateId(), role, parts };\n}\n\n// ---------------------------------------------------------------------------\n// Default event handler — accumulates `text_delta` into the last\n// assistant message's parts.\n// ---------------------------------------------------------------------------\n\nfunction defaultOnEvent(\n\tevent: SSEEvent,\n\thelpers: EventHelpers<ContentPart>,\n): void {\n\tif (event.type === \"text_delta\") {\n\t\thelpers.appendText(event.delta);\n\t}\n\t// Other event types (e.g. tool_call) are silently ignored by default.\n\t// Users can provide their own `onEvent` to handle them.\n}\n\n// ---------------------------------------------------------------------------\n// useStreamChat\n// ---------------------------------------------------------------------------\n\nexport function useStreamChat<\n\tTPart extends { type: string } = ContentPart,\n\tTEvent extends { type: string } = SSEEvent,\n>(options: UseStreamChatOptions<TPart, TEvent>): UseStreamChatReturn<TPart> {\n\tconst {\n\t\tapi,\n\t\theaders,\n\t\tbody,\n\t\tinitialMessages,\n\t\tonEvent,\n\t\tonMessage,\n\t\tonError,\n\t\tonFinish,\n\t} = options;\n\n\tconst [messages, setMessages] = useState<Message<TPart>[]>(\n\t\tinitialMessages ?? [],\n\t);\n\tconst [isLoading, setIsLoading] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst abortRef = useRef<AbortController | null>(null);\n\n\t// We use a ref for the latest messages so that callbacks created inside\n\t// `sendMessage` always see the current value without re-creating closures.\n\tconst messagesRef = useRef<Message<TPart>[]>(messages);\n\tmessagesRef.current = messages;\n\n\t// -----------------------------------------------------------------------\n\t// appendText — append a text delta to the last text part of the last\n\t// assistant message, or create a new text part if needed.\n\t// -----------------------------------------------------------------------\n\n\tconst appendText = useCallback((delta: string) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst parts = [...last.parts];\n\t\t\tconst lastPart = parts[parts.length - 1];\n\n\t\t\tif (lastPart && lastPart.type === \"text\" && \"text\" in lastPart) {\n\t\t\t\t// Append to existing text part\n\t\t\t\tconst textPart = lastPart as { type: \"text\"; text: string };\n\t\t\t\tparts[parts.length - 1] = {\n\t\t\t\t\t...lastPart,\n\t\t\t\t\ttext: textPart.text + delta,\n\t\t\t\t} as unknown as TPart;\n\t\t\t} else {\n\t\t\t\t// Create a new text part\n\t\t\t\tparts.push({ type: \"text\", text: delta } as unknown as TPart);\n\t\t\t}\n\n\t\t\tconst updated: Message<TPart> = { ...last, parts };\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// appendPart — push a new content part to the last assistant message.\n\t// -----------------------------------------------------------------------\n\n\tconst appendPart = useCallback((part: TPart) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst updated: Message<TPart> = {\n\t\t\t\t...last,\n\t\t\t\tparts: [...last.parts, part],\n\t\t\t};\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// stop\n\t// -----------------------------------------------------------------------\n\n\tconst stop = useCallback(() => {\n\t\tabortRef.current?.abort();\n\t\tabortRef.current = null;\n\t\tsetIsLoading(false);\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// sendMessage\n\t// -----------------------------------------------------------------------\n\n\tconst sendMessage = useCallback(\n\t\t(text: string) => {\n\t\t\t// Prevent sending while already streaming.\n\t\t\tif (abortRef.current) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst userMessage = createMessage<TPart>(\"user\", [\n\t\t\t\t{ type: \"text\", text } as unknown as TPart,\n\t\t\t]);\n\t\t\tconst assistantMessage = createMessage<TPart>(\"assistant\", []);\n\n\t\t\tsetMessages((prev) => {\n\t\t\t\tconst next = [...prev, userMessage, assistantMessage];\n\t\t\t\tmessagesRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tonMessage?.(userMessage);\n\n\t\t\tsetError(null);\n\t\t\tsetIsLoading(true);\n\n\t\t\tconst controller = new AbortController();\n\t\t\tabortRef.current = controller;\n\n\t\t\tconst eventHandler =\n\t\t\t\tonEvent ??\n\t\t\t\t(defaultOnEvent as unknown as (\n\t\t\t\t\tevent: TEvent,\n\t\t\t\t\thelpers: EventHelpers<TPart>,\n\t\t\t\t) => void);\n\t\t\tconst helpers: EventHelpers<TPart> = {\n\t\t\t\tappendText,\n\t\t\t\tappendPart,\n\t\t\t\tsetMessages,\n\t\t\t};\n\n\t\t\t// Fire-and-forget async IIFE — state is managed via React setState.\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(api, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ message: text, ...body }),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`SSE request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!response.body) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Response body is null — SSE streaming not supported\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor await (const event of parseSSEStream(\n\t\t\t\t\t\tresponse.body,\n\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t)) {\n\t\t\t\t\t\teventHandler(event as unknown as TEvent, helpers);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream finished — notify via callbacks.\n\t\t\t\t\tconst finalMessages = messagesRef.current;\n\t\t\t\t\tconst lastMessage = finalMessages[finalMessages.length - 1];\n\n\t\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\t\tonMessage?.(lastMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\tonFinish?.(finalMessages);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// AbortError is expected when the user calls `stop()`.\n\t\t\t\t\tif (err instanceof DOMException && err.name === \"AbortError\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\n\t\t\t\t\tsetError(error);\n\t\t\t\t\tonError?.(error);\n\t\t\t\t} finally {\n\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\t\t[\n\t\t\tapi,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tonEvent,\n\t\t\tonMessage,\n\t\t\tonError,\n\t\t\tonFinish,\n\t\t\tappendText,\n\t\t\tappendPart,\n\t\t],\n\t);\n\n\t// -----------------------------------------------------------------------\n\t// Cleanup — abort any in-flight stream when the component unmounts.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tabortRef.current?.abort();\n\t\t\tabortRef.current = null;\n\t\t};\n\t}, []);\n\n\treturn {\n\t\tmessages,\n\t\tisLoading,\n\t\terror,\n\t\tsendMessage,\n\t\tstop,\n\t\tsetMessages,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,IAAAA,eAAiD;;;ACZjD,mBAAyD;AAOzD,IAAM,oBAAoB;AAMnB,SAAS,cACf,cACA,SACyB;AACzB,QAAM;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,EACb,IAAI,WAAW,CAAC;AAEhB,QAAM,UAAM,qBAAiB,IAAI;AACjC,QAAM,oBAAgB,qBAAO,IAAI;AACjC,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,IAAI;AAKjD,QAAM,aAAS,qBAAsB,IAAI;AAEzC,QAAM,qBAAiB,0BAAY,MAAM;AACxC,QAAI,OAAO,WAAW,KAAM;AAC5B,WAAO,UAAU,sBAAsB,MAAM;AAC5C,aAAO,UAAU;AACjB,YAAM,KAAK,IAAI;AACf,UAAI,MAAM,cAAc,SAAS;AAChC,WAAG,SAAS,EAAE,KAAK,GAAG,cAAc,SAAS,CAAC;AAAA,MAC/C;AAAA,IACD,CAAC;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAOb,8BAAU,MAAM;AACf,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,MAAM,CAAC,QAAS;AAErB,UAAM,eAAe,MAAM;AAC1B,YAAM,WACL,GAAG,eAAe,GAAG,YAAY,GAAG,gBAAgB;AACrD,oBAAc,UAAU;AACxB,oBAAc,CAAC,SAAU,SAAS,WAAW,OAAO,QAAS;AAAA,IAC9D;AAEA,OAAG,iBAAiB,UAAU,cAAc,EAAE,SAAS,KAAK,CAAC;AAC7D,WAAO,MAAM,GAAG,oBAAoB,UAAU,YAAY;AAAA,EAC3D,GAAG,CAAC,SAAS,SAAS,CAAC;AAMvB,8BAAU,MAAM;AACf,QAAI,CAAC,WAAW,CAAC,cAAc,QAAS;AACxC,mBAAe;AAAA,EAEhB,GAAG,YAAY;AASf,8BAAU,MAAM;AACf,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,MAAM,CAAC,QAAS;AAErB,UAAM,iBAAiB,IAAI,eAAe,cAAc;AAGxD,eAAW,SAAS,GAAG,UAAU;AAChC,qBAAe,QAAQ,KAAK;AAAA,IAC7B;AAGA,UAAM,mBAAmB,IAAI,iBAAiB,CAAC,cAAc;AAC5D,iBAAW,YAAY,WAAW;AACjC,mBAAW,QAAQ,SAAS,YAAY;AACvC,cAAI,gBAAgB,SAAS;AAC5B,2BAAe,QAAQ,IAAI;AAAA,UAC5B;AAAA,QACD;AAAA,MACD;AACA,qBAAe;AAAA,IAChB,CAAC;AAED,qBAAiB,QAAQ,IAAI,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAE/D,WAAO,MAAM;AACZ,qBAAe,WAAW;AAC1B,uBAAiB,WAAW;AAAA,IAC7B;AAAA,EACD,GAAG,CAAC,SAAS,cAAc,CAAC;AAM5B,8BAAU,MAAM;AACf,WAAO,MAAM;AACZ,UAAI,OAAO,WAAW,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AACnC,eAAO,UAAU;AAAA,MAClB;AAAA,IACD;AAAA,EACD,GAAG,CAAC,CAAC;AAOL,QAAM,qBAAiB,0BAAY,MAAM;AACxC,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,OAAG,SAAS,EAAE,KAAK,GAAG,cAAc,SAAS,CAAC;AAAA,EAC/C,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO,EAAE,KAAK,gBAAgB,WAAW;AAC1C;;;AC1IA,kBAA+B;AAC/B,IAAAC,gBAAyD;AAWzD,IAAI,UAAU;AAEd,SAAS,aAAqB;AAC7B,SAAO,OAAO,KAAK,IAAI,CAAC,IAAI,EAAE,OAAO;AACtC;AAEA,SAAS,cACR,MACA,OACiB;AACjB,SAAO,EAAE,IAAI,WAAW,GAAG,MAAM,MAAM;AACxC;AAOA,SAAS,eACR,OACA,SACO;AACP,MAAI,MAAM,SAAS,cAAc;AAChC,YAAQ,WAAW,MAAM,KAAK;AAAA,EAC/B;AAGD;AAMO,SAAS,cAGd,SAA0E;AAC3E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,CAAC,UAAU,WAAW,QAAI;AAAA,IAC/B,mBAAmB,CAAC;AAAA,EACrB;AACA,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAuB,IAAI;AAErD,QAAM,eAAW,sBAA+B,IAAI;AAIpD,QAAM,kBAAc,sBAAyB,QAAQ;AACrD,cAAY,UAAU;AAOtB,QAAM,iBAAa,2BAAY,CAAC,UAAkB;AACjD,gBAAY,CAAC,SAAS;AACrB,YAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,UAAI,CAAC,QAAQ,KAAK,SAAS,YAAa,QAAO;AAE/C,YAAM,QAAQ,CAAC,GAAG,KAAK,KAAK;AAC5B,YAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AAEvC,UAAI,YAAY,SAAS,SAAS,UAAU,UAAU,UAAU;AAE/D,cAAM,WAAW;AACjB,cAAM,MAAM,SAAS,CAAC,IAAI;AAAA,UACzB,GAAG;AAAA,UACH,MAAM,SAAS,OAAO;AAAA,QACvB;AAAA,MACD,OAAO;AAEN,cAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAqB;AAAA,MAC7D;AAEA,YAAM,UAA0B,EAAE,GAAG,MAAM,MAAM;AACjD,aAAO,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE,GAAG,OAAO;AAAA,IACtC,CAAC;AAAA,EACF,GAAG,CAAC,CAAC;AAML,QAAM,iBAAa,2BAAY,CAAC,SAAgB;AAC/C,gBAAY,CAAC,SAAS;AACrB,YAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,UAAI,CAAC,QAAQ,KAAK,SAAS,YAAa,QAAO;AAE/C,YAAM,UAA0B;AAAA,QAC/B,GAAG;AAAA,QACH,OAAO,CAAC,GAAG,KAAK,OAAO,IAAI;AAAA,MAC5B;AACA,aAAO,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE,GAAG,OAAO;AAAA,IACtC,CAAC;AAAA,EACF,GAAG,CAAC,CAAC;AAML,QAAM,WAAO,2BAAY,MAAM;AAC9B,aAAS,SAAS,MAAM;AACxB,aAAS,UAAU;AACnB,iBAAa,KAAK;AAAA,EACnB,GAAG,CAAC,CAAC;AAML,QAAM,kBAAc;AAAA,IACnB,CAAC,SAAiB;AAEjB,UAAI,SAAS,SAAS;AACrB;AAAA,MACD;AAEA,YAAM,cAAc,cAAqB,QAAQ;AAAA,QAChD,EAAE,MAAM,QAAQ,KAAK;AAAA,MACtB,CAAC;AACD,YAAM,mBAAmB,cAAqB,aAAa,CAAC,CAAC;AAE7D,kBAAY,CAAC,SAAS;AACrB,cAAM,OAAO,CAAC,GAAG,MAAM,aAAa,gBAAgB;AACpD,oBAAY,UAAU;AACtB,eAAO;AAAA,MACR,CAAC;AAED,kBAAY,WAAW;AAEvB,eAAS,IAAI;AACb,mBAAa,IAAI;AAEjB,YAAM,aAAa,IAAI,gBAAgB;AACvC,eAAS,UAAU;AAEnB,YAAM,eACL,WACC;AAIF,YAAM,UAA+B;AAAA,QACpC;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAGA,OAAC,YAAY;AACZ,YAAI;AACH,gBAAM,WAAW,MAAM,MAAM,KAAK;AAAA,YACjC,QAAQ;AAAA,YACR,SAAS;AAAA,cACR,gBAAgB;AAAA,cAChB,GAAG;AAAA,YACJ;AAAA,YACA,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,YAC/C,QAAQ,WAAW;AAAA,UACpB,CAAC;AAED,cAAI,CAAC,SAAS,IAAI;AACjB,kBAAM,IAAI;AAAA,cACT,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,YAC9D;AAAA,UACD;AAEA,cAAI,CAAC,SAAS,MAAM;AACnB,kBAAM,IAAI;AAAA,cACT;AAAA,YACD;AAAA,UACD;AAEA,2BAAiB,aAAS;AAAA,YACzB,SAAS;AAAA,YACT,WAAW;AAAA,UACZ,GAAG;AACF,yBAAa,OAA4B,OAAO;AAAA,UACjD;AAGA,gBAAM,gBAAgB,YAAY;AAClC,gBAAM,cAAc,cAAc,cAAc,SAAS,CAAC;AAE1D,cAAI,aAAa,SAAS,aAAa;AACtC,wBAAY,WAAW;AAAA,UACxB;AAEA,qBAAW,aAAa;AAAA,QACzB,SAAS,KAAK;AAEb,cAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC7D;AAAA,UACD;AAEA,gBAAMC,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAEhE,mBAASA,MAAK;AACd,oBAAUA,MAAK;AAAA,QAChB,UAAE;AACD,mBAAS,UAAU;AACnB,uBAAa,KAAK;AAAA,QACnB;AAAA,MACD,GAAG;AAAA,IACJ;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAMA,+BAAU,MAAM;AACf,WAAO,MAAM;AACZ,eAAS,SAAS,MAAM;AACxB,eAAS,UAAU;AAAA,IACpB;AAAA,EACD,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":["import_core","import_react","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-auto-scroll.ts","../src/use-stream-chat.ts"],"sourcesContent":["// Re-export core types so consumers only need to import from @deltakit/react\nexport type {\n\tContentPart,\n\tMessage,\n\tReasoningPart,\n\tSSEEvent,\n\tTextDeltaEvent,\n\tTextPart,\n\tToolCallEvent,\n\tToolCallPart,\n\tToolResultEvent,\n} from \"@deltakit/core\";\nexport {\n\tfromAgnoAgents,\n\tfromOpenAiAgents,\n\tparseSSEStream,\n} from \"@deltakit/core\";\n\nexport type {\n\tEventHelpers,\n\tUseAutoScrollOptions,\n\tUseAutoScrollReturn,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\nexport { useAutoScroll } from \"./use-auto-scroll\";\nexport { useStreamChat } from \"./use-stream-chat\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { UseAutoScrollOptions, UseAutoScrollReturn } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_THRESHOLD = 50;\n\n// ---------------------------------------------------------------------------\n// useAutoScroll\n// ---------------------------------------------------------------------------\n\nexport function useAutoScroll<T extends HTMLElement = HTMLDivElement>(\n\tdependencies: unknown[],\n\toptions?: UseAutoScrollOptions,\n): UseAutoScrollReturn<T> {\n\tconst {\n\t\tbehavior = \"instant\",\n\t\tenabled = true,\n\t\tthreshold = DEFAULT_THRESHOLD,\n\t} = options ?? {};\n\n\tconst ref = useRef<T | null>(null);\n\tconst isAtBottomRef = useRef(true);\n\tconst [isAtBottom, setIsAtBottom] = useState(true);\n\n\t// A single rAF id shared across all scroll sources — ensures we never\n\t// call scrollTo() more than once per frame, no matter how many\n\t// MutationObserver / ResizeObserver callbacks fire.\n\tconst rafRef = useRef<number | null>(null);\n\n\tconst scheduleScroll = useCallback(() => {\n\t\tif (rafRef.current != null) return;\n\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\trafRef.current = null;\n\t\t\tconst el = ref.current;\n\t\t\tif (el && isAtBottomRef.current) {\n\t\t\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t\t\t}\n\t\t});\n\t}, [behavior]);\n\n\t// -----------------------------------------------------------------------\n\t// Track whether the user is near the bottom via scroll events.\n\t// Only triggers a React re-render when the boolean actually changes.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst handleScroll = () => {\n\t\t\tconst atBottom =\n\t\t\t\tel.scrollHeight - el.scrollTop - el.clientHeight <= threshold;\n\t\t\tisAtBottomRef.current = atBottom;\n\t\t\tsetIsAtBottom((prev) => (prev === atBottom ? prev : atBottom));\n\t\t};\n\n\t\tel.addEventListener(\"scroll\", handleScroll, { passive: true });\n\t\treturn () => el.removeEventListener(\"scroll\", handleScroll);\n\t}, [enabled, threshold]);\n\n\t// -----------------------------------------------------------------------\n\t// Scroll to bottom when dependencies change (if pinned).\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tif (!enabled || !isAtBottomRef.current) return;\n\t\tscheduleScroll();\n\t\t// biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed dynamically by the consumer\n\t}, dependencies);\n\n\t// -----------------------------------------------------------------------\n\t// MutationObserver + ResizeObserver — catch content changes during\n\t// streaming that happen between React re-renders (e.g. DOM mutations\n\t// from markdown renderers). Scroll calls are batched via rAF so we\n\t// scroll at most once per frame.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst resizeObserver = new ResizeObserver(scheduleScroll);\n\n\t\t// Observe existing children for size changes.\n\t\tfor (const child of el.children) {\n\t\t\tresizeObserver.observe(child);\n\t\t}\n\n\t\t// Watch for new children added to the container.\n\t\tconst mutationObserver = new MutationObserver((mutations) => {\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tfor (const node of mutation.addedNodes) {\n\t\t\t\t\tif (node instanceof Element) {\n\t\t\t\t\t\tresizeObserver.observe(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tscheduleScroll();\n\t\t});\n\n\t\tmutationObserver.observe(el, { childList: true, subtree: true });\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t\tmutationObserver.disconnect();\n\t\t};\n\t}, [enabled, scheduleScroll]);\n\n\t// -----------------------------------------------------------------------\n\t// Cancel any pending rAF on unmount.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current != null) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// scrollToBottom — imperative function that scrolls to the bottom and\n\t// re-pins auto-scroll.\n\t// -----------------------------------------------------------------------\n\n\tconst scrollToBottom = useCallback(() => {\n\t\tconst el = ref.current;\n\t\tif (!el) return;\n\n\t\tisAtBottomRef.current = true;\n\t\tsetIsAtBottom(true);\n\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t}, [behavior]);\n\n\treturn { ref, scrollToBottom, isAtBottom };\n}\n","import type { ContentPart, Message, SSEEvent } from \"@deltakit/core\";\nimport { parseSSEStream } from \"@deltakit/core\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n\tEventHelpers,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nlet counter = 0;\n\nfunction generateId(): string {\n\treturn `msg_${Date.now()}_${++counter}`;\n}\n\nfunction createMessage<TPart extends { type: string }>(\n\trole: Message[\"role\"],\n\tparts: TPart[],\n): Message<TPart> {\n\treturn { id: generateId(), role, parts };\n}\n\n// ---------------------------------------------------------------------------\n// Default event handler — accumulates `text_delta` into the last\n// assistant message's parts.\n// ---------------------------------------------------------------------------\n\nfunction defaultOnEvent(\n\tevent: SSEEvent,\n\thelpers: EventHelpers<ContentPart>,\n): void {\n\tif (event.type === \"text_delta\") {\n\t\thelpers.appendText(event.delta);\n\t}\n\t// Other event types (e.g. tool_call) are silently ignored by default.\n\t// Users can provide their own `onEvent` to handle them.\n}\n\n// ---------------------------------------------------------------------------\n// useStreamChat\n// ---------------------------------------------------------------------------\n\nexport function useStreamChat<\n\tTPart extends { type: string } = ContentPart,\n\tTEvent extends { type: string } = SSEEvent,\n>(options: UseStreamChatOptions<TPart, TEvent>): UseStreamChatReturn<TPart> {\n\tconst {\n\t\tapi,\n\t\theaders,\n\t\tbody,\n\t\tinitialMessages,\n\t\tonEvent,\n\t\tonMessage,\n\t\tonError,\n\t\tonFinish,\n\t} = options;\n\n\tconst [messages, setMessages] = useState<Message<TPart>[]>(\n\t\tinitialMessages ?? [],\n\t);\n\tconst [isLoading, setIsLoading] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst abortRef = useRef<AbortController | null>(null);\n\n\t// We use a ref for the latest messages so that callbacks created inside\n\t// `sendMessage` always see the current value without re-creating closures.\n\tconst messagesRef = useRef<Message<TPart>[]>(messages);\n\tmessagesRef.current = messages;\n\n\t// -----------------------------------------------------------------------\n\t// appendText — append a text delta to the last text part of the last\n\t// assistant message, or create a new text part if needed.\n\t// -----------------------------------------------------------------------\n\n\tconst appendText = useCallback((delta: string) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst parts = [...last.parts];\n\t\t\tconst lastPart = parts[parts.length - 1];\n\n\t\t\tif (lastPart && lastPart.type === \"text\" && \"text\" in lastPart) {\n\t\t\t\t// Append to existing text part\n\t\t\t\tconst textPart = lastPart as { type: \"text\"; text: string };\n\t\t\t\tparts[parts.length - 1] = {\n\t\t\t\t\t...lastPart,\n\t\t\t\t\ttext: textPart.text + delta,\n\t\t\t\t} as unknown as TPart;\n\t\t\t} else {\n\t\t\t\t// Create a new text part\n\t\t\t\tparts.push({ type: \"text\", text: delta } as unknown as TPart);\n\t\t\t}\n\n\t\t\tconst updated: Message<TPart> = { ...last, parts };\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// appendPart — push a new content part to the last assistant message.\n\t// -----------------------------------------------------------------------\n\n\tconst appendPart = useCallback((part: TPart) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst updated: Message<TPart> = {\n\t\t\t\t...last,\n\t\t\t\tparts: [...last.parts, part],\n\t\t\t};\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// stop\n\t// -----------------------------------------------------------------------\n\n\tconst stop = useCallback(() => {\n\t\tabortRef.current?.abort();\n\t\tabortRef.current = null;\n\t\tsetIsLoading(false);\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// sendMessage\n\t// -----------------------------------------------------------------------\n\n\tconst sendMessage = useCallback(\n\t\t(text: string) => {\n\t\t\t// Prevent sending while already streaming.\n\t\t\tif (abortRef.current) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst userMessage = createMessage<TPart>(\"user\", [\n\t\t\t\t{ type: \"text\", text } as unknown as TPart,\n\t\t\t]);\n\t\t\tconst assistantMessage = createMessage<TPart>(\"assistant\", []);\n\n\t\t\tsetMessages((prev) => {\n\t\t\t\tconst next = [...prev, userMessage, assistantMessage];\n\t\t\t\tmessagesRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tonMessage?.(userMessage);\n\n\t\t\tsetError(null);\n\t\t\tsetIsLoading(true);\n\n\t\t\tconst controller = new AbortController();\n\t\t\tabortRef.current = controller;\n\n\t\t\tconst eventHandler =\n\t\t\t\tonEvent ??\n\t\t\t\t(defaultOnEvent as unknown as (\n\t\t\t\t\tevent: TEvent,\n\t\t\t\t\thelpers: EventHelpers<TPart>,\n\t\t\t\t) => void);\n\t\t\tconst helpers: EventHelpers<TPart> = {\n\t\t\t\tappendText,\n\t\t\t\tappendPart,\n\t\t\t\tsetMessages,\n\t\t\t};\n\n\t\t\t// Fire-and-forget async IIFE — state is managed via React setState.\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(api, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ message: text, ...body }),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`SSE request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!response.body) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Response body is null — SSE streaming not supported\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor await (const event of parseSSEStream(\n\t\t\t\t\t\tresponse.body,\n\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t)) {\n\t\t\t\t\t\teventHandler(event as unknown as TEvent, helpers);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream finished — notify via callbacks.\n\t\t\t\t\tconst finalMessages = messagesRef.current;\n\t\t\t\t\tconst lastMessage = finalMessages[finalMessages.length - 1];\n\n\t\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\t\tonMessage?.(lastMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\tonFinish?.(finalMessages);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// AbortError is expected when the user calls `stop()`.\n\t\t\t\t\tif (err instanceof DOMException && err.name === \"AbortError\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\n\t\t\t\t\tsetError(error);\n\t\t\t\t\tonError?.(error);\n\t\t\t\t} finally {\n\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\t\t[\n\t\t\tapi,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tonEvent,\n\t\t\tonMessage,\n\t\t\tonError,\n\t\t\tonFinish,\n\t\t\tappendText,\n\t\t\tappendPart,\n\t\t],\n\t);\n\n\t// -----------------------------------------------------------------------\n\t// Cleanup — abort any in-flight stream when the component unmounts.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tabortRef.current?.abort();\n\t\t\tabortRef.current = null;\n\t\t};\n\t}, []);\n\n\treturn {\n\t\tmessages,\n\t\tisLoading,\n\t\terror,\n\t\tsendMessage,\n\t\tstop,\n\t\tsetMessages,\n\t};\n}\n"],"mappings":"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","names":["import_core","import_react","error"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContentPart, Message, SSEEvent } from '@deltakit/core';
|
|
2
|
-
export { ContentPart, Message, ReasoningPart, SSEEvent, TextDeltaEvent, TextPart, ToolCallEvent, ToolCallPart, ToolResultEvent, fromOpenAiAgents, parseSSEStream } from '@deltakit/core';
|
|
2
|
+
export { ContentPart, Message, ReasoningPart, SSEEvent, TextDeltaEvent, TextPart, ToolCallEvent, ToolCallPart, ToolResultEvent, fromAgnoAgents, fromOpenAiAgents, parseSSEStream } from '@deltakit/core';
|
|
3
3
|
import { Dispatch, SetStateAction, RefObject } from 'react';
|
|
4
4
|
|
|
5
5
|
interface EventHelpers<TPart extends {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContentPart, Message, SSEEvent } from '@deltakit/core';
|
|
2
|
-
export { ContentPart, Message, ReasoningPart, SSEEvent, TextDeltaEvent, TextPart, ToolCallEvent, ToolCallPart, ToolResultEvent, fromOpenAiAgents, parseSSEStream } from '@deltakit/core';
|
|
2
|
+
export { ContentPart, Message, ReasoningPart, SSEEvent, TextDeltaEvent, TextPart, ToolCallEvent, ToolCallPart, ToolResultEvent, fromAgnoAgents, fromOpenAiAgents, parseSSEStream } from '@deltakit/core';
|
|
3
3
|
import { Dispatch, SetStateAction, RefObject } from 'react';
|
|
4
4
|
|
|
5
5
|
interface EventHelpers<TPart extends {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fromAgnoAgents,
|
|
4
|
+
fromOpenAiAgents,
|
|
5
|
+
parseSSEStream as parseSSEStream2
|
|
6
|
+
} from "@deltakit/core";
|
|
3
7
|
|
|
4
8
|
// src/use-auto-scroll.ts
|
|
5
9
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -248,6 +252,7 @@ function useStreamChat(options) {
|
|
|
248
252
|
};
|
|
249
253
|
}
|
|
250
254
|
export {
|
|
255
|
+
fromAgnoAgents,
|
|
251
256
|
fromOpenAiAgents,
|
|
252
257
|
parseSSEStream2 as parseSSEStream,
|
|
253
258
|
useAutoScroll,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/use-auto-scroll.ts","../src/use-stream-chat.ts"],"sourcesContent":["// Re-export core types so consumers only need to import from @deltakit/react\nexport type {\n\tContentPart,\n\tMessage,\n\tReasoningPart,\n\tSSEEvent,\n\tTextDeltaEvent,\n\tTextPart,\n\tToolCallEvent,\n\tToolCallPart,\n\tToolResultEvent,\n} from \"@deltakit/core\";\nexport { fromOpenAiAgents, parseSSEStream } from \"@deltakit/core\";\n\nexport type {\n\tEventHelpers,\n\tUseAutoScrollOptions,\n\tUseAutoScrollReturn,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\nexport { useAutoScroll } from \"./use-auto-scroll\";\nexport { useStreamChat } from \"./use-stream-chat\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { UseAutoScrollOptions, UseAutoScrollReturn } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_THRESHOLD = 50;\n\n// ---------------------------------------------------------------------------\n// useAutoScroll\n// ---------------------------------------------------------------------------\n\nexport function useAutoScroll<T extends HTMLElement = HTMLDivElement>(\n\tdependencies: unknown[],\n\toptions?: UseAutoScrollOptions,\n): UseAutoScrollReturn<T> {\n\tconst {\n\t\tbehavior = \"instant\",\n\t\tenabled = true,\n\t\tthreshold = DEFAULT_THRESHOLD,\n\t} = options ?? {};\n\n\tconst ref = useRef<T | null>(null);\n\tconst isAtBottomRef = useRef(true);\n\tconst [isAtBottom, setIsAtBottom] = useState(true);\n\n\t// A single rAF id shared across all scroll sources — ensures we never\n\t// call scrollTo() more than once per frame, no matter how many\n\t// MutationObserver / ResizeObserver callbacks fire.\n\tconst rafRef = useRef<number | null>(null);\n\n\tconst scheduleScroll = useCallback(() => {\n\t\tif (rafRef.current != null) return;\n\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\trafRef.current = null;\n\t\t\tconst el = ref.current;\n\t\t\tif (el && isAtBottomRef.current) {\n\t\t\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t\t\t}\n\t\t});\n\t}, [behavior]);\n\n\t// -----------------------------------------------------------------------\n\t// Track whether the user is near the bottom via scroll events.\n\t// Only triggers a React re-render when the boolean actually changes.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst handleScroll = () => {\n\t\t\tconst atBottom =\n\t\t\t\tel.scrollHeight - el.scrollTop - el.clientHeight <= threshold;\n\t\t\tisAtBottomRef.current = atBottom;\n\t\t\tsetIsAtBottom((prev) => (prev === atBottom ? prev : atBottom));\n\t\t};\n\n\t\tel.addEventListener(\"scroll\", handleScroll, { passive: true });\n\t\treturn () => el.removeEventListener(\"scroll\", handleScroll);\n\t}, [enabled, threshold]);\n\n\t// -----------------------------------------------------------------------\n\t// Scroll to bottom when dependencies change (if pinned).\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tif (!enabled || !isAtBottomRef.current) return;\n\t\tscheduleScroll();\n\t\t// biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed dynamically by the consumer\n\t}, dependencies);\n\n\t// -----------------------------------------------------------------------\n\t// MutationObserver + ResizeObserver — catch content changes during\n\t// streaming that happen between React re-renders (e.g. DOM mutations\n\t// from markdown renderers). Scroll calls are batched via rAF so we\n\t// scroll at most once per frame.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst resizeObserver = new ResizeObserver(scheduleScroll);\n\n\t\t// Observe existing children for size changes.\n\t\tfor (const child of el.children) {\n\t\t\tresizeObserver.observe(child);\n\t\t}\n\n\t\t// Watch for new children added to the container.\n\t\tconst mutationObserver = new MutationObserver((mutations) => {\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tfor (const node of mutation.addedNodes) {\n\t\t\t\t\tif (node instanceof Element) {\n\t\t\t\t\t\tresizeObserver.observe(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tscheduleScroll();\n\t\t});\n\n\t\tmutationObserver.observe(el, { childList: true, subtree: true });\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t\tmutationObserver.disconnect();\n\t\t};\n\t}, [enabled, scheduleScroll]);\n\n\t// -----------------------------------------------------------------------\n\t// Cancel any pending rAF on unmount.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current != null) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// scrollToBottom — imperative function that scrolls to the bottom and\n\t// re-pins auto-scroll.\n\t// -----------------------------------------------------------------------\n\n\tconst scrollToBottom = useCallback(() => {\n\t\tconst el = ref.current;\n\t\tif (!el) return;\n\n\t\tisAtBottomRef.current = true;\n\t\tsetIsAtBottom(true);\n\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t}, [behavior]);\n\n\treturn { ref, scrollToBottom, isAtBottom };\n}\n","import type { ContentPart, Message, SSEEvent } from \"@deltakit/core\";\nimport { parseSSEStream } from \"@deltakit/core\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n\tEventHelpers,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nlet counter = 0;\n\nfunction generateId(): string {\n\treturn `msg_${Date.now()}_${++counter}`;\n}\n\nfunction createMessage<TPart extends { type: string }>(\n\trole: Message[\"role\"],\n\tparts: TPart[],\n): Message<TPart> {\n\treturn { id: generateId(), role, parts };\n}\n\n// ---------------------------------------------------------------------------\n// Default event handler — accumulates `text_delta` into the last\n// assistant message's parts.\n// ---------------------------------------------------------------------------\n\nfunction defaultOnEvent(\n\tevent: SSEEvent,\n\thelpers: EventHelpers<ContentPart>,\n): void {\n\tif (event.type === \"text_delta\") {\n\t\thelpers.appendText(event.delta);\n\t}\n\t// Other event types (e.g. tool_call) are silently ignored by default.\n\t// Users can provide their own `onEvent` to handle them.\n}\n\n// ---------------------------------------------------------------------------\n// useStreamChat\n// ---------------------------------------------------------------------------\n\nexport function useStreamChat<\n\tTPart extends { type: string } = ContentPart,\n\tTEvent extends { type: string } = SSEEvent,\n>(options: UseStreamChatOptions<TPart, TEvent>): UseStreamChatReturn<TPart> {\n\tconst {\n\t\tapi,\n\t\theaders,\n\t\tbody,\n\t\tinitialMessages,\n\t\tonEvent,\n\t\tonMessage,\n\t\tonError,\n\t\tonFinish,\n\t} = options;\n\n\tconst [messages, setMessages] = useState<Message<TPart>[]>(\n\t\tinitialMessages ?? [],\n\t);\n\tconst [isLoading, setIsLoading] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst abortRef = useRef<AbortController | null>(null);\n\n\t// We use a ref for the latest messages so that callbacks created inside\n\t// `sendMessage` always see the current value without re-creating closures.\n\tconst messagesRef = useRef<Message<TPart>[]>(messages);\n\tmessagesRef.current = messages;\n\n\t// -----------------------------------------------------------------------\n\t// appendText — append a text delta to the last text part of the last\n\t// assistant message, or create a new text part if needed.\n\t// -----------------------------------------------------------------------\n\n\tconst appendText = useCallback((delta: string) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst parts = [...last.parts];\n\t\t\tconst lastPart = parts[parts.length - 1];\n\n\t\t\tif (lastPart && lastPart.type === \"text\" && \"text\" in lastPart) {\n\t\t\t\t// Append to existing text part\n\t\t\t\tconst textPart = lastPart as { type: \"text\"; text: string };\n\t\t\t\tparts[parts.length - 1] = {\n\t\t\t\t\t...lastPart,\n\t\t\t\t\ttext: textPart.text + delta,\n\t\t\t\t} as unknown as TPart;\n\t\t\t} else {\n\t\t\t\t// Create a new text part\n\t\t\t\tparts.push({ type: \"text\", text: delta } as unknown as TPart);\n\t\t\t}\n\n\t\t\tconst updated: Message<TPart> = { ...last, parts };\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// appendPart — push a new content part to the last assistant message.\n\t// -----------------------------------------------------------------------\n\n\tconst appendPart = useCallback((part: TPart) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst updated: Message<TPart> = {\n\t\t\t\t...last,\n\t\t\t\tparts: [...last.parts, part],\n\t\t\t};\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// stop\n\t// -----------------------------------------------------------------------\n\n\tconst stop = useCallback(() => {\n\t\tabortRef.current?.abort();\n\t\tabortRef.current = null;\n\t\tsetIsLoading(false);\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// sendMessage\n\t// -----------------------------------------------------------------------\n\n\tconst sendMessage = useCallback(\n\t\t(text: string) => {\n\t\t\t// Prevent sending while already streaming.\n\t\t\tif (abortRef.current) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst userMessage = createMessage<TPart>(\"user\", [\n\t\t\t\t{ type: \"text\", text } as unknown as TPart,\n\t\t\t]);\n\t\t\tconst assistantMessage = createMessage<TPart>(\"assistant\", []);\n\n\t\t\tsetMessages((prev) => {\n\t\t\t\tconst next = [...prev, userMessage, assistantMessage];\n\t\t\t\tmessagesRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tonMessage?.(userMessage);\n\n\t\t\tsetError(null);\n\t\t\tsetIsLoading(true);\n\n\t\t\tconst controller = new AbortController();\n\t\t\tabortRef.current = controller;\n\n\t\t\tconst eventHandler =\n\t\t\t\tonEvent ??\n\t\t\t\t(defaultOnEvent as unknown as (\n\t\t\t\t\tevent: TEvent,\n\t\t\t\t\thelpers: EventHelpers<TPart>,\n\t\t\t\t) => void);\n\t\t\tconst helpers: EventHelpers<TPart> = {\n\t\t\t\tappendText,\n\t\t\t\tappendPart,\n\t\t\t\tsetMessages,\n\t\t\t};\n\n\t\t\t// Fire-and-forget async IIFE — state is managed via React setState.\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(api, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ message: text, ...body }),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`SSE request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!response.body) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Response body is null — SSE streaming not supported\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor await (const event of parseSSEStream(\n\t\t\t\t\t\tresponse.body,\n\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t)) {\n\t\t\t\t\t\teventHandler(event as unknown as TEvent, helpers);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream finished — notify via callbacks.\n\t\t\t\t\tconst finalMessages = messagesRef.current;\n\t\t\t\t\tconst lastMessage = finalMessages[finalMessages.length - 1];\n\n\t\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\t\tonMessage?.(lastMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\tonFinish?.(finalMessages);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// AbortError is expected when the user calls `stop()`.\n\t\t\t\t\tif (err instanceof DOMException && err.name === \"AbortError\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\n\t\t\t\t\tsetError(error);\n\t\t\t\t\tonError?.(error);\n\t\t\t\t} finally {\n\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\t\t[\n\t\t\tapi,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tonEvent,\n\t\t\tonMessage,\n\t\t\tonError,\n\t\t\tonFinish,\n\t\t\tappendText,\n\t\t\tappendPart,\n\t\t],\n\t);\n\n\t// -----------------------------------------------------------------------\n\t// Cleanup — abort any in-flight stream when the component unmounts.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tabortRef.current?.abort();\n\t\t\tabortRef.current = null;\n\t\t};\n\t}, []);\n\n\treturn {\n\t\tmessages,\n\t\tisLoading,\n\t\terror,\n\t\tsendMessage,\n\t\tstop,\n\t\tsetMessages,\n\t};\n}\n"],"mappings":"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","names":["parseSSEStream","useCallback","useEffect","useRef","useState","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-auto-scroll.ts","../src/use-stream-chat.ts"],"sourcesContent":["// Re-export core types so consumers only need to import from @deltakit/react\nexport type {\n\tContentPart,\n\tMessage,\n\tReasoningPart,\n\tSSEEvent,\n\tTextDeltaEvent,\n\tTextPart,\n\tToolCallEvent,\n\tToolCallPart,\n\tToolResultEvent,\n} from \"@deltakit/core\";\nexport {\n\tfromAgnoAgents,\n\tfromOpenAiAgents,\n\tparseSSEStream,\n} from \"@deltakit/core\";\n\nexport type {\n\tEventHelpers,\n\tUseAutoScrollOptions,\n\tUseAutoScrollReturn,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\nexport { useAutoScroll } from \"./use-auto-scroll\";\nexport { useStreamChat } from \"./use-stream-chat\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { UseAutoScrollOptions, UseAutoScrollReturn } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_THRESHOLD = 50;\n\n// ---------------------------------------------------------------------------\n// useAutoScroll\n// ---------------------------------------------------------------------------\n\nexport function useAutoScroll<T extends HTMLElement = HTMLDivElement>(\n\tdependencies: unknown[],\n\toptions?: UseAutoScrollOptions,\n): UseAutoScrollReturn<T> {\n\tconst {\n\t\tbehavior = \"instant\",\n\t\tenabled = true,\n\t\tthreshold = DEFAULT_THRESHOLD,\n\t} = options ?? {};\n\n\tconst ref = useRef<T | null>(null);\n\tconst isAtBottomRef = useRef(true);\n\tconst [isAtBottom, setIsAtBottom] = useState(true);\n\n\t// A single rAF id shared across all scroll sources — ensures we never\n\t// call scrollTo() more than once per frame, no matter how many\n\t// MutationObserver / ResizeObserver callbacks fire.\n\tconst rafRef = useRef<number | null>(null);\n\n\tconst scheduleScroll = useCallback(() => {\n\t\tif (rafRef.current != null) return;\n\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\trafRef.current = null;\n\t\t\tconst el = ref.current;\n\t\t\tif (el && isAtBottomRef.current) {\n\t\t\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t\t\t}\n\t\t});\n\t}, [behavior]);\n\n\t// -----------------------------------------------------------------------\n\t// Track whether the user is near the bottom via scroll events.\n\t// Only triggers a React re-render when the boolean actually changes.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst handleScroll = () => {\n\t\t\tconst atBottom =\n\t\t\t\tel.scrollHeight - el.scrollTop - el.clientHeight <= threshold;\n\t\t\tisAtBottomRef.current = atBottom;\n\t\t\tsetIsAtBottom((prev) => (prev === atBottom ? prev : atBottom));\n\t\t};\n\n\t\tel.addEventListener(\"scroll\", handleScroll, { passive: true });\n\t\treturn () => el.removeEventListener(\"scroll\", handleScroll);\n\t}, [enabled, threshold]);\n\n\t// -----------------------------------------------------------------------\n\t// Scroll to bottom when dependencies change (if pinned).\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tif (!enabled || !isAtBottomRef.current) return;\n\t\tscheduleScroll();\n\t\t// biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed dynamically by the consumer\n\t}, dependencies);\n\n\t// -----------------------------------------------------------------------\n\t// MutationObserver + ResizeObserver — catch content changes during\n\t// streaming that happen between React re-renders (e.g. DOM mutations\n\t// from markdown renderers). Scroll calls are batched via rAF so we\n\t// scroll at most once per frame.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\tconst el = ref.current;\n\t\tif (!el || !enabled) return;\n\n\t\tconst resizeObserver = new ResizeObserver(scheduleScroll);\n\n\t\t// Observe existing children for size changes.\n\t\tfor (const child of el.children) {\n\t\t\tresizeObserver.observe(child);\n\t\t}\n\n\t\t// Watch for new children added to the container.\n\t\tconst mutationObserver = new MutationObserver((mutations) => {\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tfor (const node of mutation.addedNodes) {\n\t\t\t\t\tif (node instanceof Element) {\n\t\t\t\t\t\tresizeObserver.observe(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tscheduleScroll();\n\t\t});\n\n\t\tmutationObserver.observe(el, { childList: true, subtree: true });\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t\tmutationObserver.disconnect();\n\t\t};\n\t}, [enabled, scheduleScroll]);\n\n\t// -----------------------------------------------------------------------\n\t// Cancel any pending rAF on unmount.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current != null) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// scrollToBottom — imperative function that scrolls to the bottom and\n\t// re-pins auto-scroll.\n\t// -----------------------------------------------------------------------\n\n\tconst scrollToBottom = useCallback(() => {\n\t\tconst el = ref.current;\n\t\tif (!el) return;\n\n\t\tisAtBottomRef.current = true;\n\t\tsetIsAtBottom(true);\n\t\tel.scrollTo({ top: el.scrollHeight, behavior });\n\t}, [behavior]);\n\n\treturn { ref, scrollToBottom, isAtBottom };\n}\n","import type { ContentPart, Message, SSEEvent } from \"@deltakit/core\";\nimport { parseSSEStream } from \"@deltakit/core\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n\tEventHelpers,\n\tUseStreamChatOptions,\n\tUseStreamChatReturn,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nlet counter = 0;\n\nfunction generateId(): string {\n\treturn `msg_${Date.now()}_${++counter}`;\n}\n\nfunction createMessage<TPart extends { type: string }>(\n\trole: Message[\"role\"],\n\tparts: TPart[],\n): Message<TPart> {\n\treturn { id: generateId(), role, parts };\n}\n\n// ---------------------------------------------------------------------------\n// Default event handler — accumulates `text_delta` into the last\n// assistant message's parts.\n// ---------------------------------------------------------------------------\n\nfunction defaultOnEvent(\n\tevent: SSEEvent,\n\thelpers: EventHelpers<ContentPart>,\n): void {\n\tif (event.type === \"text_delta\") {\n\t\thelpers.appendText(event.delta);\n\t}\n\t// Other event types (e.g. tool_call) are silently ignored by default.\n\t// Users can provide their own `onEvent` to handle them.\n}\n\n// ---------------------------------------------------------------------------\n// useStreamChat\n// ---------------------------------------------------------------------------\n\nexport function useStreamChat<\n\tTPart extends { type: string } = ContentPart,\n\tTEvent extends { type: string } = SSEEvent,\n>(options: UseStreamChatOptions<TPart, TEvent>): UseStreamChatReturn<TPart> {\n\tconst {\n\t\tapi,\n\t\theaders,\n\t\tbody,\n\t\tinitialMessages,\n\t\tonEvent,\n\t\tonMessage,\n\t\tonError,\n\t\tonFinish,\n\t} = options;\n\n\tconst [messages, setMessages] = useState<Message<TPart>[]>(\n\t\tinitialMessages ?? [],\n\t);\n\tconst [isLoading, setIsLoading] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst abortRef = useRef<AbortController | null>(null);\n\n\t// We use a ref for the latest messages so that callbacks created inside\n\t// `sendMessage` always see the current value without re-creating closures.\n\tconst messagesRef = useRef<Message<TPart>[]>(messages);\n\tmessagesRef.current = messages;\n\n\t// -----------------------------------------------------------------------\n\t// appendText — append a text delta to the last text part of the last\n\t// assistant message, or create a new text part if needed.\n\t// -----------------------------------------------------------------------\n\n\tconst appendText = useCallback((delta: string) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst parts = [...last.parts];\n\t\t\tconst lastPart = parts[parts.length - 1];\n\n\t\t\tif (lastPart && lastPart.type === \"text\" && \"text\" in lastPart) {\n\t\t\t\t// Append to existing text part\n\t\t\t\tconst textPart = lastPart as { type: \"text\"; text: string };\n\t\t\t\tparts[parts.length - 1] = {\n\t\t\t\t\t...lastPart,\n\t\t\t\t\ttext: textPart.text + delta,\n\t\t\t\t} as unknown as TPart;\n\t\t\t} else {\n\t\t\t\t// Create a new text part\n\t\t\t\tparts.push({ type: \"text\", text: delta } as unknown as TPart);\n\t\t\t}\n\n\t\t\tconst updated: Message<TPart> = { ...last, parts };\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// appendPart — push a new content part to the last assistant message.\n\t// -----------------------------------------------------------------------\n\n\tconst appendPart = useCallback((part: TPart) => {\n\t\tsetMessages((prev) => {\n\t\t\tconst last = prev[prev.length - 1];\n\t\t\tif (!last || last.role !== \"assistant\") return prev;\n\n\t\t\tconst updated: Message<TPart> = {\n\t\t\t\t...last,\n\t\t\t\tparts: [...last.parts, part],\n\t\t\t};\n\t\t\treturn [...prev.slice(0, -1), updated];\n\t\t});\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// stop\n\t// -----------------------------------------------------------------------\n\n\tconst stop = useCallback(() => {\n\t\tabortRef.current?.abort();\n\t\tabortRef.current = null;\n\t\tsetIsLoading(false);\n\t}, []);\n\n\t// -----------------------------------------------------------------------\n\t// sendMessage\n\t// -----------------------------------------------------------------------\n\n\tconst sendMessage = useCallback(\n\t\t(text: string) => {\n\t\t\t// Prevent sending while already streaming.\n\t\t\tif (abortRef.current) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst userMessage = createMessage<TPart>(\"user\", [\n\t\t\t\t{ type: \"text\", text } as unknown as TPart,\n\t\t\t]);\n\t\t\tconst assistantMessage = createMessage<TPart>(\"assistant\", []);\n\n\t\t\tsetMessages((prev) => {\n\t\t\t\tconst next = [...prev, userMessage, assistantMessage];\n\t\t\t\tmessagesRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tonMessage?.(userMessage);\n\n\t\t\tsetError(null);\n\t\t\tsetIsLoading(true);\n\n\t\t\tconst controller = new AbortController();\n\t\t\tabortRef.current = controller;\n\n\t\t\tconst eventHandler =\n\t\t\t\tonEvent ??\n\t\t\t\t(defaultOnEvent as unknown as (\n\t\t\t\t\tevent: TEvent,\n\t\t\t\t\thelpers: EventHelpers<TPart>,\n\t\t\t\t) => void);\n\t\t\tconst helpers: EventHelpers<TPart> = {\n\t\t\t\tappendText,\n\t\t\t\tappendPart,\n\t\t\t\tsetMessages,\n\t\t\t};\n\n\t\t\t// Fire-and-forget async IIFE — state is managed via React setState.\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(api, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: JSON.stringify({ message: text, ...body }),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`SSE request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!response.body) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\"Response body is null — SSE streaming not supported\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor await (const event of parseSSEStream(\n\t\t\t\t\t\tresponse.body,\n\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t)) {\n\t\t\t\t\t\teventHandler(event as unknown as TEvent, helpers);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream finished — notify via callbacks.\n\t\t\t\t\tconst finalMessages = messagesRef.current;\n\t\t\t\t\tconst lastMessage = finalMessages[finalMessages.length - 1];\n\n\t\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\t\tonMessage?.(lastMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\tonFinish?.(finalMessages);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// AbortError is expected when the user calls `stop()`.\n\t\t\t\t\tif (err instanceof DOMException && err.name === \"AbortError\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\n\t\t\t\t\tsetError(error);\n\t\t\t\t\tonError?.(error);\n\t\t\t\t} finally {\n\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\t\t[\n\t\t\tapi,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tonEvent,\n\t\t\tonMessage,\n\t\t\tonError,\n\t\t\tonFinish,\n\t\t\tappendText,\n\t\t\tappendPart,\n\t\t],\n\t);\n\n\t// -----------------------------------------------------------------------\n\t// Cleanup — abort any in-flight stream when the component unmounts.\n\t// -----------------------------------------------------------------------\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tabortRef.current?.abort();\n\t\t\tabortRef.current = null;\n\t\t};\n\t}, []);\n\n\treturn {\n\t\tmessages,\n\t\tisLoading,\n\t\terror,\n\t\tsendMessage,\n\t\tstop,\n\t\tsetMessages,\n\t};\n}\n"],"mappings":";AAYA;AAAA,EACC;AAAA,EACA;AAAA,EACA,kBAAAA;AAAA,OACM;;;AChBP,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AAOzD,IAAM,oBAAoB;AAMnB,SAAS,cACf,cACA,SACyB;AACzB,QAAM;AAAA,IACL,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,EACb,IAAI,WAAW,CAAC;AAEhB,QAAM,MAAM,OAAiB,IAAI;AACjC,QAAM,gBAAgB,OAAO,IAAI;AACjC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,IAAI;AAKjD,QAAM,SAAS,OAAsB,IAAI;AAEzC,QAAM,iBAAiB,YAAY,MAAM;AACxC,QAAI,OAAO,WAAW,KAAM;AAC5B,WAAO,UAAU,sBAAsB,MAAM;AAC5C,aAAO,UAAU;AACjB,YAAM,KAAK,IAAI;AACf,UAAI,MAAM,cAAc,SAAS;AAChC,WAAG,SAAS,EAAE,KAAK,GAAG,cAAc,SAAS,CAAC;AAAA,MAC/C;AAAA,IACD,CAAC;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAOb,YAAU,MAAM;AACf,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,MAAM,CAAC,QAAS;AAErB,UAAM,eAAe,MAAM;AAC1B,YAAM,WACL,GAAG,eAAe,GAAG,YAAY,GAAG,gBAAgB;AACrD,oBAAc,UAAU;AACxB,oBAAc,CAAC,SAAU,SAAS,WAAW,OAAO,QAAS;AAAA,IAC9D;AAEA,OAAG,iBAAiB,UAAU,cAAc,EAAE,SAAS,KAAK,CAAC;AAC7D,WAAO,MAAM,GAAG,oBAAoB,UAAU,YAAY;AAAA,EAC3D,GAAG,CAAC,SAAS,SAAS,CAAC;AAMvB,YAAU,MAAM;AACf,QAAI,CAAC,WAAW,CAAC,cAAc,QAAS;AACxC,mBAAe;AAAA,EAEhB,GAAG,YAAY;AASf,YAAU,MAAM;AACf,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,MAAM,CAAC,QAAS;AAErB,UAAM,iBAAiB,IAAI,eAAe,cAAc;AAGxD,eAAW,SAAS,GAAG,UAAU;AAChC,qBAAe,QAAQ,KAAK;AAAA,IAC7B;AAGA,UAAM,mBAAmB,IAAI,iBAAiB,CAAC,cAAc;AAC5D,iBAAW,YAAY,WAAW;AACjC,mBAAW,QAAQ,SAAS,YAAY;AACvC,cAAI,gBAAgB,SAAS;AAC5B,2BAAe,QAAQ,IAAI;AAAA,UAC5B;AAAA,QACD;AAAA,MACD;AACA,qBAAe;AAAA,IAChB,CAAC;AAED,qBAAiB,QAAQ,IAAI,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAE/D,WAAO,MAAM;AACZ,qBAAe,WAAW;AAC1B,uBAAiB,WAAW;AAAA,IAC7B;AAAA,EACD,GAAG,CAAC,SAAS,cAAc,CAAC;AAM5B,YAAU,MAAM;AACf,WAAO,MAAM;AACZ,UAAI,OAAO,WAAW,MAAM;AAC3B,6BAAqB,OAAO,OAAO;AACnC,eAAO,UAAU;AAAA,MAClB;AAAA,IACD;AAAA,EACD,GAAG,CAAC,CAAC;AAOL,QAAM,iBAAiB,YAAY,MAAM;AACxC,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,OAAG,SAAS,EAAE,KAAK,GAAG,cAAc,SAAS,CAAC;AAAA,EAC/C,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO,EAAE,KAAK,gBAAgB,WAAW;AAC1C;;;AC1IA,SAAS,sBAAsB;AAC/B,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAWzD,IAAI,UAAU;AAEd,SAAS,aAAqB;AAC7B,SAAO,OAAO,KAAK,IAAI,CAAC,IAAI,EAAE,OAAO;AACtC;AAEA,SAAS,cACR,MACA,OACiB;AACjB,SAAO,EAAE,IAAI,WAAW,GAAG,MAAM,MAAM;AACxC;AAOA,SAAS,eACR,OACA,SACO;AACP,MAAI,MAAM,SAAS,cAAc;AAChC,YAAQ,WAAW,MAAM,KAAK;AAAA,EAC/B;AAGD;AAMO,SAAS,cAGd,SAA0E;AAC3E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,CAAC,UAAU,WAAW,IAAIA;AAAA,IAC/B,mBAAmB,CAAC;AAAA,EACrB;AACA,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAErD,QAAM,WAAWD,QAA+B,IAAI;AAIpD,QAAM,cAAcA,QAAyB,QAAQ;AACrD,cAAY,UAAU;AAOtB,QAAM,aAAaF,aAAY,CAAC,UAAkB;AACjD,gBAAY,CAAC,SAAS;AACrB,YAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,UAAI,CAAC,QAAQ,KAAK,SAAS,YAAa,QAAO;AAE/C,YAAM,QAAQ,CAAC,GAAG,KAAK,KAAK;AAC5B,YAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AAEvC,UAAI,YAAY,SAAS,SAAS,UAAU,UAAU,UAAU;AAE/D,cAAM,WAAW;AACjB,cAAM,MAAM,SAAS,CAAC,IAAI;AAAA,UACzB,GAAG;AAAA,UACH,MAAM,SAAS,OAAO;AAAA,QACvB;AAAA,MACD,OAAO;AAEN,cAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAqB;AAAA,MAC7D;AAEA,YAAM,UAA0B,EAAE,GAAG,MAAM,MAAM;AACjD,aAAO,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE,GAAG,OAAO;AAAA,IACtC,CAAC;AAAA,EACF,GAAG,CAAC,CAAC;AAML,QAAM,aAAaA,aAAY,CAAC,SAAgB;AAC/C,gBAAY,CAAC,SAAS;AACrB,YAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,UAAI,CAAC,QAAQ,KAAK,SAAS,YAAa,QAAO;AAE/C,YAAM,UAA0B;AAAA,QAC/B,GAAG;AAAA,QACH,OAAO,CAAC,GAAG,KAAK,OAAO,IAAI;AAAA,MAC5B;AACA,aAAO,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE,GAAG,OAAO;AAAA,IACtC,CAAC;AAAA,EACF,GAAG,CAAC,CAAC;AAML,QAAM,OAAOA,aAAY,MAAM;AAC9B,aAAS,SAAS,MAAM;AACxB,aAAS,UAAU;AACnB,iBAAa,KAAK;AAAA,EACnB,GAAG,CAAC,CAAC;AAML,QAAM,cAAcA;AAAA,IACnB,CAAC,SAAiB;AAEjB,UAAI,SAAS,SAAS;AACrB;AAAA,MACD;AAEA,YAAM,cAAc,cAAqB,QAAQ;AAAA,QAChD,EAAE,MAAM,QAAQ,KAAK;AAAA,MACtB,CAAC;AACD,YAAM,mBAAmB,cAAqB,aAAa,CAAC,CAAC;AAE7D,kBAAY,CAAC,SAAS;AACrB,cAAM,OAAO,CAAC,GAAG,MAAM,aAAa,gBAAgB;AACpD,oBAAY,UAAU;AACtB,eAAO;AAAA,MACR,CAAC;AAED,kBAAY,WAAW;AAEvB,eAAS,IAAI;AACb,mBAAa,IAAI;AAEjB,YAAM,aAAa,IAAI,gBAAgB;AACvC,eAAS,UAAU;AAEnB,YAAM,eACL,WACC;AAIF,YAAM,UAA+B;AAAA,QACpC;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAGA,OAAC,YAAY;AACZ,YAAI;AACH,gBAAM,WAAW,MAAM,MAAM,KAAK;AAAA,YACjC,QAAQ;AAAA,YACR,SAAS;AAAA,cACR,gBAAgB;AAAA,cAChB,GAAG;AAAA,YACJ;AAAA,YACA,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,YAC/C,QAAQ,WAAW;AAAA,UACpB,CAAC;AAED,cAAI,CAAC,SAAS,IAAI;AACjB,kBAAM,IAAI;AAAA,cACT,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,YAC9D;AAAA,UACD;AAEA,cAAI,CAAC,SAAS,MAAM;AACnB,kBAAM,IAAI;AAAA,cACT;AAAA,YACD;AAAA,UACD;AAEA,2BAAiB,SAAS;AAAA,YACzB,SAAS;AAAA,YACT,WAAW;AAAA,UACZ,GAAG;AACF,yBAAa,OAA4B,OAAO;AAAA,UACjD;AAGA,gBAAM,gBAAgB,YAAY;AAClC,gBAAM,cAAc,cAAc,cAAc,SAAS,CAAC;AAE1D,cAAI,aAAa,SAAS,aAAa;AACtC,wBAAY,WAAW;AAAA,UACxB;AAEA,qBAAW,aAAa;AAAA,QACzB,SAAS,KAAK;AAEb,cAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC7D;AAAA,UACD;AAEA,gBAAMI,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAEhE,mBAASA,MAAK;AACd,oBAAUA,MAAK;AAAA,QAChB,UAAE;AACD,mBAAS,UAAU;AACnB,uBAAa,KAAK;AAAA,QACnB;AAAA,MACD,GAAG;AAAA,IACJ;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAMA,EAAAH,WAAU,MAAM;AACf,WAAO,MAAM;AACZ,eAAS,SAAS,MAAM;AACxB,eAAS,UAAU;AAAA,IACpB;AAAA,EACD,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":["parseSSEStream","useCallback","useEffect","useRef","useState","error"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deltakit/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React hooks for streaming AI conversations with real-time updates",
|
|
5
5
|
"homepage": "https://deltakit.dev",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@deltakit/core": "0.
|
|
32
|
+
"@deltakit/core": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/react": "^18.3.18",
|