@sqlrooms/ai-core 0.26.0-rc.6 → 0.26.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/README.md +171 -2
- package/dist/AiSlice.d.ts +35 -5
- package/dist/AiSlice.d.ts.map +1 -1
- package/dist/AiSlice.js +148 -14
- package/dist/AiSlice.js.map +1 -1
- package/dist/agents/AgentUtils.d.ts +61 -0
- package/dist/agents/AgentUtils.d.ts.map +1 -0
- package/dist/agents/AgentUtils.js +90 -0
- package/dist/agents/AgentUtils.js.map +1 -0
- package/dist/chatTransport.d.ts +17 -4
- package/dist/chatTransport.d.ts.map +1 -1
- package/dist/chatTransport.js +244 -19
- package/dist/chatTransport.js.map +1 -1
- package/dist/components/AnalysisAnswer.d.ts +2 -0
- package/dist/components/AnalysisAnswer.d.ts.map +1 -1
- package/dist/components/AnalysisAnswer.js +4 -2
- package/dist/components/AnalysisAnswer.js.map +1 -1
- package/dist/components/AnalysisResult.d.ts +7 -0
- package/dist/components/AnalysisResult.d.ts.map +1 -1
- package/dist/components/AnalysisResult.js +42 -42
- package/dist/components/AnalysisResult.js.map +1 -1
- package/dist/components/AnalysisResultsContainer.d.ts +4 -0
- package/dist/components/AnalysisResultsContainer.d.ts.map +1 -1
- package/dist/components/AnalysisResultsContainer.js +11 -4
- package/dist/components/AnalysisResultsContainer.js.map +1 -1
- package/dist/components/DeleteConfirmationDialog.d.ts +14 -0
- package/dist/components/DeleteConfirmationDialog.d.ts.map +1 -0
- package/dist/components/DeleteConfirmationDialog.js +6 -0
- package/dist/components/DeleteConfirmationDialog.js.map +1 -0
- package/dist/components/GroupedMessageParts.d.ts +25 -0
- package/dist/components/GroupedMessageParts.d.ts.map +1 -0
- package/dist/components/GroupedMessageParts.js +34 -0
- package/dist/components/GroupedMessageParts.js.map +1 -0
- package/dist/components/MessagePartsList.d.ts +23 -0
- package/dist/components/MessagePartsList.d.ts.map +1 -0
- package/dist/components/MessagePartsList.js +27 -0
- package/dist/components/MessagePartsList.js.map +1 -0
- package/dist/components/PromptSuggestions.d.ts +32 -0
- package/dist/components/PromptSuggestions.d.ts.map +1 -0
- package/dist/components/PromptSuggestions.js +69 -0
- package/dist/components/PromptSuggestions.js.map +1 -0
- package/dist/components/QueryControls.d.ts.map +1 -1
- package/dist/components/QueryControls.js +11 -4
- package/dist/components/QueryControls.js.map +1 -1
- package/dist/components/ReasoningBox.d.ts +21 -0
- package/dist/components/ReasoningBox.d.ts.map +1 -0
- package/dist/components/ReasoningBox.js +29 -0
- package/dist/components/ReasoningBox.js.map +1 -0
- package/dist/components/ToolCallInfo.d.ts.map +1 -1
- package/dist/components/ToolCallInfo.js +1 -11
- package/dist/components/ToolCallInfo.js.map +1 -1
- package/dist/components/ToolPartRenderer.d.ts +20 -0
- package/dist/components/ToolPartRenderer.d.ts.map +1 -0
- package/dist/components/ToolPartRenderer.js +85 -0
- package/dist/components/ToolPartRenderer.js.map +1 -0
- package/dist/components/tools/ToolErrorMessage.d.ts.map +1 -1
- package/dist/components/tools/ToolErrorMessage.js +1 -2
- package/dist/components/tools/ToolErrorMessage.js.map +1 -1
- package/dist/components/tools/ToolResult.d.ts.map +1 -1
- package/dist/components/tools/ToolResult.js +1 -1
- package/dist/components/tools/ToolResult.js.map +1 -1
- package/dist/hooks/useAiChat.d.ts +2 -0
- package/dist/hooks/useAiChat.d.ts.map +1 -1
- package/dist/hooks/useAiChat.js +56 -10
- package/dist/hooks/useAiChat.js.map +1 -1
- package/dist/hooks/useAssistantMessageParts.d.ts +14 -0
- package/dist/hooks/useAssistantMessageParts.d.ts.map +1 -0
- package/dist/hooks/useAssistantMessageParts.js +44 -0
- package/dist/hooks/useAssistantMessageParts.js.map +1 -0
- package/dist/hooks/useScrollToBottom.d.ts.map +1 -1
- package/dist/hooks/useScrollToBottom.js +4 -3
- package/dist/hooks/useScrollToBottom.js.map +1 -1
- package/dist/hooks/useToolGrouping.d.ts +23 -0
- package/dist/hooks/useToolGrouping.d.ts.map +1 -0
- package/dist/hooks/useToolGrouping.js +290 -0
- package/dist/hooks/useToolGrouping.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +41 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +91 -0
- package/dist/utils.js.map +1 -1
- package/package.json +8 -8
package/dist/chatTransport.js
CHANGED
|
@@ -1,8 +1,72 @@
|
|
|
1
|
-
import { DefaultChatTransport, convertToModelMessages, streamText, } from 'ai';
|
|
1
|
+
import { DefaultChatTransport, convertToModelMessages, streamText, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai';
|
|
2
2
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
3
3
|
import { convertToVercelAiToolV5 } from '@openassistant/utils';
|
|
4
4
|
import { produce } from 'immer';
|
|
5
5
|
import { getErrorMessageForDisplay } from '@sqlrooms/utils';
|
|
6
|
+
import { ToolAbortError } from './utils';
|
|
7
|
+
/**
|
|
8
|
+
* Validates and completes UIMessages to ensure all tool-call parts have corresponding tool-result parts.
|
|
9
|
+
* This is important when canceling with AbortController, which may leave incomplete tool-calls.
|
|
10
|
+
* Assumes sequential tool execution (only one tool runs at a time).
|
|
11
|
+
*
|
|
12
|
+
* @param messages - The messages to validate and complete
|
|
13
|
+
* @returns Cleaned messages with completed tool-call/result pairs
|
|
14
|
+
*/
|
|
15
|
+
export function completeIncompleteToolCalls(messages) {
|
|
16
|
+
return messages.map((message) => {
|
|
17
|
+
if (message.role !== 'assistant' || !message.parts) {
|
|
18
|
+
return message;
|
|
19
|
+
}
|
|
20
|
+
const isToolPart = (part) => {
|
|
21
|
+
if (typeof part !== 'object' || part === null)
|
|
22
|
+
return false;
|
|
23
|
+
const p = part;
|
|
24
|
+
const typeVal = typeof p.type === 'string' ? p.type : undefined;
|
|
25
|
+
return (!!typeVal &&
|
|
26
|
+
'toolCallId' in p &&
|
|
27
|
+
(typeVal === 'dynamic-tool' || typeVal.startsWith('tool-')));
|
|
28
|
+
};
|
|
29
|
+
const updatedParts = [...message.parts];
|
|
30
|
+
let sawAnyTool = false;
|
|
31
|
+
for (let i = updatedParts.length - 1; i >= 0; i--) {
|
|
32
|
+
const current = updatedParts[i];
|
|
33
|
+
if (!isToolPart(current)) {
|
|
34
|
+
// Stop once we exit the trailing tool region
|
|
35
|
+
if (sawAnyTool)
|
|
36
|
+
break;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
sawAnyTool = true;
|
|
40
|
+
const toolPart = current;
|
|
41
|
+
const hasOutput = toolPart.state?.startsWith('output');
|
|
42
|
+
if (hasOutput) {
|
|
43
|
+
// Completed tool; continue checking earlier parts just in case
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Synthesize a completed error result for the incomplete tool call
|
|
47
|
+
const base = {
|
|
48
|
+
toolCallId: toolPart.toolCallId,
|
|
49
|
+
state: 'output-error',
|
|
50
|
+
input: toolPart.input ?? {},
|
|
51
|
+
errorText: 'Operation cancelled by user',
|
|
52
|
+
providerExecuted: false,
|
|
53
|
+
};
|
|
54
|
+
const syntheticPart = toolPart.type === 'dynamic-tool'
|
|
55
|
+
? {
|
|
56
|
+
type: 'dynamic-tool',
|
|
57
|
+
toolName: toolPart.toolName || 'unknown',
|
|
58
|
+
...base,
|
|
59
|
+
}
|
|
60
|
+
: { type: toolPart.type, ...base };
|
|
61
|
+
updatedParts[i] =
|
|
62
|
+
syntheticPart;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...message,
|
|
66
|
+
parts: updatedParts,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
6
70
|
/**
|
|
7
71
|
* Creates a handler for tool completion that updates the tool additional data in the store
|
|
8
72
|
*/
|
|
@@ -19,7 +83,7 @@ function createOnToolCompletedHandler(store) {
|
|
|
19
83
|
/**
|
|
20
84
|
* Converts OpenAssistant tools to Vercel AI SDK tools with onToolCompleted handler
|
|
21
85
|
*/
|
|
22
|
-
function convertToAiSDKTools(tools, onToolCompleted) {
|
|
86
|
+
export function convertToAiSDKTools(tools, onToolCompleted) {
|
|
23
87
|
return Object.entries(tools || {}).reduce((acc, [name, tool]) => {
|
|
24
88
|
acc[name] = convertToVercelAiToolV5({
|
|
25
89
|
...tool,
|
|
@@ -48,15 +112,32 @@ export function createLocalChatTransportFactory({ store, defaultProvider, defaul
|
|
|
48
112
|
});
|
|
49
113
|
model = openai.chatModel(modelId);
|
|
50
114
|
}
|
|
115
|
+
// Parse caller-supplied body defensively to avoid breaking the stream
|
|
51
116
|
const body = init?.body;
|
|
52
|
-
|
|
53
|
-
|
|
117
|
+
let parsed = {};
|
|
118
|
+
try {
|
|
119
|
+
parsed = body ? JSON.parse(body) : {};
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
parsed = {};
|
|
123
|
+
}
|
|
124
|
+
const parsedObj = parsed || {};
|
|
125
|
+
const messagesCopy = Array.isArray(parsedObj.messages)
|
|
126
|
+
? parsedObj.messages
|
|
127
|
+
: [];
|
|
54
128
|
const onToolCompleted = createOnToolCompletedHandler(store);
|
|
55
129
|
const tools = convertToAiSDKTools(state.ai.tools || {}, onToolCompleted);
|
|
130
|
+
// Remove execute from tools for the model call so tool invocations are
|
|
131
|
+
// handled exclusively by onChatToolCall. convertToAiSDKTools is expected
|
|
132
|
+
// to return fresh tool objects; if that ever changes, clone before mutate.
|
|
133
|
+
Object.values(tools).forEach((tool) => {
|
|
134
|
+
tool.execute = undefined;
|
|
135
|
+
});
|
|
56
136
|
// get system instructions dynamically at request time to ensure fresh table schema
|
|
57
137
|
const systemInstructions = getInstructions();
|
|
58
138
|
const result = streamText({
|
|
59
139
|
model,
|
|
140
|
+
// Ensure we always pass an array of messages
|
|
60
141
|
messages: convertToModelMessages(messagesCopy),
|
|
61
142
|
tools,
|
|
62
143
|
system: systemInstructions,
|
|
@@ -75,11 +156,20 @@ export function createRemoteChatTransportFactory(params) {
|
|
|
75
156
|
const currentSession = state.ai.getCurrentSession();
|
|
76
157
|
const modelProvider = currentSession?.modelProvider || params.defaultProvider;
|
|
77
158
|
const model = currentSession?.model || params.defaultModel;
|
|
78
|
-
// Parse the existing body and add model information
|
|
159
|
+
// Parse the existing body and add model information (defensive parsing)
|
|
79
160
|
const body = init?.body;
|
|
80
|
-
|
|
161
|
+
let parsed = {};
|
|
162
|
+
try {
|
|
163
|
+
parsed = body ? JSON.parse(body) : {};
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
parsed = {};
|
|
167
|
+
}
|
|
168
|
+
const parsedObj = typeof parsed === 'object' && parsed !== null
|
|
169
|
+
? parsed
|
|
170
|
+
: {};
|
|
81
171
|
const enhancedBody = {
|
|
82
|
-
...
|
|
172
|
+
...parsedObj,
|
|
83
173
|
modelProvider,
|
|
84
174
|
model,
|
|
85
175
|
};
|
|
@@ -104,14 +194,30 @@ export function createChatHandlers({ store }) {
|
|
|
104
194
|
try {
|
|
105
195
|
// handle client tools
|
|
106
196
|
const state = store.getState();
|
|
197
|
+
// Check if the stream was aborted before executing tool
|
|
198
|
+
if (state.ai.analysisAbortController?.signal.aborted) {
|
|
199
|
+
if (addToolResult) {
|
|
200
|
+
addToolResult({
|
|
201
|
+
tool: toolName,
|
|
202
|
+
toolCallId,
|
|
203
|
+
state: 'output-error',
|
|
204
|
+
errorText: 'Operation cancelled by user',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
107
209
|
const onToolCompleted = createOnToolCompletedHandler(store);
|
|
108
210
|
const tools = convertToAiSDKTools(state.ai.tools || {}, onToolCompleted);
|
|
109
211
|
// find tool from tools using toolName
|
|
110
212
|
const tool = tools[toolName];
|
|
111
|
-
if (tool && tool.execute) {
|
|
213
|
+
if (tool && state.ai.tools[toolName]?.execute && tool.execute) {
|
|
214
|
+
// Always provide a defined messages array to the tool runtime
|
|
215
|
+
const sessionMessages = (state.ai.getCurrentSession()?.uiMessages ??
|
|
216
|
+
[]);
|
|
112
217
|
const llmResult = await tool.execute(input, {
|
|
113
218
|
toolCallId,
|
|
114
|
-
messages:
|
|
219
|
+
messages: convertToModelMessages(sessionMessages),
|
|
220
|
+
abortSignal: state.ai.analysisAbortController?.signal,
|
|
115
221
|
});
|
|
116
222
|
if (addToolResult) {
|
|
117
223
|
// Note: When using sendAutomaticallyWhen, avoid awaiting addToolResult to prevent deadlocks
|
|
@@ -122,21 +228,54 @@ export function createChatHandlers({ store }) {
|
|
|
122
228
|
});
|
|
123
229
|
}
|
|
124
230
|
}
|
|
231
|
+
else {
|
|
232
|
+
// Tool has no execute function - wait for UI component to call addToolResult
|
|
233
|
+
// Check if there's a ToolComponent for this tool
|
|
234
|
+
const hasToolComponent = !!state.ai.findToolComponent(toolName);
|
|
235
|
+
if (hasToolComponent && state.ai.waitForToolResult) {
|
|
236
|
+
try {
|
|
237
|
+
// Wait for the UI component to call addToolResult
|
|
238
|
+
await state.ai.waitForToolResult(toolCallId, state.ai.analysisAbortController?.signal);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
// If waiting was cancelled or failed, ensure we add an error result
|
|
242
|
+
if (addToolResult && error instanceof Error) {
|
|
243
|
+
addToolResult({
|
|
244
|
+
tool: toolName,
|
|
245
|
+
toolCallId,
|
|
246
|
+
state: 'output-error',
|
|
247
|
+
errorText: error.message,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// Re-throw to let the outer catch handle it
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// If no ToolComponent, we still return (no-op) - the UI won't render anything
|
|
255
|
+
// and the tool call will remain incomplete, which is fine for error handling
|
|
256
|
+
}
|
|
125
257
|
}
|
|
126
258
|
catch (error) {
|
|
259
|
+
// Check if this is an abort error
|
|
260
|
+
const isAbortError = error instanceof ToolAbortError;
|
|
127
261
|
if (addToolResult) {
|
|
128
262
|
addToolResult({
|
|
129
263
|
tool: toolName,
|
|
130
264
|
toolCallId,
|
|
131
265
|
state: 'output-error',
|
|
132
|
-
errorText:
|
|
266
|
+
errorText: isAbortError
|
|
267
|
+
? 'Operation cancelled by user'
|
|
268
|
+
: getErrorMessageForDisplay(error),
|
|
133
269
|
});
|
|
134
270
|
}
|
|
135
271
|
}
|
|
136
272
|
},
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
274
|
onChatData: (dataPart) => {
|
|
138
|
-
// Handle additional tool output data from the backend
|
|
139
|
-
if (dataPart.type === 'data-tool-additional-output'
|
|
275
|
+
// Handle additional tool output data from the backend (defensive guards)
|
|
276
|
+
if (dataPart.type === 'data-tool-additional-output' &&
|
|
277
|
+
dataPart.data &&
|
|
278
|
+
dataPart.data.toolCallId != null) {
|
|
140
279
|
const { toolCallId, output } = dataPart.data;
|
|
141
280
|
// Store the additional data in the session
|
|
142
281
|
const currentSessionId = store.getState().ai.config.currentSessionId;
|
|
@@ -152,14 +291,69 @@ export function createChatHandlers({ store }) {
|
|
|
152
291
|
const currentSessionId = store.getState().ai.config.currentSessionId;
|
|
153
292
|
if (!currentSessionId)
|
|
154
293
|
return;
|
|
155
|
-
|
|
294
|
+
// If the analysis has been aborted, force-complete and clean up immediately
|
|
295
|
+
const aborted = !!store.getState().ai.analysisAbortController?.signal.aborted;
|
|
296
|
+
if (aborted) {
|
|
297
|
+
// If messages are empty (possible when stopping immediately), fall back to existing session messages
|
|
298
|
+
const sessionMessages = store.getState().ai.getCurrentSession()
|
|
299
|
+
?.uiMessages || [];
|
|
300
|
+
const sourceMessages = messages && messages.length > 0 ? messages : sessionMessages;
|
|
301
|
+
const completedMessages = completeIncompleteToolCalls(sourceMessages);
|
|
302
|
+
store
|
|
303
|
+
.getState()
|
|
304
|
+
.ai.setSessionUiMessages(currentSessionId, completedMessages);
|
|
305
|
+
// Ensure an analysis result exists and is marked as cancelled
|
|
306
|
+
store.setState((state) => produce(state, (draft) => {
|
|
307
|
+
draft.ai.isRunningAnalysis = false;
|
|
308
|
+
draft.ai.analysisAbortController = undefined;
|
|
309
|
+
const targetSession = draft.ai.config.sessions.find((s) => s.id === currentSessionId);
|
|
310
|
+
if (!targetSession)
|
|
311
|
+
return;
|
|
312
|
+
// Find the last user message
|
|
313
|
+
const lastUserMessage = completedMessages
|
|
314
|
+
.filter((msg) => msg.role === 'user')
|
|
315
|
+
.slice(-1)[0];
|
|
316
|
+
if (!lastUserMessage)
|
|
317
|
+
return;
|
|
318
|
+
const promptText = lastUserMessage.parts
|
|
319
|
+
.filter((part) => part.type === 'text')
|
|
320
|
+
.map((part) => part.text)
|
|
321
|
+
.join('');
|
|
322
|
+
const pendingIndex = targetSession.analysisResults.findIndex((result) => result.id === '__pending__');
|
|
323
|
+
if (pendingIndex !== -1) {
|
|
324
|
+
targetSession.analysisResults[pendingIndex] = {
|
|
325
|
+
id: lastUserMessage.id,
|
|
326
|
+
prompt: promptText,
|
|
327
|
+
errorMessage: { error: 'Operation cancelled by user' },
|
|
328
|
+
isCompleted: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const existing = targetSession.analysisResults.find((r) => r.id === lastUserMessage.id);
|
|
333
|
+
if (!existing) {
|
|
334
|
+
targetSession.analysisResults.push({
|
|
335
|
+
id: lastUserMessage.id,
|
|
336
|
+
prompt: promptText,
|
|
337
|
+
errorMessage: { error: 'Operation cancelled by user' },
|
|
338
|
+
isCompleted: true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Complete any incomplete tool-calls before saving (can happen with AbortController)
|
|
346
|
+
const completedMessages = completeIncompleteToolCalls(messages);
|
|
347
|
+
store
|
|
348
|
+
.getState()
|
|
349
|
+
.ai.setSessionUiMessages(currentSessionId, completedMessages);
|
|
156
350
|
// Create or update analysis result with the user message ID for proper correlation
|
|
157
351
|
store.setState((state) => produce(state, (draft) => {
|
|
158
352
|
const targetSession = draft.ai.config.sessions.find((s) => s.id === currentSessionId);
|
|
159
353
|
if (!targetSession)
|
|
160
354
|
return;
|
|
161
355
|
// Find the last user message to get its ID and prompt
|
|
162
|
-
const lastUserMessage =
|
|
356
|
+
const lastUserMessage = completedMessages
|
|
163
357
|
.filter((msg) => msg.role === 'user')
|
|
164
358
|
.slice(-1)[0];
|
|
165
359
|
if (lastUserMessage) {
|
|
@@ -192,11 +386,38 @@ export function createChatHandlers({ store }) {
|
|
|
192
386
|
}
|
|
193
387
|
}
|
|
194
388
|
}));
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
389
|
+
// Determine if SDK wants to auto-send a follow-up turn (i.e., more steps pending)
|
|
390
|
+
const shouldAutoSendNext = lastAssistantMessageIsCompleteWithToolCalls({
|
|
391
|
+
messages: completedMessages,
|
|
392
|
+
});
|
|
393
|
+
// Step-aware completion: look only at parts after the most recent step-start
|
|
394
|
+
const lastMessage = completedMessages[completedMessages.length - 1];
|
|
395
|
+
const isLastMessageAssistant = lastMessage?.role === 'assistant';
|
|
396
|
+
let tailHasTool = false;
|
|
397
|
+
if (isLastMessageAssistant) {
|
|
398
|
+
const parts = lastMessage?.parts ?? [];
|
|
399
|
+
let lastStepStartIndex = -1;
|
|
400
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
401
|
+
if (parts[i]?.type === 'step-start') {
|
|
402
|
+
lastStepStartIndex = i;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const tailParts = parts.slice(lastStepStartIndex + 1);
|
|
407
|
+
tailHasTool = tailParts.some((part) => typeof part?.type === 'string' &&
|
|
408
|
+
(part.type.startsWith('tool-') || part.type === 'dynamic-tool'));
|
|
409
|
+
}
|
|
410
|
+
// End analysis when there is no autosend and there are no pending tool parts
|
|
411
|
+
// even if the assistant didn't emit additional text (e.g., tool-only tails).
|
|
412
|
+
const shouldEndAnalysis = (isLastMessageAssistant && !shouldAutoSendNext && !tailHasTool) ||
|
|
413
|
+
(!shouldAutoSendNext && !isLastMessageAssistant);
|
|
414
|
+
if (shouldEndAnalysis) {
|
|
415
|
+
store.setState((state) => produce(state, (draft) => {
|
|
416
|
+
draft.ai.isRunningAnalysis = false;
|
|
417
|
+
draft.ai.analysisPrompt = '';
|
|
418
|
+
draft.ai.analysisAbortController = undefined;
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
200
421
|
}
|
|
201
422
|
catch (err) {
|
|
202
423
|
console.error('onChatFinish error:', err);
|
|
@@ -215,6 +436,10 @@ export function createChatHandlers({ store }) {
|
|
|
215
436
|
return;
|
|
216
437
|
const targetSession = draft.ai.config.sessions.find((s) => s.id === currentSessionId);
|
|
217
438
|
if (targetSession) {
|
|
439
|
+
// Ensure message structure is valid even on errors
|
|
440
|
+
const existingMessages = (targetSession.uiMessages ||
|
|
441
|
+
[]);
|
|
442
|
+
targetSession.uiMessages = completeIncompleteToolCalls(existingMessages);
|
|
218
443
|
// Find the last user message to create analysis result with correct ID
|
|
219
444
|
const uiMessages = targetSession.uiMessages;
|
|
220
445
|
const lastUserMessage = uiMessages
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatTransport.js","sourceRoot":"","sources":["../src/chatTransport.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DefaultChatTransport,\n UIMessage,\n convertToModelMessages,\n streamText,\n} from 'ai';\nimport type {LanguageModel, ToolSet} from 'ai';\nimport {createOpenAICompatible} from '@ai-sdk/openai-compatible';\nimport {convertToVercelAiToolV5, OpenAssistantTool} from '@openassistant/utils';\nimport {produce} from 'immer';\nimport {getErrorMessageForDisplay} from '@sqlrooms/utils';\nimport type {AiSliceState} from './AiSlice';\nimport type {AnalysisSessionSchema} from '@sqlrooms/ai-config';\nimport {AddToolResult} from './hooks/useAiChat';\nimport type {StoreApi} from '@sqlrooms/room-store';\n\ntype ToolCall = {\n input: string;\n toolCallId: string;\n toolName: string;\n type: 'tool-input-available';\n};\n\nexport type ChatTransportConfig = {\n store: StoreApi<AiSliceState>;\n defaultProvider: string;\n defaultModel: string;\n apiKey: string;\n baseUrl?: string;\n headers?: Record<string, string>;\n getInstructions: () => string;\n /**\n * Optional: supply a pre-configured custom model.\n * e.g. import {xai} from \"@ai-sdk/xai\";\n * getCustomModel: () => xai('grok-4')\n * If provided, this model will be used instead of the default OpenAI-compatible client.\n */\n getCustomModel?: () => LanguageModel | undefined;\n};\n\n/**\n * Creates a handler for tool completion that updates the tool additional data in the store\n */\nfunction createOnToolCompletedHandler(store: StoreApi<AiSliceState>) {\n return (toolCallId: string, additionalData: unknown) => {\n const sessionId = store.getState().ai.config.currentSessionId;\n if (!sessionId) return;\n\n store\n .getState()\n .ai.setSessionToolAdditionalData(sessionId, toolCallId, additionalData);\n };\n}\n\n/**\n * Converts OpenAssistant tools to Vercel AI SDK tools with onToolCompleted handler\n */\nfunction convertToAiSDKTools(\n tools: Record<string, OpenAssistantTool>,\n onToolCompleted: (toolCallId: string, additionalData: unknown) => void,\n): ToolSet {\n return Object.entries(tools || {}).reduce(\n (acc: ToolSet, [name, tool]: [string, OpenAssistantTool]) => {\n acc[name] = convertToVercelAiToolV5({\n ...tool,\n onToolCompleted,\n });\n return acc;\n },\n {},\n );\n}\n\nexport function createLocalChatTransportFactory({\n store,\n defaultProvider,\n defaultModel,\n apiKey,\n baseUrl,\n headers,\n getInstructions,\n getCustomModel,\n}: ChatTransportConfig) {\n return () => {\n const fetchImpl = async (_input: RequestInfo | URL, init?: RequestInit) => {\n // Resolve provider/model and client at call time to pick up latest settings\n const state = store.getState();\n const currentSession = state.ai.getCurrentSession();\n const provider = currentSession?.modelProvider || defaultProvider;\n const modelId = currentSession?.model || defaultModel;\n\n // Prefer a user-supplied model if available\n let model: LanguageModel | undefined = getCustomModel?.();\n\n // Fallback to OpenAI-compatible if no custom model provided\n if (!model) {\n const openai = createOpenAICompatible({\n apiKey,\n name: provider,\n baseURL: baseUrl || 'https://api.openai.com/v1',\n headers,\n });\n model = openai.chatModel(modelId);\n }\n\n const body = init?.body as string;\n const parsed = body ? JSON.parse(body) : {};\n const messagesCopy = JSON.parse(JSON.stringify(parsed.messages || []));\n\n const onToolCompleted = createOnToolCompletedHandler(store);\n const tools = convertToAiSDKTools(state.ai.tools || {}, onToolCompleted);\n\n // get system instructions dynamically at request time to ensure fresh table schema\n const systemInstructions = getInstructions();\n\n const result = streamText({\n model,\n messages: convertToModelMessages(messagesCopy),\n tools,\n system: systemInstructions,\n abortSignal: state.ai.analysisAbortController?.signal,\n });\n\n return result.toUIMessageStreamResponse();\n };\n\n return new DefaultChatTransport({fetch: fetchImpl});\n };\n}\n\nexport function createRemoteChatTransportFactory(params: {\n store: StoreApi<AiSliceState>;\n defaultProvider: string;\n defaultModel: string;\n}) {\n return (endpoint: string, headers?: Record<string, string>) => {\n const fetchImpl = async (input: RequestInfo | URL, init?: RequestInit) => {\n // Get current session's model and provider at request time\n const state = params.store.getState();\n const currentSession = state.ai.getCurrentSession();\n const modelProvider =\n currentSession?.modelProvider || params.defaultProvider;\n const model = currentSession?.model || params.defaultModel;\n\n // Parse the existing body and add model information\n const body = init?.body as string;\n const parsed = body ? JSON.parse(body) : {};\n\n const enhancedBody = {\n ...parsed,\n modelProvider,\n model,\n };\n\n // Make the request with enhanced body\n return fetch(input, {\n ...init,\n body: JSON.stringify(enhancedBody),\n });\n };\n\n return new DefaultChatTransport({\n api: endpoint,\n credentials: 'include',\n headers,\n fetch: fetchImpl,\n });\n };\n}\n\nexport function createChatHandlers({store}: {store: StoreApi<AiSliceState>}) {\n return {\n onChatToolCall: async ({\n toolCall,\n addToolResult,\n }: {\n toolCall: ToolCall;\n addToolResult?: AddToolResult;\n }) => {\n const {input, toolCallId, toolName} = toolCall;\n try {\n // handle client tools\n const state = store.getState();\n\n const onToolCompleted = createOnToolCompletedHandler(store);\n const tools = convertToAiSDKTools(\n state.ai.tools || {},\n onToolCompleted,\n );\n\n // find tool from tools using toolName\n const tool = tools[toolName];\n if (tool && tool.execute) {\n const llmResult = await tool.execute(input, {\n toolCallId,\n messages: [],\n });\n\n if (addToolResult) {\n // Note: When using sendAutomaticallyWhen, avoid awaiting addToolResult to prevent deadlocks\n addToolResult({\n tool: toolName,\n toolCallId,\n output: llmResult,\n });\n }\n }\n } catch (error) {\n if (addToolResult) {\n addToolResult({\n tool: toolName,\n toolCallId,\n state: 'output-error',\n errorText: getErrorMessageForDisplay(error),\n });\n }\n }\n },\n onChatData: (dataPart: any) => {\n // Handle additional tool output data from the backend\n if (dataPart.type === 'data-tool-additional-output') {\n const {toolCallId, output} = dataPart.data;\n\n // Store the additional data in the session\n const currentSessionId = store.getState().ai.config.currentSessionId;\n if (currentSessionId) {\n store\n .getState()\n .ai.setSessionToolAdditionalData(\n currentSessionId,\n toolCallId,\n output,\n );\n }\n }\n },\n onChatFinish: ({messages}: {messages: UIMessage[]}) => {\n try {\n const currentSessionId = store.getState().ai.config.currentSessionId;\n if (!currentSessionId) return;\n\n store.getState().ai.setSessionUiMessages(currentSessionId, messages);\n\n // Create or update analysis result with the user message ID for proper correlation\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n const targetSession = draft.ai.config.sessions.find(\n (s: AnalysisSessionSchema) => s.id === currentSessionId,\n );\n if (!targetSession) return;\n\n // Find the last user message to get its ID and prompt\n const lastUserMessage = messages\n .filter((msg) => msg.role === 'user')\n .slice(-1)[0];\n\n if (lastUserMessage) {\n // Extract text content from user message\n const promptText = lastUserMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => (part as {text: string}).text)\n .join('');\n\n // Check if there's a pending analysis result\n const pendingIndex = targetSession.analysisResults.findIndex(\n (result: any) => result.id === '__pending__',\n );\n\n if (pendingIndex !== -1) {\n // Update the pending result with actual data\n targetSession.analysisResults[pendingIndex] = {\n id: lastUserMessage.id,\n prompt: promptText,\n isCompleted: true,\n };\n } else {\n // Check if analysis result already exists for this user message\n const existingResult = targetSession.analysisResults.find(\n (result: any) => result.id === lastUserMessage.id,\n );\n\n if (!existingResult) {\n // Create analysis result with the same ID as the user message\n targetSession.analysisResults.push({\n id: lastUserMessage.id,\n prompt: promptText,\n isCompleted: true,\n });\n }\n }\n }\n }),\n );\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n draft.ai.isRunningAnalysis = false;\n draft.ai.analysisPrompt = '';\n draft.ai.analysisAbortController = undefined;\n }),\n );\n } catch (err) {\n console.error('onChatFinish error:', err);\n throw err;\n }\n },\n onChatError: (error: unknown) => {\n try {\n let errMsg = getErrorMessageForDisplay(error);\n if (!errMsg || errMsg.trim().length === 0) {\n errMsg = 'Unknown error';\n }\n const currentSessionId = store.getState().ai.config.currentSessionId;\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n if (!currentSessionId) return;\n const targetSession = draft.ai.config.sessions.find(\n (s: AnalysisSessionSchema) => s.id === currentSessionId,\n );\n if (targetSession) {\n // Find the last user message to create analysis result with correct ID\n const uiMessages = targetSession.uiMessages as UIMessage[];\n const lastUserMessage = uiMessages\n .filter((msg) => msg.role === 'user')\n .slice(-1)[0];\n\n if (lastUserMessage) {\n // Extract text content from user message\n const promptText = lastUserMessage.parts\n .filter((part: any) => part.type === 'text')\n .map((part: any) => (part as {text: string}).text)\n .join('');\n\n // Check if there's a pending analysis result\n const pendingIndex = targetSession.analysisResults.findIndex(\n (result: any) => result.id === '__pending__',\n );\n\n if (pendingIndex !== -1) {\n // Update the pending result with error\n targetSession.analysisResults[pendingIndex] = {\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: errMsg},\n isCompleted: true,\n };\n } else {\n // Check if analysis result already exists for this user message\n const existingResult = targetSession.analysisResults.find(\n (result: any) => result.id === lastUserMessage.id,\n );\n\n if (!existingResult) {\n // Create analysis result with the same ID as the user message\n targetSession.analysisResults.push({\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: errMsg},\n isCompleted: true,\n });\n } else {\n // Update existing result with error message\n existingResult.errorMessage = {error: errMsg};\n }\n }\n }\n }\n draft.ai.isRunningAnalysis = false;\n draft.ai.analysisAbortController = undefined;\n }),\n );\n } catch (err) {\n console.error('Failed to store chat error:', err);\n throw err;\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chatTransport.js","sourceRoot":"","sources":["../src/chatTransport.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DefaultChatTransport,\n UIMessage,\n convertToModelMessages,\n streamText,\n lastAssistantMessageIsCompleteWithToolCalls,\n} from 'ai';\nimport type {DataUIPart, LanguageModel, ToolSet} from 'ai';\nimport {createOpenAICompatible} from '@ai-sdk/openai-compatible';\nimport {convertToVercelAiToolV5, OpenAssistantTool} from '@openassistant/utils';\nimport {produce} from 'immer';\nimport {getErrorMessageForDisplay} from '@sqlrooms/utils';\nimport type {AiSliceState} from './AiSlice';\nimport type {AnalysisSessionSchema} from '@sqlrooms/ai-config';\nimport {AddToolResult} from './hooks/useAiChat';\nimport type {StoreApi} from '@sqlrooms/room-store';\nimport {ToolAbortError} from './utils';\n\n/**\n * Validates and completes UIMessages to ensure all tool-call parts have corresponding tool-result parts.\n * This is important when canceling with AbortController, which may leave incomplete tool-calls.\n * Assumes sequential tool execution (only one tool runs at a time).\n *\n * @param messages - The messages to validate and complete\n * @returns Cleaned messages with completed tool-call/result pairs\n */\nexport function completeIncompleteToolCalls(\n messages: UIMessage[],\n): UIMessage[] {\n return messages.map((message) => {\n if (message.role !== 'assistant' || !message.parts) {\n return message;\n }\n\n // Walk backward and complete any TRAILING tool parts that lack output.\n // This covers multi-tool-step aborts where several tool calls were started\n // but the stream was cancelled before the outputs were emitted.\n type ToolPart = {\n type: string;\n toolCallId: string;\n toolName?: string;\n input?: unknown;\n state?: string;\n };\n const isToolPart = (part: unknown): part is ToolPart => {\n if (typeof part !== 'object' || part === null) return false;\n const p = part as Record<string, unknown> & {type?: unknown};\n const typeVal =\n typeof p.type === 'string' ? (p.type as string) : undefined;\n return (\n !!typeVal &&\n 'toolCallId' in p &&\n (typeVal === 'dynamic-tool' || typeVal.startsWith('tool-'))\n );\n };\n\n const updatedParts = [...message.parts];\n let sawAnyTool = false;\n for (let i = updatedParts.length - 1; i >= 0; i--) {\n const current = updatedParts[i] as unknown;\n if (!isToolPart(current)) {\n // Stop once we exit the trailing tool region\n if (sawAnyTool) break;\n continue;\n }\n sawAnyTool = true;\n const toolPart = current as ToolPart;\n const hasOutput = toolPart.state?.startsWith('output');\n if (hasOutput) {\n // Completed tool; continue checking earlier parts just in case\n continue;\n }\n\n // Synthesize a completed error result for the incomplete tool call\n const base = {\n toolCallId: toolPart.toolCallId,\n state: 'output-error' as const,\n input: toolPart.input ?? {},\n errorText: 'Operation cancelled by user',\n providerExecuted: false,\n };\n\n const syntheticPart =\n toolPart.type === 'dynamic-tool'\n ? {\n type: 'dynamic-tool' as const,\n toolName: toolPart.toolName || 'unknown',\n ...base,\n }\n : {type: toolPart.type as string, ...base};\n\n updatedParts[i] =\n syntheticPart as unknown as (typeof message.parts)[number];\n }\n\n return {\n ...message,\n parts: updatedParts,\n };\n });\n}\n\nexport type ToolCall = {\n input: string;\n toolCallId: string;\n toolName: string;\n type: 'tool-input-available';\n};\n\nexport type ChatTransportConfig = {\n store: StoreApi<AiSliceState>;\n defaultProvider: string;\n defaultModel: string;\n apiKey: string;\n baseUrl?: string;\n headers?: Record<string, string>;\n getInstructions: () => string;\n /**\n * Optional: supply a pre-configured custom model.\n * e.g. import {xai} from \"@ai-sdk/xai\";\n * getCustomModel: () => xai('grok-4')\n * If provided, this model will be used instead of the default OpenAI-compatible client.\n */\n getCustomModel?: () => LanguageModel | undefined;\n};\n\n/**\n * Creates a handler for tool completion that updates the tool additional data in the store\n */\nfunction createOnToolCompletedHandler(store: StoreApi<AiSliceState>) {\n return (toolCallId: string, additionalData: unknown) => {\n const sessionId = store.getState().ai.config.currentSessionId;\n if (!sessionId) return;\n\n store\n .getState()\n .ai.setSessionToolAdditionalData(sessionId, toolCallId, additionalData);\n };\n}\n\n/**\n * Converts OpenAssistant tools to Vercel AI SDK tools with onToolCompleted handler\n */\nexport function convertToAiSDKTools(\n tools: Record<string, OpenAssistantTool>,\n onToolCompleted?: (toolCallId: string, additionalData: unknown) => void,\n): ToolSet {\n return Object.entries(tools || {}).reduce(\n (acc: ToolSet, [name, tool]: [string, OpenAssistantTool]) => {\n acc[name] = convertToVercelAiToolV5({\n ...tool,\n onToolCompleted,\n });\n return acc;\n },\n {},\n );\n}\n\nexport function createLocalChatTransportFactory({\n store,\n defaultProvider,\n defaultModel,\n apiKey,\n baseUrl,\n headers,\n getInstructions,\n getCustomModel,\n}: ChatTransportConfig) {\n return () => {\n const fetchImpl = async (_input: RequestInfo | URL, init?: RequestInit) => {\n // Resolve provider/model and client at call time to pick up latest settings\n const state = store.getState();\n const currentSession = state.ai.getCurrentSession();\n const provider = currentSession?.modelProvider || defaultProvider;\n const modelId = currentSession?.model || defaultModel;\n\n // Prefer a user-supplied model if available\n let model: LanguageModel | undefined = getCustomModel?.();\n\n // Fallback to OpenAI-compatible if no custom model provided\n if (!model) {\n const openai = createOpenAICompatible({\n apiKey,\n name: provider,\n baseURL: baseUrl || 'https://api.openai.com/v1',\n headers,\n });\n model = openai.chatModel(modelId);\n }\n\n // Parse caller-supplied body defensively to avoid breaking the stream\n const body = init?.body as string;\n let parsed: unknown = {};\n try {\n parsed = body ? JSON.parse(body) : {};\n } catch {\n parsed = {};\n }\n const parsedObj = (parsed as {messages?: unknown}) || {};\n const messagesCopy = Array.isArray(parsedObj.messages)\n ? (parsedObj.messages as UIMessage[])\n : [];\n\n const onToolCompleted = createOnToolCompletedHandler(store);\n const tools = convertToAiSDKTools(state.ai.tools || {}, onToolCompleted);\n // Remove execute from tools for the model call so tool invocations are\n // handled exclusively by onChatToolCall. convertToAiSDKTools is expected\n // to return fresh tool objects; if that ever changes, clone before mutate.\n Object.values(tools).forEach((tool) => {\n tool.execute = undefined;\n });\n\n // get system instructions dynamically at request time to ensure fresh table schema\n const systemInstructions = getInstructions();\n\n const result = streamText({\n model,\n // Ensure we always pass an array of messages\n messages: convertToModelMessages(messagesCopy),\n tools,\n system: systemInstructions,\n abortSignal: state.ai.analysisAbortController?.signal,\n });\n\n return result.toUIMessageStreamResponse();\n };\n\n return new DefaultChatTransport({fetch: fetchImpl});\n };\n}\n\nexport function createRemoteChatTransportFactory(params: {\n store: StoreApi<AiSliceState>;\n defaultProvider: string;\n defaultModel: string;\n}) {\n return (endpoint: string, headers?: Record<string, string>) => {\n const fetchImpl = async (input: RequestInfo | URL, init?: RequestInit) => {\n // Get current session's model and provider at request time\n const state = params.store.getState();\n const currentSession = state.ai.getCurrentSession();\n const modelProvider =\n currentSession?.modelProvider || params.defaultProvider;\n const model = currentSession?.model || params.defaultModel;\n\n // Parse the existing body and add model information (defensive parsing)\n const body = init?.body as string;\n let parsed: unknown = {};\n try {\n parsed = body ? JSON.parse(body) : {};\n } catch {\n parsed = {};\n }\n\n const parsedObj =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n const enhancedBody = {\n ...parsedObj,\n modelProvider,\n model,\n };\n\n // Make the request with enhanced body\n return fetch(input, {\n ...init,\n body: JSON.stringify(enhancedBody),\n });\n };\n\n return new DefaultChatTransport({\n api: endpoint,\n credentials: 'include',\n headers,\n fetch: fetchImpl,\n });\n };\n}\n\nexport function createChatHandlers({store}: {store: StoreApi<AiSliceState>}) {\n return {\n onChatToolCall: async ({\n toolCall,\n addToolResult,\n }: {\n toolCall: ToolCall;\n addToolResult?: AddToolResult;\n }) => {\n const {input, toolCallId, toolName} = toolCall;\n try {\n // handle client tools\n const state = store.getState();\n\n // Check if the stream was aborted before executing tool\n if (state.ai.analysisAbortController?.signal.aborted) {\n if (addToolResult) {\n addToolResult({\n tool: toolName,\n toolCallId,\n state: 'output-error',\n errorText: 'Operation cancelled by user',\n });\n }\n return;\n }\n\n const onToolCompleted = createOnToolCompletedHandler(store);\n const tools = convertToAiSDKTools(\n state.ai.tools || {},\n onToolCompleted,\n );\n\n // find tool from tools using toolName\n const tool = tools[toolName];\n if (tool && state.ai.tools[toolName]?.execute && tool.execute) {\n // Always provide a defined messages array to the tool runtime\n const sessionMessages = (state.ai.getCurrentSession()?.uiMessages ??\n []) as UIMessage[];\n const llmResult = await tool.execute(input, {\n toolCallId,\n messages: convertToModelMessages(sessionMessages),\n abortSignal: state.ai.analysisAbortController?.signal,\n });\n\n if (addToolResult) {\n // Note: When using sendAutomaticallyWhen, avoid awaiting addToolResult to prevent deadlocks\n addToolResult({\n tool: toolName,\n toolCallId,\n output: llmResult,\n });\n }\n } else {\n // Tool has no execute function - wait for UI component to call addToolResult\n // Check if there's a ToolComponent for this tool\n const hasToolComponent = !!state.ai.findToolComponent(toolName);\n if (hasToolComponent && state.ai.waitForToolResult) {\n try {\n // Wait for the UI component to call addToolResult\n await state.ai.waitForToolResult(\n toolCallId,\n state.ai.analysisAbortController?.signal,\n );\n } catch (error) {\n // If waiting was cancelled or failed, ensure we add an error result\n if (addToolResult && error instanceof Error) {\n addToolResult({\n tool: toolName,\n toolCallId,\n state: 'output-error',\n errorText: error.message,\n });\n }\n // Re-throw to let the outer catch handle it\n throw error;\n }\n }\n // If no ToolComponent, we still return (no-op) - the UI won't render anything\n // and the tool call will remain incomplete, which is fine for error handling\n }\n } catch (error) {\n // Check if this is an abort error\n const isAbortError = error instanceof ToolAbortError;\n\n if (addToolResult) {\n addToolResult({\n tool: toolName,\n toolCallId,\n state: 'output-error',\n errorText: isAbortError\n ? 'Operation cancelled by user'\n : getErrorMessageForDisplay(error),\n });\n }\n }\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onChatData: (dataPart: DataUIPart<any>) => {\n // Handle additional tool output data from the backend (defensive guards)\n if (\n dataPart.type === 'data-tool-additional-output' &&\n dataPart.data &&\n (dataPart.data as {toolCallId?: unknown}).toolCallId != null\n ) {\n const {toolCallId, output} = dataPart.data as {\n toolCallId: string;\n output: unknown;\n };\n\n // Store the additional data in the session\n const currentSessionId = store.getState().ai.config.currentSessionId;\n if (currentSessionId) {\n store\n .getState()\n .ai.setSessionToolAdditionalData(\n currentSessionId,\n toolCallId,\n output,\n );\n }\n }\n },\n onChatFinish: ({messages}: {messages: UIMessage[]}) => {\n try {\n const currentSessionId = store.getState().ai.config.currentSessionId;\n if (!currentSessionId) return;\n\n // If the analysis has been aborted, force-complete and clean up immediately\n const aborted =\n !!store.getState().ai.analysisAbortController?.signal.aborted;\n if (aborted) {\n // If messages are empty (possible when stopping immediately), fall back to existing session messages\n const sessionMessages =\n (store.getState().ai.getCurrentSession()\n ?.uiMessages as UIMessage[]) || [];\n const sourceMessages =\n messages && messages.length > 0 ? messages : sessionMessages;\n\n const completedMessages = completeIncompleteToolCalls(sourceMessages);\n store\n .getState()\n .ai.setSessionUiMessages(currentSessionId, completedMessages);\n\n // Ensure an analysis result exists and is marked as cancelled\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n draft.ai.isRunningAnalysis = false;\n draft.ai.analysisAbortController = undefined;\n\n const targetSession = draft.ai.config.sessions.find(\n (s: AnalysisSessionSchema) => s.id === currentSessionId,\n );\n if (!targetSession) return;\n\n // Find the last user message\n const lastUserMessage = completedMessages\n .filter((msg) => msg.role === 'user')\n .slice(-1)[0];\n if (!lastUserMessage) return;\n\n const promptText = lastUserMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => (part as {text: string}).text)\n .join('');\n\n const pendingIndex = targetSession.analysisResults.findIndex(\n (result) => result.id === '__pending__',\n );\n\n if (pendingIndex !== -1) {\n targetSession.analysisResults[pendingIndex] = {\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: 'Operation cancelled by user'},\n isCompleted: true,\n };\n } else {\n const existing = targetSession.analysisResults.find(\n (r) => r.id === lastUserMessage.id,\n );\n if (!existing) {\n targetSession.analysisResults.push({\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: 'Operation cancelled by user'},\n isCompleted: true,\n });\n }\n }\n }),\n );\n return;\n }\n\n // Complete any incomplete tool-calls before saving (can happen with AbortController)\n const completedMessages = completeIncompleteToolCalls(messages);\n\n store\n .getState()\n .ai.setSessionUiMessages(currentSessionId, completedMessages);\n\n // Create or update analysis result with the user message ID for proper correlation\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n const targetSession = draft.ai.config.sessions.find(\n (s: AnalysisSessionSchema) => s.id === currentSessionId,\n );\n if (!targetSession) return;\n\n // Find the last user message to get its ID and prompt\n const lastUserMessage = completedMessages\n .filter((msg) => msg.role === 'user')\n .slice(-1)[0];\n\n if (lastUserMessage) {\n // Extract text content from user message\n const promptText = lastUserMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => (part as {text: string}).text)\n .join('');\n\n // Check if there's a pending analysis result\n const pendingIndex = targetSession.analysisResults.findIndex(\n (result) => result.id === '__pending__',\n );\n\n if (pendingIndex !== -1) {\n // Update the pending result with actual data\n targetSession.analysisResults[pendingIndex] = {\n id: lastUserMessage.id,\n prompt: promptText,\n isCompleted: true,\n };\n } else {\n // Check if analysis result already exists for this user message\n const existingResult = targetSession.analysisResults.find(\n (result) => result.id === lastUserMessage.id,\n );\n\n if (!existingResult) {\n // Create analysis result with the same ID as the user message\n targetSession.analysisResults.push({\n id: lastUserMessage.id,\n prompt: promptText,\n isCompleted: true,\n });\n }\n }\n }\n }),\n );\n\n // Determine if SDK wants to auto-send a follow-up turn (i.e., more steps pending)\n const shouldAutoSendNext = lastAssistantMessageIsCompleteWithToolCalls({\n messages: completedMessages,\n });\n\n // Step-aware completion: look only at parts after the most recent step-start\n const lastMessage = completedMessages[completedMessages.length - 1];\n const isLastMessageAssistant = lastMessage?.role === 'assistant';\n let tailHasTool = false;\n if (isLastMessageAssistant) {\n const parts = lastMessage?.parts ?? [];\n let lastStepStartIndex = -1;\n for (let i = parts.length - 1; i >= 0; i--) {\n if (parts[i]?.type === 'step-start') {\n lastStepStartIndex = i;\n break;\n }\n }\n const tailParts = parts.slice(lastStepStartIndex + 1);\n tailHasTool = tailParts.some(\n (part) =>\n typeof part?.type === 'string' &&\n (part.type.startsWith('tool-') || part.type === 'dynamic-tool'),\n );\n }\n\n // End analysis when there is no autosend and there are no pending tool parts\n // even if the assistant didn't emit additional text (e.g., tool-only tails).\n const shouldEndAnalysis =\n (isLastMessageAssistant && !shouldAutoSendNext && !tailHasTool) ||\n (!shouldAutoSendNext && !isLastMessageAssistant);\n\n if (shouldEndAnalysis) {\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n draft.ai.isRunningAnalysis = false;\n draft.ai.analysisPrompt = '';\n draft.ai.analysisAbortController = undefined;\n }),\n );\n }\n } catch (err) {\n console.error('onChatFinish error:', err);\n throw err;\n }\n },\n onChatError: (error: unknown) => {\n try {\n let errMsg = getErrorMessageForDisplay(error);\n if (!errMsg || errMsg.trim().length === 0) {\n errMsg = 'Unknown error';\n }\n const currentSessionId = store.getState().ai.config.currentSessionId;\n store.setState((state: AiSliceState) =>\n produce(state, (draft: AiSliceState) => {\n if (!currentSessionId) return;\n const targetSession = draft.ai.config.sessions.find(\n (s: AnalysisSessionSchema) => s.id === currentSessionId,\n );\n if (targetSession) {\n // Ensure message structure is valid even on errors\n const existingMessages = (targetSession.uiMessages ||\n []) as UIMessage[];\n targetSession.uiMessages = completeIncompleteToolCalls(\n existingMessages,\n ) as unknown as AnalysisSessionSchema['uiMessages'];\n\n // Find the last user message to create analysis result with correct ID\n const uiMessages = targetSession.uiMessages as UIMessage[];\n const lastUserMessage = uiMessages\n .filter((msg) => msg.role === 'user')\n .slice(-1)[0];\n\n if (lastUserMessage) {\n // Extract text content from user message\n const promptText = lastUserMessage.parts\n .filter((part) => part.type === 'text')\n .map((part) => (part as {text: string}).text)\n .join('');\n\n // Check if there's a pending analysis result\n const pendingIndex = targetSession.analysisResults.findIndex(\n (result) => result.id === '__pending__',\n );\n\n if (pendingIndex !== -1) {\n // Update the pending result with error\n targetSession.analysisResults[pendingIndex] = {\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: errMsg},\n isCompleted: true,\n };\n } else {\n // Check if analysis result already exists for this user message\n const existingResult = targetSession.analysisResults.find(\n (result) => result.id === lastUserMessage.id,\n );\n\n if (!existingResult) {\n // Create analysis result with the same ID as the user message\n targetSession.analysisResults.push({\n id: lastUserMessage.id,\n prompt: promptText,\n errorMessage: {error: errMsg},\n isCompleted: true,\n });\n } else {\n // Update existing result with error message\n existingResult.errorMessage = {error: errMsg};\n }\n }\n }\n }\n draft.ai.isRunningAnalysis = false;\n draft.ai.analysisAbortController = undefined;\n }),\n );\n } catch (err) {\n console.error('Failed to store chat error:', err);\n throw err;\n }\n },\n };\n}\n"]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Components } from 'react-markdown';
|
|
2
3
|
type AnalysisAnswerProps = {
|
|
3
4
|
content: string;
|
|
4
5
|
isAnswer: boolean;
|
|
6
|
+
customMarkdownComponents?: Partial<Components>;
|
|
5
7
|
};
|
|
6
8
|
/**
|
|
7
9
|
* Renders an analysis answer with markdown content of the final streaming response.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalysisAnswer.d.ts","sourceRoot":"","sources":["../../src/components/AnalysisAnswer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AnalysisAnswer.d.ts","sourceRoot":"","sources":["../../src/components/AnalysisAnswer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAiB,EAAC,UAAU,EAAC,MAAM,gBAAgB,CAAC;AAQpD,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,wBAAwB,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAChD,CAAC;AA6GF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,iDA4EzB,CAAC"}
|
|
@@ -62,6 +62,7 @@ ThinkBlock.displayName = 'ThinkBlock';
|
|
|
62
62
|
* @returns {JSX.Element} The rendered answer tool call
|
|
63
63
|
*/
|
|
64
64
|
export const AnalysisAnswer = React.memo(function AnalysisAnswer(props) {
|
|
65
|
+
const { content, isAnswer, customMarkdownComponents } = props;
|
|
65
66
|
const [expandedThink, setExpandedThink] = useState(new Set());
|
|
66
67
|
const toggleThinkExpansion = useCallback((content) => {
|
|
67
68
|
setExpandedThink((prev) => {
|
|
@@ -76,7 +77,7 @@ export const AnalysisAnswer = React.memo(function AnalysisAnswer(props) {
|
|
|
76
77
|
});
|
|
77
78
|
}, []);
|
|
78
79
|
// Memoize content processing to avoid recalculation on every render
|
|
79
|
-
const { processedContent, thinkContents } = useMemo(() => processContent(
|
|
80
|
+
const { processedContent, thinkContents } = useMemo(() => processContent(content), [content]);
|
|
80
81
|
// Memoize the think-block component to prevent unnecessary re-renders
|
|
81
82
|
const thinkBlockComponent = useCallback((thinkBlock) => {
|
|
82
83
|
try {
|
|
@@ -94,9 +95,10 @@ export const AnalysisAnswer = React.memo(function AnalysisAnswer(props) {
|
|
|
94
95
|
return null;
|
|
95
96
|
}
|
|
96
97
|
}, [thinkContents, expandedThink, toggleThinkExpansion]);
|
|
97
|
-
return (_jsx("div", { className: "flex flex-col gap-5", children: _jsx(MessageContainer, { isSuccess: true, type:
|
|
98
|
+
return (_jsx("div", { className: "flex flex-col gap-5", children: _jsx(MessageContainer, { isSuccess: true, type: isAnswer ? 'answer' : 'thinking', content: { content, isAnswer }, children: _jsx("div", { className: "prose dark:prose-invert max-w-none text-sm", children: _jsx(Markdown, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeRaw], components: {
|
|
98
99
|
// @ts-expect-error - Custom HTML element not in react-markdown types
|
|
99
100
|
'think-block': thinkBlockComponent,
|
|
101
|
+
...customMarkdownComponents,
|
|
100
102
|
}, children: processedContent }) }) }) }));
|
|
101
103
|
});
|
|
102
104
|
//# sourceMappingURL=AnalysisAnswer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalysisAnswer.js","sourceRoot":"","sources":["../../src/components/AnalysisAnswer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useState, useCallback, useMemo} from 'react';\nimport Markdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport rehypeRaw from 'rehype-raw';\nimport {truncate} from '@sqlrooms/utils';\nimport {MessageContainer} from './MessageContainer';\nimport {BrainIcon} from 'lucide-react';\nimport {cn} from '@sqlrooms/ui';\n\ntype AnalysisAnswerProps = {\n content: string;\n isAnswer: boolean;\n};\n\ntype ThinkContent = {\n content: string;\n isComplete: boolean;\n index: number;\n};\n\n// Constants moved outside component to prevent recreation\nconst THINK_WORD_LIMIT = 10;\nconst COMPLETE_THINK_REGEX = /<think>([\\s\\S]*?)<\\/think>/g;\nconst INCOMPLETE_THINK_REGEX = /<think>([\\s\\S]*)$/;\n\n/**\n * Processes content and extracts think content in one pass\n */\nconst processContent = (\n originalContent: string,\n): {\n processedContent: string;\n thinkContents: ThinkContent[];\n} => {\n const thinkContents: ThinkContent[] = [];\n let processedContent = originalContent;\n let index = 0;\n\n // Replace complete think tags\n processedContent = processedContent.replace(\n COMPLETE_THINK_REGEX,\n (match, content) => {\n if (content) {\n thinkContents.push({\n content: content.trim(),\n isComplete: true,\n index: index++,\n });\n return `\\n\\n<think-block data-index=\"${index - 1}\"></think-block>\\n\\n`;\n }\n return match;\n },\n );\n\n // Replace incomplete think tags (no closing tag)\n processedContent = processedContent.replace(\n INCOMPLETE_THINK_REGEX,\n (match, content) => {\n if (content) {\n thinkContents.push({\n content: content.trim(),\n isComplete: false,\n index: index++,\n });\n return `\\n\\n<think-block data-index=\"${index - 1}\"></think-block>\\n\\n`;\n }\n return match;\n },\n );\n\n return {processedContent, thinkContents};\n};\n\n/**\n * ThinkBlock component for rendering individual think blocks\n */\nconst ThinkBlock = React.memo<{\n className?: string;\n thinkContent: ThinkContent;\n isExpanded: boolean;\n onToggleExpansion: (content: string) => void;\n}>(({thinkContent, isExpanded, onToggleExpansion, className}) => {\n const {content, isComplete, index} = thinkContent;\n\n const displayText =\n isComplete && !isExpanded ? truncate(content, THINK_WORD_LIMIT) : content;\n const needsTruncation =\n isComplete && content.split(' ').length > THINK_WORD_LIMIT;\n\n return (\n <span\n key={`think-${index}`}\n className={cn(\n 'inline-block rounded-lg px-3 py-2 text-xs font-normal text-gray-100 dark:text-gray-400',\n isExpanded && 'bg-gray-50 dark:bg-gray-800/50',\n className,\n )}\n >\n <span className=\"inline-flex items-start gap-2\">\n <span className=\"text-gray-400\">\n <BrainIcon\n className=\"mb-1 inline-block opacity-60 grayscale\"\n size={12}\n />{' '}\n {displayText}\n </span>\n </span>{' '}\n {needsTruncation && (\n <button\n onClick={() => onToggleExpansion(content)}\n className=\"text-xs text-gray-500 underline hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300\"\n >\n {isExpanded ? 'Show less' : 'Show more thinking'}\n </button>\n )}\n </span>\n );\n});\n\nThinkBlock.displayName = 'ThinkBlock';\n\n/**\n * Renders an analysis answer with markdown content of the final streaming response.\n * Supports streaming think content that may arrive in chunks (e.g., \"<think>Hello\" before \"</think>\").\n *\n * @param {AnalysisAnswerProps} props - The component props. See {@link AnalysisAnswerProps} for more details.\n * @returns {JSX.Element} The rendered answer tool call\n */\nexport const AnalysisAnswer = React.memo(function AnalysisAnswer(\n props: AnalysisAnswerProps,\n) {\n const [expandedThink, setExpandedThink] = useState<Set<string>>(new Set());\n\n const toggleThinkExpansion = useCallback((content: string) => {\n setExpandedThink((prev) => {\n const newExpanded = new Set(prev);\n if (newExpanded.has(content)) {\n newExpanded.delete(content);\n } else {\n newExpanded.add(content);\n }\n return newExpanded;\n });\n }, []);\n\n // Memoize content processing to avoid recalculation on every render\n const {processedContent, thinkContents} = useMemo(\n () => processContent(props.content),\n [props.content],\n );\n\n // Memoize the think-block component to prevent unnecessary re-renders\n const thinkBlockComponent = useCallback(\n (thinkBlock: any) => {\n try {\n const index = parseInt(thinkBlock.props?.['data-index'] || '0', 10);\n const thinkContent = thinkContents[index];\n\n if (!thinkContent) {\n console.warn(`Think content not found for index: ${index}`);\n return null;\n }\n\n const isExpanded = expandedThink.has(thinkContent.content);\n\n return (\n <ThinkBlock\n thinkContent={thinkContent}\n isExpanded={isExpanded}\n onToggleExpansion={toggleThinkExpansion}\n />\n );\n } catch (error) {\n console.error('Error rendering think block:', error);\n return null;\n }\n },\n [thinkContents, expandedThink, toggleThinkExpansion],\n );\n\n return (\n <div className=\"flex flex-col gap-5\">\n <MessageContainer\n isSuccess={true}\n type={props.isAnswer ? 'answer' : 'thinking'}\n content={props}\n >\n <div className=\"prose dark:prose-invert max-w-none text-sm\">\n <Markdown\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n components={{\n // @ts-expect-error - Custom HTML element not in react-markdown types\n 'think-block': thinkBlockComponent,\n }}\n >\n {processedContent}\n </Markdown>\n </div>\n </MessageContainer>\n </div>\n );\n});\n"]}
|
|
1
|
+
{"version":3,"file":"AnalysisAnswer.js","sourceRoot":"","sources":["../../src/components/AnalysisAnswer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useState, useCallback, useMemo} from 'react';\nimport Markdown, {Components} from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport rehypeRaw from 'rehype-raw';\nimport {truncate} from '@sqlrooms/utils';\nimport {MessageContainer} from './MessageContainer';\nimport {BrainIcon} from 'lucide-react';\nimport {cn} from '@sqlrooms/ui';\n\ntype AnalysisAnswerProps = {\n content: string;\n isAnswer: boolean;\n customMarkdownComponents?: Partial<Components>;\n};\n\ntype ThinkContent = {\n content: string;\n isComplete: boolean;\n index: number;\n};\n\n// Constants moved outside component to prevent recreation\nconst THINK_WORD_LIMIT = 10;\nconst COMPLETE_THINK_REGEX = /<think>([\\s\\S]*?)<\\/think>/g;\nconst INCOMPLETE_THINK_REGEX = /<think>([\\s\\S]*)$/;\n\n/**\n * Processes content and extracts think content in one pass\n */\nconst processContent = (\n originalContent: string,\n): {\n processedContent: string;\n thinkContents: ThinkContent[];\n} => {\n const thinkContents: ThinkContent[] = [];\n let processedContent = originalContent;\n let index = 0;\n\n // Replace complete think tags\n processedContent = processedContent.replace(\n COMPLETE_THINK_REGEX,\n (match, content) => {\n if (content) {\n thinkContents.push({\n content: content.trim(),\n isComplete: true,\n index: index++,\n });\n return `\\n\\n<think-block data-index=\"${index - 1}\"></think-block>\\n\\n`;\n }\n return match;\n },\n );\n\n // Replace incomplete think tags (no closing tag)\n processedContent = processedContent.replace(\n INCOMPLETE_THINK_REGEX,\n (match, content) => {\n if (content) {\n thinkContents.push({\n content: content.trim(),\n isComplete: false,\n index: index++,\n });\n return `\\n\\n<think-block data-index=\"${index - 1}\"></think-block>\\n\\n`;\n }\n return match;\n },\n );\n\n return {processedContent, thinkContents};\n};\n\n/**\n * ThinkBlock component for rendering individual think blocks\n */\nconst ThinkBlock = React.memo<{\n className?: string;\n thinkContent: ThinkContent;\n isExpanded: boolean;\n onToggleExpansion: (content: string) => void;\n}>(({thinkContent, isExpanded, onToggleExpansion, className}) => {\n const {content, isComplete, index} = thinkContent;\n\n const displayText =\n isComplete && !isExpanded ? truncate(content, THINK_WORD_LIMIT) : content;\n const needsTruncation =\n isComplete && content.split(' ').length > THINK_WORD_LIMIT;\n\n return (\n <span\n key={`think-${index}`}\n className={cn(\n 'inline-block rounded-lg px-3 py-2 text-xs font-normal text-gray-100 dark:text-gray-400',\n isExpanded && 'bg-gray-50 dark:bg-gray-800/50',\n className,\n )}\n >\n <span className=\"inline-flex items-start gap-2\">\n <span className=\"text-gray-400\">\n <BrainIcon\n className=\"mb-1 inline-block opacity-60 grayscale\"\n size={12}\n />{' '}\n {displayText}\n </span>\n </span>{' '}\n {needsTruncation && (\n <button\n onClick={() => onToggleExpansion(content)}\n className=\"text-xs text-gray-500 underline hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300\"\n >\n {isExpanded ? 'Show less' : 'Show more thinking'}\n </button>\n )}\n </span>\n );\n});\n\nThinkBlock.displayName = 'ThinkBlock';\n\n/**\n * Renders an analysis answer with markdown content of the final streaming response.\n * Supports streaming think content that may arrive in chunks (e.g., \"<think>Hello\" before \"</think>\").\n *\n * @param {AnalysisAnswerProps} props - The component props. See {@link AnalysisAnswerProps} for more details.\n * @returns {JSX.Element} The rendered answer tool call\n */\nexport const AnalysisAnswer = React.memo(function AnalysisAnswer(\n props: AnalysisAnswerProps,\n) {\n const {content, isAnswer, customMarkdownComponents} = props;\n const [expandedThink, setExpandedThink] = useState<Set<string>>(new Set());\n\n const toggleThinkExpansion = useCallback((content: string) => {\n setExpandedThink((prev) => {\n const newExpanded = new Set(prev);\n if (newExpanded.has(content)) {\n newExpanded.delete(content);\n } else {\n newExpanded.add(content);\n }\n return newExpanded;\n });\n }, []);\n\n // Memoize content processing to avoid recalculation on every render\n const {processedContent, thinkContents} = useMemo(\n () => processContent(content),\n [content],\n );\n\n // Memoize the think-block component to prevent unnecessary re-renders\n const thinkBlockComponent = useCallback(\n (thinkBlock: any) => {\n try {\n const index = parseInt(thinkBlock.props?.['data-index'] || '0', 10);\n const thinkContent = thinkContents[index];\n\n if (!thinkContent) {\n console.warn(`Think content not found for index: ${index}`);\n return null;\n }\n\n const isExpanded = expandedThink.has(thinkContent.content);\n\n return (\n <ThinkBlock\n thinkContent={thinkContent}\n isExpanded={isExpanded}\n onToggleExpansion={toggleThinkExpansion}\n />\n );\n } catch (error) {\n console.error('Error rendering think block:', error);\n return null;\n }\n },\n [thinkContents, expandedThink, toggleThinkExpansion],\n );\n\n return (\n <div className=\"flex flex-col gap-5\">\n <MessageContainer\n isSuccess={true}\n type={isAnswer ? 'answer' : 'thinking'}\n content={{content, isAnswer}}\n >\n <div className=\"prose dark:prose-invert max-w-none text-sm\">\n <Markdown\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n components={{\n // @ts-expect-error - Custom HTML element not in react-markdown types\n 'think-block': thinkBlockComponent,\n ...customMarkdownComponents,\n }}\n >\n {processedContent}\n </Markdown>\n </div>\n </MessageContainer>\n </div>\n );\n});\n"]}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { AnalysisResultSchema } from '@sqlrooms/ai-config';
|
|
2
|
+
import { Components } from 'react-markdown';
|
|
2
3
|
/**
|
|
3
4
|
* Props for the AnalysisResult component
|
|
4
5
|
* @property {AnalysisResultSchema} result - The result of the analysis containing prompt, tool calls, and analysis data
|
|
6
|
+
* @property {boolean} enableReasoningBox - Whether to group consecutive tool parts into a collapsible ReasoningBox
|
|
7
|
+
* @property {Partial<Components>} customMarkdownComponents - Optional custom components for markdown rendering
|
|
8
|
+
* @property {string[]} userTools - Array of tool names that should not be grouped and must be rendered separately
|
|
5
9
|
*/
|
|
6
10
|
type AnalysisResultProps = {
|
|
7
11
|
analysisResult: AnalysisResultSchema;
|
|
12
|
+
enableReasoningBox?: boolean;
|
|
13
|
+
customMarkdownComponents?: Partial<Components>;
|
|
14
|
+
userTools?: string[];
|
|
8
15
|
};
|
|
9
16
|
/**
|
|
10
17
|
* Component that displays the results of an AI analysis.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalysisResult.d.ts","sourceRoot":"","sources":["../../src/components/AnalysisResult.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"AnalysisResult.d.ts","sourceRoot":"","sources":["../../src/components/AnalysisResult.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAIzD,OAAO,EAAC,UAAU,EAAC,MAAM,gBAAgB,CAAC;AAU1C;;;;;;GAMG;AACH,KAAK,mBAAmB,GAAG;IACzB,cAAc,EAAE,oBAAoB,CAAC;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wBAAwB,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA2HxD,CAAC"}
|