@kubuild/ai 0.3.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/LICENSE +21 -0
- package/dist/chunk-27AY2KBI.js +500 -0
- package/dist/chunk-27AY2KBI.js.map +1 -0
- package/dist/chunk-JY4NHD3Q.js +227 -0
- package/dist/chunk-JY4NHD3Q.js.map +1 -0
- package/dist/chunk-UH24LHRY.js +443 -0
- package/dist/chunk-UH24LHRY.js.map +1 -0
- package/dist/client/ai-client.d.ts +59 -0
- package/dist/client/ai-client.d.ts.map +1 -0
- package/dist/client/index.cjs +254 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/normalizer.d.ts +9 -0
- package/dist/core/normalizer.d.ts.map +1 -0
- package/dist/core/prompt-compiler.d.ts +10 -0
- package/dist/core/prompt-compiler.d.ts.map +1 -0
- package/dist/index.cjs +468 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +584 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +336 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/use-ai-chat.d.ts +63 -0
- package/dist/react/use-ai-chat.d.ts.map +1 -0
- package/dist/react/use-ai-generator.d.ts +19 -0
- package/dist/react/use-ai-generator.d.ts.map +1 -0
- package/dist/server/adapters/anthropic.d.ts +30 -0
- package/dist/server/adapters/anthropic.d.ts.map +1 -0
- package/dist/server/adapters/base.d.ts +2 -0
- package/dist/server/adapters/base.d.ts.map +1 -0
- package/dist/server/adapters/custom.d.ts +18 -0
- package/dist/server/adapters/custom.d.ts.map +1 -0
- package/dist/server/adapters/gemini.d.ts +22 -0
- package/dist/server/adapters/gemini.d.ts.map +1 -0
- package/dist/server/adapters/index.cjs +529 -0
- package/dist/server/adapters/index.cjs.map +1 -0
- package/dist/server/adapters/index.d.ts +6 -0
- package/dist/server/adapters/index.d.ts.map +1 -0
- package/dist/server/adapters/index.js +13 -0
- package/dist/server/adapters/index.js.map +1 -0
- package/dist/server/adapters/openai.d.ts +26 -0
- package/dist/server/adapters/openai.d.ts.map +1 -0
- package/dist/server/adapters/stream-utils.d.ts +12 -0
- package/dist/server/adapters/stream-utils.d.ts.map +1 -0
- package/dist/server/engine.d.ts +59 -0
- package/dist/server/engine.d.ts.map +1 -0
- package/dist/server/handler.d.ts +66 -0
- package/dist/server/handler.d.ts.map +1 -0
- package/dist/server/index.cjs +1824 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +890 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAiClient
|
|
3
|
+
} from "../chunk-JY4NHD3Q.js";
|
|
4
|
+
|
|
5
|
+
// src/react/use-ai-generator.ts
|
|
6
|
+
import { useState, useCallback, useRef } from "react";
|
|
7
|
+
function useAiGenerator(options) {
|
|
8
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
9
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
10
|
+
const [currentStep, setCurrentStep] = useState("");
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const abortControllerRef = useRef(null);
|
|
13
|
+
const clientRef = useRef(createAiClient(options));
|
|
14
|
+
const cancel = useCallback(() => {
|
|
15
|
+
if (abortControllerRef.current) {
|
|
16
|
+
abortControllerRef.current.abort();
|
|
17
|
+
abortControllerRef.current = null;
|
|
18
|
+
setIsGenerating(false);
|
|
19
|
+
setIsStreaming(false);
|
|
20
|
+
setCurrentStep("");
|
|
21
|
+
}
|
|
22
|
+
}, []);
|
|
23
|
+
const generatePage = useCallback(
|
|
24
|
+
async (params) => {
|
|
25
|
+
cancel();
|
|
26
|
+
const ac = new AbortController();
|
|
27
|
+
abortControllerRef.current = ac;
|
|
28
|
+
setIsGenerating(true);
|
|
29
|
+
setIsStreaming(false);
|
|
30
|
+
setError(null);
|
|
31
|
+
setCurrentStep("Generating full page...");
|
|
32
|
+
try {
|
|
33
|
+
const doc = await clientRef.current.generatePage(params, {
|
|
34
|
+
signal: ac.signal
|
|
35
|
+
});
|
|
36
|
+
options.onSuccess?.(doc);
|
|
37
|
+
return doc;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (ac.signal.aborted) return null;
|
|
40
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
41
|
+
setError(e);
|
|
42
|
+
options.onError?.(e);
|
|
43
|
+
return null;
|
|
44
|
+
} finally {
|
|
45
|
+
if (abortControllerRef.current === ac) {
|
|
46
|
+
setIsGenerating(false);
|
|
47
|
+
setCurrentStep("");
|
|
48
|
+
abortControllerRef.current = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[cancel, options]
|
|
53
|
+
);
|
|
54
|
+
const streamPage = useCallback(
|
|
55
|
+
async (params, streamCallbacks) => {
|
|
56
|
+
cancel();
|
|
57
|
+
const ac = new AbortController();
|
|
58
|
+
abortControllerRef.current = ac;
|
|
59
|
+
setIsGenerating(true);
|
|
60
|
+
setIsStreaming(true);
|
|
61
|
+
setError(null);
|
|
62
|
+
setCurrentStep("Connecting to AI stream...");
|
|
63
|
+
try {
|
|
64
|
+
const doc = await clientRef.current.streamPage(
|
|
65
|
+
params,
|
|
66
|
+
{
|
|
67
|
+
onStatus: (msg) => {
|
|
68
|
+
setCurrentStep(msg);
|
|
69
|
+
streamCallbacks?.onStatus?.(msg);
|
|
70
|
+
},
|
|
71
|
+
onMetadata: (metadata, rootPage) => {
|
|
72
|
+
streamCallbacks?.onMetadata?.(metadata, rootPage);
|
|
73
|
+
},
|
|
74
|
+
onSection: (section, index, total) => {
|
|
75
|
+
setCurrentStep(`Section ${index + 1}/${total} rendered`);
|
|
76
|
+
streamCallbacks?.onSection?.(section, index, total);
|
|
77
|
+
},
|
|
78
|
+
onComplete: (completedDoc) => {
|
|
79
|
+
setCurrentStep("Completed");
|
|
80
|
+
streamCallbacks?.onComplete?.(completedDoc);
|
|
81
|
+
options.onSuccess?.(completedDoc);
|
|
82
|
+
},
|
|
83
|
+
onError: (err) => {
|
|
84
|
+
setError(err);
|
|
85
|
+
streamCallbacks?.onError?.(err);
|
|
86
|
+
options.onError?.(err);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{ signal: ac.signal }
|
|
90
|
+
);
|
|
91
|
+
return doc;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (ac.signal.aborted) return null;
|
|
94
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
95
|
+
setError(e);
|
|
96
|
+
options.onError?.(e);
|
|
97
|
+
return null;
|
|
98
|
+
} finally {
|
|
99
|
+
if (abortControllerRef.current === ac) {
|
|
100
|
+
setIsGenerating(false);
|
|
101
|
+
setIsStreaming(false);
|
|
102
|
+
setCurrentStep("");
|
|
103
|
+
abortControllerRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[cancel, options]
|
|
108
|
+
);
|
|
109
|
+
const generateSection = useCallback(
|
|
110
|
+
async (params) => {
|
|
111
|
+
cancel();
|
|
112
|
+
const ac = new AbortController();
|
|
113
|
+
abortControllerRef.current = ac;
|
|
114
|
+
setIsGenerating(true);
|
|
115
|
+
setIsStreaming(false);
|
|
116
|
+
setError(null);
|
|
117
|
+
setCurrentStep("Generating section...");
|
|
118
|
+
try {
|
|
119
|
+
const node = await clientRef.current.generateSection(params, {
|
|
120
|
+
signal: ac.signal
|
|
121
|
+
});
|
|
122
|
+
options.onSuccess?.(node);
|
|
123
|
+
return node;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (ac.signal.aborted) return null;
|
|
126
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
127
|
+
setError(e);
|
|
128
|
+
options.onError?.(e);
|
|
129
|
+
return null;
|
|
130
|
+
} finally {
|
|
131
|
+
if (abortControllerRef.current === ac) {
|
|
132
|
+
setIsGenerating(false);
|
|
133
|
+
setCurrentStep("");
|
|
134
|
+
abortControllerRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
[cancel, options]
|
|
139
|
+
);
|
|
140
|
+
const refactorNode = useCallback(
|
|
141
|
+
async (params) => {
|
|
142
|
+
cancel();
|
|
143
|
+
const ac = new AbortController();
|
|
144
|
+
abortControllerRef.current = ac;
|
|
145
|
+
setIsGenerating(true);
|
|
146
|
+
setIsStreaming(false);
|
|
147
|
+
setError(null);
|
|
148
|
+
setCurrentStep("Refactoring node...");
|
|
149
|
+
try {
|
|
150
|
+
const node = await clientRef.current.refactorNode(params, {
|
|
151
|
+
signal: ac.signal
|
|
152
|
+
});
|
|
153
|
+
options.onSuccess?.(node);
|
|
154
|
+
return node;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (ac.signal.aborted) return null;
|
|
157
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
158
|
+
setError(e);
|
|
159
|
+
options.onError?.(e);
|
|
160
|
+
return null;
|
|
161
|
+
} finally {
|
|
162
|
+
if (abortControllerRef.current === ac) {
|
|
163
|
+
setIsGenerating(false);
|
|
164
|
+
setCurrentStep("");
|
|
165
|
+
abortControllerRef.current = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
[cancel, options]
|
|
170
|
+
);
|
|
171
|
+
return {
|
|
172
|
+
generatePage,
|
|
173
|
+
streamPage,
|
|
174
|
+
generateSection,
|
|
175
|
+
refactorNode,
|
|
176
|
+
isGenerating,
|
|
177
|
+
isStreaming,
|
|
178
|
+
currentStep,
|
|
179
|
+
error,
|
|
180
|
+
cancel
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/react/use-ai-chat.ts
|
|
185
|
+
import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect } from "react";
|
|
186
|
+
function useAiChat(options) {
|
|
187
|
+
const [messages, setMessages] = useState2(options.initialMessages || []);
|
|
188
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
189
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
190
|
+
const [error, setError] = useState2(null);
|
|
191
|
+
const abortControllerRef = useRef2(null);
|
|
192
|
+
const clientRef = useRef2(createAiClient(options));
|
|
193
|
+
const historyStorageRef = useRef2(options.historyStorage);
|
|
194
|
+
historyStorageRef.current = options.historyStorage;
|
|
195
|
+
const persistHistory = useCallback2((history) => {
|
|
196
|
+
const adapter = historyStorageRef.current;
|
|
197
|
+
if (!adapter) return;
|
|
198
|
+
try {
|
|
199
|
+
void Promise.resolve(adapter.saveHistory(history)).catch(() => void 0);
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}, []);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const adapter = historyStorageRef.current;
|
|
205
|
+
if (!adapter) return;
|
|
206
|
+
let cancelled = false;
|
|
207
|
+
Promise.resolve(adapter.loadHistory()).then((loaded) => {
|
|
208
|
+
if (!cancelled && Array.isArray(loaded) && loaded.length > 0) {
|
|
209
|
+
setMessages(loaded);
|
|
210
|
+
}
|
|
211
|
+
}).catch((err) => {
|
|
212
|
+
if (cancelled) return;
|
|
213
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
214
|
+
options.onError?.(e);
|
|
215
|
+
});
|
|
216
|
+
return () => {
|
|
217
|
+
cancelled = true;
|
|
218
|
+
};
|
|
219
|
+
}, []);
|
|
220
|
+
const cancel = useCallback2(() => {
|
|
221
|
+
if (abortControllerRef.current) {
|
|
222
|
+
abortControllerRef.current.abort();
|
|
223
|
+
abortControllerRef.current = null;
|
|
224
|
+
setIsLoading(false);
|
|
225
|
+
setIsStreaming(false);
|
|
226
|
+
}
|
|
227
|
+
}, []);
|
|
228
|
+
const clearMessages = useCallback2(() => {
|
|
229
|
+
setMessages([]);
|
|
230
|
+
setError(null);
|
|
231
|
+
persistHistory([]);
|
|
232
|
+
}, [persistHistory]);
|
|
233
|
+
const sendMessage = useCallback2(
|
|
234
|
+
async (content, sendOptions) => {
|
|
235
|
+
if (!content.trim()) return null;
|
|
236
|
+
cancel();
|
|
237
|
+
const ac = new AbortController();
|
|
238
|
+
abortControllerRef.current = ac;
|
|
239
|
+
const userMessage = {
|
|
240
|
+
role: "user",
|
|
241
|
+
content: content.trim(),
|
|
242
|
+
timestamp: Date.now()
|
|
243
|
+
};
|
|
244
|
+
const updatedHistory = [...messages, userMessage];
|
|
245
|
+
setMessages(updatedHistory);
|
|
246
|
+
setIsLoading(true);
|
|
247
|
+
setError(null);
|
|
248
|
+
persistHistory(updatedHistory);
|
|
249
|
+
const useStream = sendOptions?.stream !== false;
|
|
250
|
+
try {
|
|
251
|
+
if (useStream) {
|
|
252
|
+
setIsStreaming(true);
|
|
253
|
+
let assistantIndex = -1;
|
|
254
|
+
setMessages((prev) => {
|
|
255
|
+
assistantIndex = prev.length;
|
|
256
|
+
return [...prev, { role: "assistant", content: "", timestamp: Date.now() }];
|
|
257
|
+
});
|
|
258
|
+
const finalMessage = await clientRef.current.chatStream(
|
|
259
|
+
{
|
|
260
|
+
messages: updatedHistory,
|
|
261
|
+
currentDocument: sendOptions?.currentDocument,
|
|
262
|
+
selectedNodeId: sendOptions?.selectedNodeId,
|
|
263
|
+
systemPrompt: sendOptions?.systemPrompt
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
onChatChunk: (delta, contentSoFar) => {
|
|
267
|
+
options.onChunk?.(delta, contentSoFar);
|
|
268
|
+
setMessages((prev) => {
|
|
269
|
+
if (assistantIndex < 0 || assistantIndex >= prev.length) return prev;
|
|
270
|
+
const next = prev.slice();
|
|
271
|
+
next[assistantIndex] = { ...next[assistantIndex], content: contentSoFar };
|
|
272
|
+
return next;
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
onChatComplete: (message) => {
|
|
276
|
+
setMessages((prev) => {
|
|
277
|
+
if (assistantIndex < 0 || assistantIndex >= prev.length) return prev;
|
|
278
|
+
const next = prev.slice();
|
|
279
|
+
next[assistantIndex] = message;
|
|
280
|
+
return next;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{ signal: ac.signal }
|
|
285
|
+
);
|
|
286
|
+
persistHistory([...updatedHistory, finalMessage]);
|
|
287
|
+
options.onMessage?.(finalMessage);
|
|
288
|
+
return finalMessage;
|
|
289
|
+
}
|
|
290
|
+
const response = await clientRef.current.chat(
|
|
291
|
+
{
|
|
292
|
+
messages: updatedHistory,
|
|
293
|
+
currentDocument: sendOptions?.currentDocument,
|
|
294
|
+
selectedNodeId: sendOptions?.selectedNodeId,
|
|
295
|
+
systemPrompt: sendOptions?.systemPrompt
|
|
296
|
+
},
|
|
297
|
+
{ signal: ac.signal }
|
|
298
|
+
);
|
|
299
|
+
const assistantMsg = response.message;
|
|
300
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
301
|
+
persistHistory([...updatedHistory, assistantMsg]);
|
|
302
|
+
options.onMessage?.(assistantMsg);
|
|
303
|
+
return assistantMsg;
|
|
304
|
+
} catch (err) {
|
|
305
|
+
if (ac.signal.aborted) return null;
|
|
306
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
307
|
+
setError(e);
|
|
308
|
+
options.onError?.(e);
|
|
309
|
+
return null;
|
|
310
|
+
} finally {
|
|
311
|
+
if (abortControllerRef.current === ac) {
|
|
312
|
+
setIsLoading(false);
|
|
313
|
+
setIsStreaming(false);
|
|
314
|
+
abortControllerRef.current = null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
[cancel, messages, options, persistHistory]
|
|
319
|
+
);
|
|
320
|
+
return {
|
|
321
|
+
messages,
|
|
322
|
+
sendMessage,
|
|
323
|
+
isLoading,
|
|
324
|
+
/** Whether the in-flight `sendMessage` call is a token-level stream (STORA-516). */
|
|
325
|
+
isStreaming,
|
|
326
|
+
error,
|
|
327
|
+
cancel,
|
|
328
|
+
clearMessages,
|
|
329
|
+
setMessages
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
export {
|
|
333
|
+
useAiChat,
|
|
334
|
+
useAiGenerator
|
|
335
|
+
};
|
|
336
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/use-ai-generator.ts","../../src/react/use-ai-chat.ts"],"sourcesContent":["import { useState, useCallback, useRef } from 'react';\nimport type { PageDocument, Node } from '@kubuild/schema';\nimport type {\n AiGeneratePageRequest,\n AiGenerateSectionRequest,\n AiRefactorNodeRequest,\n AiStreamCallbacks,\n} from '../types';\nimport { createAiClient, type AiClientOptions, KubuildAiClient } from '../client/ai-client';\n\nexport interface UseAiGeneratorOptions extends AiClientOptions {\n onSuccess?: (data: PageDocument | Node) => void;\n onError?: (error: Error) => void;\n}\n\nexport function useAiGenerator(options: UseAiGeneratorOptions) {\n const [isGenerating, setIsGenerating] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [currentStep, setCurrentStep] = useState<string>('');\n const [error, setError] = useState<Error | null>(null);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n const clientRef = useRef<KubuildAiClient>(createAiClient(options));\n\n // Cancel any in-flight request\n const cancel = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsGenerating(false);\n setIsStreaming(false);\n setCurrentStep('');\n }\n }, []);\n\n /**\n * Standard full page generator (non-streaming).\n */\n const generatePage = useCallback(\n async (params: AiGeneratePageRequest): Promise<PageDocument | null> => {\n cancel();\n const ac = new AbortController();\n abortControllerRef.current = ac;\n\n setIsGenerating(true);\n setIsStreaming(false);\n setError(null);\n setCurrentStep('Generating full page...');\n\n try {\n const doc = await clientRef.current.generatePage(params, {\n signal: ac.signal,\n });\n options.onSuccess?.(doc);\n return doc;\n } catch (err: unknown) {\n if (ac.signal.aborted) return null;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n options.onError?.(e);\n return null;\n } finally {\n if (abortControllerRef.current === ac) {\n setIsGenerating(false);\n setCurrentStep('');\n abortControllerRef.current = null;\n }\n }\n },\n [cancel, options],\n );\n\n /**\n * Progressive section streamer (SSE).\n * Calls callbacks on each step (onStatus, onMetadata, onSection, onComplete).\n */\n const streamPage = useCallback(\n async (\n params: AiGeneratePageRequest,\n streamCallbacks?: AiStreamCallbacks,\n ): Promise<PageDocument | null> => {\n cancel();\n const ac = new AbortController();\n abortControllerRef.current = ac;\n\n setIsGenerating(true);\n setIsStreaming(true);\n setError(null);\n setCurrentStep('Connecting to AI stream...');\n\n try {\n const doc = await clientRef.current.streamPage(\n params,\n {\n onStatus: (msg) => {\n setCurrentStep(msg);\n streamCallbacks?.onStatus?.(msg);\n },\n onMetadata: (metadata, rootPage) => {\n streamCallbacks?.onMetadata?.(metadata, rootPage);\n },\n onSection: (section, index, total) => {\n setCurrentStep(`Section ${index + 1}/${total} rendered`);\n streamCallbacks?.onSection?.(section, index, total);\n },\n onComplete: (completedDoc) => {\n setCurrentStep('Completed');\n streamCallbacks?.onComplete?.(completedDoc);\n options.onSuccess?.(completedDoc);\n },\n onError: (err) => {\n setError(err);\n streamCallbacks?.onError?.(err);\n options.onError?.(err);\n },\n },\n { signal: ac.signal },\n );\n\n return doc;\n } catch (err: unknown) {\n if (ac.signal.aborted) return null;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n options.onError?.(e);\n return null;\n } finally {\n if (abortControllerRef.current === ac) {\n setIsGenerating(false);\n setIsStreaming(false);\n setCurrentStep('');\n abortControllerRef.current = null;\n }\n }\n },\n [cancel, options],\n );\n\n const generateSection = useCallback(\n async (params: AiGenerateSectionRequest): Promise<Node | null> => {\n cancel();\n const ac = new AbortController();\n abortControllerRef.current = ac;\n\n setIsGenerating(true);\n setIsStreaming(false);\n setError(null);\n setCurrentStep('Generating section...');\n\n try {\n const node = await clientRef.current.generateSection(params, {\n signal: ac.signal,\n });\n options.onSuccess?.(node);\n return node;\n } catch (err: unknown) {\n if (ac.signal.aborted) return null;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n options.onError?.(e);\n return null;\n } finally {\n if (abortControllerRef.current === ac) {\n setIsGenerating(false);\n setCurrentStep('');\n abortControllerRef.current = null;\n }\n }\n },\n [cancel, options],\n );\n\n const refactorNode = useCallback(\n async (params: AiRefactorNodeRequest): Promise<Node | null> => {\n cancel();\n const ac = new AbortController();\n abortControllerRef.current = ac;\n\n setIsGenerating(true);\n setIsStreaming(false);\n setError(null);\n setCurrentStep('Refactoring node...');\n\n try {\n const node = await clientRef.current.refactorNode(params, {\n signal: ac.signal,\n });\n options.onSuccess?.(node);\n return node;\n } catch (err: unknown) {\n if (ac.signal.aborted) return null;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n options.onError?.(e);\n return null;\n } finally {\n if (abortControllerRef.current === ac) {\n setIsGenerating(false);\n setCurrentStep('');\n abortControllerRef.current = null;\n }\n }\n },\n [cancel, options],\n );\n\n return {\n generatePage,\n streamPage,\n generateSection,\n refactorNode,\n isGenerating,\n isStreaming,\n currentStep,\n error,\n cancel,\n };\n}\n","import { useState, useCallback, useRef, useEffect } from 'react';\nimport type { PageDocument } from '@kubuild/schema';\nimport type { AiChatMessage, AiChatResponse } from '../types';\nimport { createAiClient, type AiClientOptions, KubuildAiClient } from '../client/ai-client';\n\n/**\n * Optional, host-implemented chat history persistence (STORA-520). `@kubuild/ai` stays\n * storage-agnostic — it never imports `localStorage` or any concrete storage mechanism\n * itself; the host supplies an adapter backed by whatever it wants (browser\n * `localStorage`, IndexedDB, a Stora.page backend call, etc.).\n */\nexport interface AiChatHistoryStorageAdapter {\n /** Called once on mount to seed `messages` before the user sends anything. */\n loadHistory(): Promise<AiChatMessage[]> | AiChatMessage[];\n /**\n * Called at completed-message boundaries (a user message just sent, or an assistant\n * reply that just finished/streamed to completion) — never on every mid-stream token,\n * see `useAiChat`'s persistence notes.\n */\n saveHistory(messages: AiChatMessage[]): Promise<void> | void;\n}\n\nexport interface UseAiChatOptions extends AiClientOptions {\n initialMessages?: AiChatMessage[];\n onMessage?: (message: AiChatMessage) => void;\n onError?: (error: Error) => void;\n /**\n * Fired for every partial token/chunk of a streaming chat response (STORA-516),\n * mirroring `useAiGenerator`'s `AiStreamCallbacks` naming convention. Optional — the\n * `messages` array already updates incrementally on its own, this is only for hosts\n * that want a side-channel (e.g. custom rendering, telemetry) into the raw chunks.\n */\n onChunk?: (delta: string, content: string) => void;\n /**\n * Optional storage adapter (STORA-520) used to persist/restore chat history across\n * reloads. Without it, behavior is exactly as before: pure in-memory React state, lost\n * on reload/unmount. `loadHistory()` is called once on mount to hydrate `messages`\n * (only overriding `initialMessages` when it resolves a non-empty array);\n * `saveHistory()` is called at completed-message boundaries — after a user message is\n * appended, and after an assistant reply finishes (streamed or not) — intentionally\n * *not* on every streaming chunk, to avoid hammering the adapter mid-stream.\n */\n historyStorage?: AiChatHistoryStorageAdapter;\n}\n\nexport interface SendMessageOptions {\n currentDocument?: PageDocument;\n selectedNodeId?: string;\n systemPrompt?: string;\n /**\n * Opt out of token-level streaming for this call (STORA-515/516 default to `true`).\n * When `false`, behaves exactly like the pre-streaming single request/response call.\n */\n stream?: boolean;\n}\n\nexport function useAiChat(options: UseAiChatOptions) {\n const [messages, setMessages] = useState<AiChatMessage[]>(options.initialMessages || []);\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n const clientRef = useRef<KubuildAiClient>(createAiClient(options));\n const historyStorageRef = useRef(options.historyStorage);\n historyStorageRef.current = options.historyStorage;\n\n /**\n * STORA-520 — best-effort persistence: a storage failure (quota exceeded, network\n * error on a remote adapter, etc.) must never break the chat itself, so errors are\n * swallowed here rather than surfaced through `setError`/`onError` (those are reserved\n * for the actual chat request failing).\n */\n const persistHistory = useCallback((history: AiChatMessage[]) => {\n const adapter = historyStorageRef.current;\n if (!adapter) return;\n try {\n void Promise.resolve(adapter.saveHistory(history)).catch(() => undefined);\n } catch {\n // Synchronous throw from a non-Promise-returning adapter — ignore, same reasoning.\n }\n }, []);\n\n // STORA-520 — hydrate from storage once on mount. Only overrides `initialMessages`\n // when the adapter actually resolves a non-empty history, so a fresh/empty adapter\n // (e.g. first-ever visit) doesn't clobber a host-provided `initialMessages` seed.\n useEffect(() => {\n const adapter = historyStorageRef.current;\n if (!adapter) return;\n let cancelled = false;\n Promise.resolve(adapter.loadHistory())\n .then((loaded) => {\n if (!cancelled && Array.isArray(loaded) && loaded.length > 0) {\n setMessages(loaded);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const e = err instanceof Error ? err : new Error(String(err));\n options.onError?.(e);\n });\n return () => {\n cancelled = true;\n };\n // Deliberately empty deps: `historyStorage`/`onError` are read via refs/closures at\n // call time and this must run exactly once per mount, not whenever the caller passes\n // a fresh options object.\n }, []);\n\n const cancel = useCallback(() => {\n if (abortControllerRef.current) {\n // Aborts the underlying fetch (and, for a streaming request, the in-flight\n // ReadableStream read) — not just local UI state (STORA-516 AC).\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsLoading(false);\n setIsStreaming(false);\n }\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n setError(null);\n persistHistory([]);\n }, [persistHistory]);\n\n const sendMessage = useCallback(\n async (content: string, sendOptions?: SendMessageOptions): Promise<AiChatMessage | null> => {\n if (!content.trim()) return null;\n\n cancel();\n const ac = new AbortController();\n abortControllerRef.current = ac;\n\n const userMessage: AiChatMessage = {\n role: 'user',\n content: content.trim(),\n timestamp: Date.now(),\n };\n\n const updatedHistory = [...messages, userMessage];\n setMessages(updatedHistory);\n setIsLoading(true);\n setError(null);\n // Completed-message boundary #1 (STORA-520): the user's message is final the\n // instant it's sent — persist it now rather than waiting for the assistant reply,\n // so a reload mid-request doesn't lose it.\n persistHistory(updatedHistory);\n\n const useStream = sendOptions?.stream !== false;\n\n try {\n if (useStream) {\n setIsStreaming(true);\n\n // Placeholder assistant message, filled in incrementally as chunks arrive.\n let assistantIndex = -1;\n setMessages((prev) => {\n assistantIndex = prev.length;\n return [...prev, { role: 'assistant', content: '', timestamp: Date.now() }];\n });\n\n const finalMessage = await clientRef.current.chatStream(\n {\n messages: updatedHistory,\n currentDocument: sendOptions?.currentDocument,\n selectedNodeId: sendOptions?.selectedNodeId,\n systemPrompt: sendOptions?.systemPrompt,\n },\n {\n onChatChunk: (delta, contentSoFar) => {\n options.onChunk?.(delta, contentSoFar);\n setMessages((prev) => {\n if (assistantIndex < 0 || assistantIndex >= prev.length) return prev;\n const next = prev.slice();\n next[assistantIndex] = { ...next[assistantIndex], content: contentSoFar };\n return next;\n });\n },\n onChatComplete: (message) => {\n setMessages((prev) => {\n if (assistantIndex < 0 || assistantIndex >= prev.length) return prev;\n const next = prev.slice();\n next[assistantIndex] = message;\n return next;\n });\n },\n },\n { signal: ac.signal },\n );\n\n // Completed-message boundary #2 (STORA-520): the stream finished — persist the\n // fully assembled assistant message, never the intermediate per-chunk content\n // (`onChatChunk` above deliberately does not call `persistHistory`).\n persistHistory([...updatedHistory, finalMessage]);\n options.onMessage?.(finalMessage);\n return finalMessage;\n }\n\n const response: AiChatResponse = await clientRef.current.chat(\n {\n messages: updatedHistory,\n currentDocument: sendOptions?.currentDocument,\n selectedNodeId: sendOptions?.selectedNodeId,\n systemPrompt: sendOptions?.systemPrompt,\n },\n { signal: ac.signal },\n );\n\n const assistantMsg = response.message;\n setMessages((prev) => [...prev, assistantMsg]);\n // Completed-message boundary #2 (non-streaming variant): a single request/response\n // call has no intermediate chunks at all, so this is simply \"after the reply\".\n persistHistory([...updatedHistory, assistantMsg]);\n options.onMessage?.(assistantMsg);\n return assistantMsg;\n } catch (err: unknown) {\n if (ac.signal.aborted) return null;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n options.onError?.(e);\n return null;\n } finally {\n if (abortControllerRef.current === ac) {\n setIsLoading(false);\n setIsStreaming(false);\n abortControllerRef.current = null;\n }\n }\n },\n [cancel, messages, options, persistHistory],\n );\n\n return {\n messages,\n sendMessage,\n isLoading,\n /** Whether the in-flight `sendMessage` call is a token-level stream (STORA-516). */\n isStreaming,\n error,\n cancel,\n clearMessages,\n setMessages,\n };\n}\n"],"mappings":";;;;;AAAA,SAAS,UAAU,aAAa,cAAc;AAevC,SAAS,eAAe,SAAgC;AAC7D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAiB,EAAE;AACzD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,qBAAqB,OAA+B,IAAI;AAC9D,QAAM,YAAY,OAAwB,eAAe,OAAO,CAAC;AAGjE,QAAM,SAAS,YAAY,MAAM;AAC/B,QAAI,mBAAmB,SAAS;AAC9B,yBAAmB,QAAQ,MAAM;AACjC,yBAAmB,UAAU;AAC7B,sBAAgB,KAAK;AACrB,qBAAe,KAAK;AACpB,qBAAe,EAAE;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,CAAC;AAKL,QAAM,eAAe;AAAA,IACnB,OAAO,WAAgE;AACrE,aAAO;AACP,YAAM,KAAK,IAAI,gBAAgB;AAC/B,yBAAmB,UAAU;AAE7B,sBAAgB,IAAI;AACpB,qBAAe,KAAK;AACpB,eAAS,IAAI;AACb,qBAAe,yBAAyB;AAExC,UAAI;AACF,cAAM,MAAM,MAAM,UAAU,QAAQ,aAAa,QAAQ;AAAA,UACvD,QAAQ,GAAG;AAAA,QACb,CAAC;AACD,gBAAQ,YAAY,GAAG;AACvB,eAAO;AAAA,MACT,SAAS,KAAc;AACrB,YAAI,GAAG,OAAO,QAAS,QAAO;AAC9B,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,gBAAQ,UAAU,CAAC;AACnB,eAAO;AAAA,MACT,UAAE;AACA,YAAI,mBAAmB,YAAY,IAAI;AACrC,0BAAgB,KAAK;AACrB,yBAAe,EAAE;AACjB,6BAAmB,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,OAAO;AAAA,EAClB;AAMA,QAAM,aAAa;AAAA,IACjB,OACE,QACA,oBACiC;AACjC,aAAO;AACP,YAAM,KAAK,IAAI,gBAAgB;AAC/B,yBAAmB,UAAU;AAE7B,sBAAgB,IAAI;AACpB,qBAAe,IAAI;AACnB,eAAS,IAAI;AACb,qBAAe,4BAA4B;AAE3C,UAAI;AACF,cAAM,MAAM,MAAM,UAAU,QAAQ;AAAA,UAClC;AAAA,UACA;AAAA,YACE,UAAU,CAAC,QAAQ;AACjB,6BAAe,GAAG;AAClB,+BAAiB,WAAW,GAAG;AAAA,YACjC;AAAA,YACA,YAAY,CAAC,UAAU,aAAa;AAClC,+BAAiB,aAAa,UAAU,QAAQ;AAAA,YAClD;AAAA,YACA,WAAW,CAAC,SAAS,OAAO,UAAU;AACpC,6BAAe,WAAW,QAAQ,CAAC,IAAI,KAAK,WAAW;AACvD,+BAAiB,YAAY,SAAS,OAAO,KAAK;AAAA,YACpD;AAAA,YACA,YAAY,CAAC,iBAAiB;AAC5B,6BAAe,WAAW;AAC1B,+BAAiB,aAAa,YAAY;AAC1C,sBAAQ,YAAY,YAAY;AAAA,YAClC;AAAA,YACA,SAAS,CAAC,QAAQ;AAChB,uBAAS,GAAG;AACZ,+BAAiB,UAAU,GAAG;AAC9B,sBAAQ,UAAU,GAAG;AAAA,YACvB;AAAA,UACF;AAAA,UACA,EAAE,QAAQ,GAAG,OAAO;AAAA,QACtB;AAEA,eAAO;AAAA,MACT,SAAS,KAAc;AACrB,YAAI,GAAG,OAAO,QAAS,QAAO;AAC9B,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,gBAAQ,UAAU,CAAC;AACnB,eAAO;AAAA,MACT,UAAE;AACA,YAAI,mBAAmB,YAAY,IAAI;AACrC,0BAAgB,KAAK;AACrB,yBAAe,KAAK;AACpB,yBAAe,EAAE;AACjB,6BAAmB,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,OAAO;AAAA,EAClB;AAEA,QAAM,kBAAkB;AAAA,IACtB,OAAO,WAA2D;AAChE,aAAO;AACP,YAAM,KAAK,IAAI,gBAAgB;AAC/B,yBAAmB,UAAU;AAE7B,sBAAgB,IAAI;AACpB,qBAAe,KAAK;AACpB,eAAS,IAAI;AACb,qBAAe,uBAAuB;AAEtC,UAAI;AACF,cAAM,OAAO,MAAM,UAAU,QAAQ,gBAAgB,QAAQ;AAAA,UAC3D,QAAQ,GAAG;AAAA,QACb,CAAC;AACD,gBAAQ,YAAY,IAAI;AACxB,eAAO;AAAA,MACT,SAAS,KAAc;AACrB,YAAI,GAAG,OAAO,QAAS,QAAO;AAC9B,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,gBAAQ,UAAU,CAAC;AACnB,eAAO;AAAA,MACT,UAAE;AACA,YAAI,mBAAmB,YAAY,IAAI;AACrC,0BAAgB,KAAK;AACrB,yBAAe,EAAE;AACjB,6BAAmB,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,OAAO;AAAA,EAClB;AAEA,QAAM,eAAe;AAAA,IACnB,OAAO,WAAwD;AAC7D,aAAO;AACP,YAAM,KAAK,IAAI,gBAAgB;AAC/B,yBAAmB,UAAU;AAE7B,sBAAgB,IAAI;AACpB,qBAAe,KAAK;AACpB,eAAS,IAAI;AACb,qBAAe,qBAAqB;AAEpC,UAAI;AACF,cAAM,OAAO,MAAM,UAAU,QAAQ,aAAa,QAAQ;AAAA,UACxD,QAAQ,GAAG;AAAA,QACb,CAAC;AACD,gBAAQ,YAAY,IAAI;AACxB,eAAO;AAAA,MACT,SAAS,KAAc;AACrB,YAAI,GAAG,OAAO,QAAS,QAAO;AAC9B,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,gBAAQ,UAAU,CAAC;AACnB,eAAO;AAAA,MACT,UAAE;AACA,YAAI,mBAAmB,YAAY,IAAI;AACrC,0BAAgB,KAAK;AACrB,yBAAe,EAAE;AACjB,6BAAmB,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,OAAO;AAAA,EAClB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACzNA,SAAS,YAAAA,WAAU,eAAAC,cAAa,UAAAC,SAAQ,iBAAiB;AAwDlD,SAAS,UAAU,SAA2B;AACnD,QAAM,CAAC,UAAU,WAAW,IAAIC,UAA0B,QAAQ,mBAAmB,CAAC,CAAC;AACvF,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,KAAK;AACpD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAErD,QAAM,qBAAqBC,QAA+B,IAAI;AAC9D,QAAM,YAAYA,QAAwB,eAAe,OAAO,CAAC;AACjE,QAAM,oBAAoBA,QAAO,QAAQ,cAAc;AACvD,oBAAkB,UAAU,QAAQ;AAQpC,QAAM,iBAAiBC,aAAY,CAAC,YAA6B;AAC/D,UAAM,UAAU,kBAAkB;AAClC,QAAI,CAAC,QAAS;AACd,QAAI;AACF,WAAK,QAAQ,QAAQ,QAAQ,YAAY,OAAO,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1E,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAKL,YAAU,MAAM;AACd,UAAM,UAAU,kBAAkB;AAClC,QAAI,CAAC,QAAS;AACd,QAAI,YAAY;AAChB,YAAQ,QAAQ,QAAQ,YAAY,CAAC,EAClC,KAAK,CAAC,WAAW;AAChB,UAAI,CAAC,aAAa,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG;AAC5D,oBAAY,MAAM;AAAA,MACpB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,UAAW;AACf,YAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,cAAQ,UAAU,CAAC;AAAA,IACrB,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EAIF,GAAG,CAAC,CAAC;AAEL,QAAM,SAASA,aAAY,MAAM;AAC/B,QAAI,mBAAmB,SAAS;AAG9B,yBAAmB,QAAQ,MAAM;AACjC,yBAAmB,UAAU;AAC7B,mBAAa,KAAK;AAClB,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgBA,aAAY,MAAM;AACtC,gBAAY,CAAC,CAAC;AACd,aAAS,IAAI;AACb,mBAAe,CAAC,CAAC;AAAA,EACnB,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,cAAcA;AAAA,IAClB,OAAO,SAAiB,gBAAoE;AAC1F,UAAI,CAAC,QAAQ,KAAK,EAAG,QAAO;AAE5B,aAAO;AACP,YAAM,KAAK,IAAI,gBAAgB;AAC/B,yBAAmB,UAAU;AAE7B,YAAM,cAA6B;AAAA,QACjC,MAAM;AAAA,QACN,SAAS,QAAQ,KAAK;AAAA,QACtB,WAAW,KAAK,IAAI;AAAA,MACtB;AAEA,YAAM,iBAAiB,CAAC,GAAG,UAAU,WAAW;AAChD,kBAAY,cAAc;AAC1B,mBAAa,IAAI;AACjB,eAAS,IAAI;AAIb,qBAAe,cAAc;AAE7B,YAAM,YAAY,aAAa,WAAW;AAE1C,UAAI;AACF,YAAI,WAAW;AACb,yBAAe,IAAI;AAGnB,cAAI,iBAAiB;AACrB,sBAAY,CAAC,SAAS;AACpB,6BAAiB,KAAK;AACtB,mBAAO,CAAC,GAAG,MAAM,EAAE,MAAM,aAAa,SAAS,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,UAC5E,CAAC;AAED,gBAAM,eAAe,MAAM,UAAU,QAAQ;AAAA,YAC3C;AAAA,cACE,UAAU;AAAA,cACV,iBAAiB,aAAa;AAAA,cAC9B,gBAAgB,aAAa;AAAA,cAC7B,cAAc,aAAa;AAAA,YAC7B;AAAA,YACA;AAAA,cACE,aAAa,CAAC,OAAO,iBAAiB;AACpC,wBAAQ,UAAU,OAAO,YAAY;AACrC,4BAAY,CAAC,SAAS;AACpB,sBAAI,iBAAiB,KAAK,kBAAkB,KAAK,OAAQ,QAAO;AAChE,wBAAM,OAAO,KAAK,MAAM;AACxB,uBAAK,cAAc,IAAI,EAAE,GAAG,KAAK,cAAc,GAAG,SAAS,aAAa;AACxE,yBAAO;AAAA,gBACT,CAAC;AAAA,cACH;AAAA,cACA,gBAAgB,CAAC,YAAY;AAC3B,4BAAY,CAAC,SAAS;AACpB,sBAAI,iBAAiB,KAAK,kBAAkB,KAAK,OAAQ,QAAO;AAChE,wBAAM,OAAO,KAAK,MAAM;AACxB,uBAAK,cAAc,IAAI;AACvB,yBAAO;AAAA,gBACT,CAAC;AAAA,cACH;AAAA,YACF;AAAA,YACA,EAAE,QAAQ,GAAG,OAAO;AAAA,UACtB;AAKA,yBAAe,CAAC,GAAG,gBAAgB,YAAY,CAAC;AAChD,kBAAQ,YAAY,YAAY;AAChC,iBAAO;AAAA,QACT;AAEA,cAAM,WAA2B,MAAM,UAAU,QAAQ;AAAA,UACvD;AAAA,YACE,UAAU;AAAA,YACV,iBAAiB,aAAa;AAAA,YAC9B,gBAAgB,aAAa;AAAA,YAC7B,cAAc,aAAa;AAAA,UAC7B;AAAA,UACA,EAAE,QAAQ,GAAG,OAAO;AAAA,QACtB;AAEA,cAAM,eAAe,SAAS;AAC9B,oBAAY,CAAC,SAAS,CAAC,GAAG,MAAM,YAAY,CAAC;AAG7C,uBAAe,CAAC,GAAG,gBAAgB,YAAY,CAAC;AAChD,gBAAQ,YAAY,YAAY;AAChC,eAAO;AAAA,MACT,SAAS,KAAc;AACrB,YAAI,GAAG,OAAO,QAAS,QAAO;AAC9B,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,gBAAQ,UAAU,CAAC;AACnB,eAAO;AAAA,MACT,UAAE;AACA,YAAI,mBAAmB,YAAY,IAAI;AACrC,uBAAa,KAAK;AAClB,yBAAe,KAAK;AACpB,6BAAmB,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,UAAU,SAAS,cAAc;AAAA,EAC5C;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["useState","useCallback","useRef","useState","useRef","useCallback"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PageDocument } from '@kubuild/schema';
|
|
2
|
+
import type { AiChatMessage } from '../types';
|
|
3
|
+
import { type AiClientOptions } from '../client/ai-client';
|
|
4
|
+
/**
|
|
5
|
+
* Optional, host-implemented chat history persistence (STORA-520). `@kubuild/ai` stays
|
|
6
|
+
* storage-agnostic — it never imports `localStorage` or any concrete storage mechanism
|
|
7
|
+
* itself; the host supplies an adapter backed by whatever it wants (browser
|
|
8
|
+
* `localStorage`, IndexedDB, a Stora.page backend call, etc.).
|
|
9
|
+
*/
|
|
10
|
+
export interface AiChatHistoryStorageAdapter {
|
|
11
|
+
/** Called once on mount to seed `messages` before the user sends anything. */
|
|
12
|
+
loadHistory(): Promise<AiChatMessage[]> | AiChatMessage[];
|
|
13
|
+
/**
|
|
14
|
+
* Called at completed-message boundaries (a user message just sent, or an assistant
|
|
15
|
+
* reply that just finished/streamed to completion) — never on every mid-stream token,
|
|
16
|
+
* see `useAiChat`'s persistence notes.
|
|
17
|
+
*/
|
|
18
|
+
saveHistory(messages: AiChatMessage[]): Promise<void> | void;
|
|
19
|
+
}
|
|
20
|
+
export interface UseAiChatOptions extends AiClientOptions {
|
|
21
|
+
initialMessages?: AiChatMessage[];
|
|
22
|
+
onMessage?: (message: AiChatMessage) => void;
|
|
23
|
+
onError?: (error: Error) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Fired for every partial token/chunk of a streaming chat response (STORA-516),
|
|
26
|
+
* mirroring `useAiGenerator`'s `AiStreamCallbacks` naming convention. Optional — the
|
|
27
|
+
* `messages` array already updates incrementally on its own, this is only for hosts
|
|
28
|
+
* that want a side-channel (e.g. custom rendering, telemetry) into the raw chunks.
|
|
29
|
+
*/
|
|
30
|
+
onChunk?: (delta: string, content: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Optional storage adapter (STORA-520) used to persist/restore chat history across
|
|
33
|
+
* reloads. Without it, behavior is exactly as before: pure in-memory React state, lost
|
|
34
|
+
* on reload/unmount. `loadHistory()` is called once on mount to hydrate `messages`
|
|
35
|
+
* (only overriding `initialMessages` when it resolves a non-empty array);
|
|
36
|
+
* `saveHistory()` is called at completed-message boundaries — after a user message is
|
|
37
|
+
* appended, and after an assistant reply finishes (streamed or not) — intentionally
|
|
38
|
+
* *not* on every streaming chunk, to avoid hammering the adapter mid-stream.
|
|
39
|
+
*/
|
|
40
|
+
historyStorage?: AiChatHistoryStorageAdapter;
|
|
41
|
+
}
|
|
42
|
+
export interface SendMessageOptions {
|
|
43
|
+
currentDocument?: PageDocument;
|
|
44
|
+
selectedNodeId?: string;
|
|
45
|
+
systemPrompt?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Opt out of token-level streaming for this call (STORA-515/516 default to `true`).
|
|
48
|
+
* When `false`, behaves exactly like the pre-streaming single request/response call.
|
|
49
|
+
*/
|
|
50
|
+
stream?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function useAiChat(options: UseAiChatOptions): {
|
|
53
|
+
messages: AiChatMessage[];
|
|
54
|
+
sendMessage: (content: string, sendOptions?: SendMessageOptions) => Promise<AiChatMessage | null>;
|
|
55
|
+
isLoading: boolean;
|
|
56
|
+
/** Whether the in-flight `sendMessage` call is a token-level stream (STORA-516). */
|
|
57
|
+
isStreaming: boolean;
|
|
58
|
+
error: Error | null;
|
|
59
|
+
cancel: () => void;
|
|
60
|
+
clearMessages: () => void;
|
|
61
|
+
setMessages: import("react").Dispatch<import("react").SetStateAction<AiChatMessage[]>>;
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=use-ai-chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-ai-chat.d.ts","sourceRoot":"","sources":["../../src/react/use-ai-chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAkB,KAAK,eAAe,EAAmB,MAAM,qBAAqB,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,8EAA8E;IAC9E,WAAW,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC;IAC1D;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,eAAe,CAAC,EAAE,aAAa,EAAE,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,2BAA2B,CAAC;CAC9C;AAED,MAAM,WAAW,kBAAkB;IACjC,eAAe,CAAC,EAAE,YAAY,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB;;2BAuE/B,MAAM,gBAAgB,kBAAkB,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;;IA8GxF,oFAAoF;;;;;;EAOvF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PageDocument, Node } from '@kubuild/schema';
|
|
2
|
+
import type { AiGeneratePageRequest, AiGenerateSectionRequest, AiRefactorNodeRequest, AiStreamCallbacks } from '../types';
|
|
3
|
+
import { type AiClientOptions } from '../client/ai-client';
|
|
4
|
+
export interface UseAiGeneratorOptions extends AiClientOptions {
|
|
5
|
+
onSuccess?: (data: PageDocument | Node) => void;
|
|
6
|
+
onError?: (error: Error) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useAiGenerator(options: UseAiGeneratorOptions): {
|
|
9
|
+
generatePage: (params: AiGeneratePageRequest) => Promise<PageDocument | null>;
|
|
10
|
+
streamPage: (params: AiGeneratePageRequest, streamCallbacks?: AiStreamCallbacks) => Promise<PageDocument | null>;
|
|
11
|
+
generateSection: (params: AiGenerateSectionRequest) => Promise<Node | null>;
|
|
12
|
+
refactorNode: (params: AiRefactorNodeRequest) => Promise<Node | null>;
|
|
13
|
+
isGenerating: boolean;
|
|
14
|
+
isStreaming: boolean;
|
|
15
|
+
currentStep: string;
|
|
16
|
+
error: Error | null;
|
|
17
|
+
cancel: () => void;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=use-ai-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-ai-generator.d.ts","sourceRoot":"","sources":["../../src/react/use-ai-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EACV,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAkB,KAAK,eAAe,EAAmB,MAAM,qBAAqB,CAAC;AAE5F,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB;2BAwB1C,qBAAqB,KAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;yBAuCzD,qBAAqB,oBACX,iBAAiB,KAClC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;8BA2DhB,wBAAwB,KAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;2BAkC/C,qBAAqB,KAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;;;;;;EA4C9D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AiProviderAdapter, AiProviderGenerateParams, AiProviderGenerateResult } from '../../types';
|
|
2
|
+
export interface AnthropicAdapterOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
maxTokens?: number;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
anthropicVersion?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export declare class AnthropicAdapter implements AiProviderAdapter {
|
|
12
|
+
readonly name = "anthropic";
|
|
13
|
+
private apiKey;
|
|
14
|
+
private model;
|
|
15
|
+
private maxTokens;
|
|
16
|
+
private temperature;
|
|
17
|
+
private baseUrl;
|
|
18
|
+
private anthropicVersion;
|
|
19
|
+
private customHeaders?;
|
|
20
|
+
constructor(options: AnthropicAdapterOptions);
|
|
21
|
+
private buildMessages;
|
|
22
|
+
generate(params: AiProviderGenerateParams): Promise<AiProviderGenerateResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Token-level streaming via Anthropic's `stream: true` Messages API (STORA-515).
|
|
25
|
+
* Parses the `content_block_delta` (`text_delta`) events for incremental text and the
|
|
26
|
+
* `message_start`/`message_delta` events for usage accounting.
|
|
27
|
+
*/
|
|
28
|
+
generateStream(params: AiProviderGenerateParams): AsyncGenerator<string, AiProviderGenerateResult, void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,QAAQ,CAAC,IAAI,eAAe;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,aAAa,CAAC,CAAyB;gBAEnC,OAAO,EAAE,uBAAuB;IAa5C,OAAO,CAAC,aAAa;IAgBf,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA4DnF;;;;OAIG;IACI,cAAc,CACnB,MAAM,EAAE,wBAAwB,GAC/B,cAAc,CAAC,MAAM,EAAE,wBAAwB,EAAE,IAAI,CAAC;CA6E1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/base.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AiProviderAdapter, AiProviderGenerateParams, AiProviderGenerateResult } from '../../types';
|
|
2
|
+
export interface CustomHttpAdapterOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
formatRequestBody?: (params: AiProviderGenerateParams) => unknown;
|
|
7
|
+
parseResponse?: (json: unknown) => string;
|
|
8
|
+
}
|
|
9
|
+
export declare class CustomHttpAdapter implements AiProviderAdapter {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
private url;
|
|
12
|
+
private headers?;
|
|
13
|
+
private formatRequestBody;
|
|
14
|
+
private parseResponse;
|
|
15
|
+
constructor(options: CustomHttpAdapterOptions);
|
|
16
|
+
generate(params: AiProviderGenerateParams): Promise<AiProviderGenerateResult>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC;IAClE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CAC3C;AAED,qBAAa,iBAAkB,YAAW,iBAAiB;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,OAAO,CAAC,CAAyB;IACzC,OAAO,CAAC,iBAAiB,CAAgD;IACzE,OAAO,CAAC,aAAa,CAA4B;gBAErC,OAAO,EAAE,wBAAwB;IA8CvC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAyBpF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AiProviderAdapter, AiProviderGenerateParams, AiProviderGenerateResult } from '../../types';
|
|
2
|
+
export interface GeminiAdapterOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
temperature?: number;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class GeminiAdapter implements AiProviderAdapter {
|
|
9
|
+
readonly name = "gemini";
|
|
10
|
+
private apiKey;
|
|
11
|
+
private model;
|
|
12
|
+
private temperature;
|
|
13
|
+
private baseUrl;
|
|
14
|
+
constructor(options: GeminiAdapterOptions);
|
|
15
|
+
private buildContents;
|
|
16
|
+
generate(params: AiProviderGenerateParams): Promise<AiProviderGenerateResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Token-level streaming via Gemini's `streamGenerateContent` SSE endpoint (STORA-515).
|
|
19
|
+
*/
|
|
20
|
+
generateStream(params: AiProviderGenerateParams): AsyncGenerator<string, AiProviderGenerateResult, void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,aAAc,YAAW,iBAAiB;IACrD,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,oBAAoB;IAWzC,OAAO,CAAC,aAAa;IAiBf,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAkEnF;;OAEG;IACI,cAAc,CACnB,MAAM,EAAE,wBAAwB,GAC/B,cAAc,CAAC,MAAM,EAAE,wBAAwB,EAAE,IAAI,CAAC;CAmE1D"}
|