@sqlrooms/ai-core 0.26.0-rc.2 → 0.26.0-rc.4
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 +238 -348
- package/dist/AiSlice.d.ts +37 -7
- package/dist/AiSlice.d.ts.map +1 -1
- package/dist/AiSlice.js +203 -147
- package/dist/AiSlice.js.map +1 -1
- package/dist/chatTransport.d.ts +48 -0
- package/dist/chatTransport.d.ts.map +1 -0
- package/dist/chatTransport.js +270 -0
- package/dist/chatTransport.js.map +1 -0
- package/dist/components/AiThinkingDots.d.ts +10 -0
- package/dist/components/AiThinkingDots.d.ts.map +1 -0
- package/dist/components/AiThinkingDots.js +18 -0
- package/dist/components/AiThinkingDots.js.map +1 -0
- package/dist/components/AnalysisResult.d.ts +1 -2
- package/dist/components/AnalysisResult.d.ts.map +1 -1
- package/dist/components/AnalysisResult.js +48 -18
- package/dist/components/AnalysisResult.js.map +1 -1
- package/dist/components/AnalysisResultsContainer.d.ts.map +1 -1
- package/dist/components/AnalysisResultsContainer.js +5 -10
- package/dist/components/AnalysisResultsContainer.js.map +1 -1
- package/dist/components/QueryControls.d.ts.map +1 -1
- package/dist/components/QueryControls.js +6 -3
- package/dist/components/QueryControls.js.map +1 -1
- package/dist/components/ToolCallInfo.d.ts +25 -0
- package/dist/components/ToolCallInfo.d.ts.map +1 -0
- package/dist/components/ToolCallInfo.js +32 -0
- package/dist/components/ToolCallInfo.js.map +1 -0
- package/dist/components/tools/ToolResult.d.ts +12 -7
- package/dist/components/tools/ToolResult.d.ts.map +1 -1
- package/dist/components/tools/ToolResult.js +8 -6
- package/dist/components/tools/ToolResult.js.map +1 -1
- package/dist/hooks/useAiChat.d.ts +60 -0
- package/dist/hooks/useAiChat.d.ts.map +1 -0
- package/dist/hooks/useAiChat.js +92 -0
- package/dist/hooks/useAiChat.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +23 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -1
- package/package.json +15 -14
- package/dist/analysis.d.ts +0 -51
- package/dist/analysis.d.ts.map +0 -1
- package/dist/analysis.js +0 -43
- package/dist/analysis.js.map +0 -1
package/dist/AiSlice.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AiSliceConfig, AnalysisSessionSchema } from '@sqlrooms/ai-config';
|
|
1
|
+
import { AiSliceConfig, AnalysisResultSchema, AnalysisSessionSchema } from '@sqlrooms/ai-config';
|
|
3
2
|
import { BaseRoomConfig, RoomState, type StateCreator } from '@sqlrooms/room-store';
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { UIMessage, DefaultChatTransport, LanguageModel } from 'ai';
|
|
4
|
+
import { OpenAssistantToolSet } from '@openassistant/utils';
|
|
5
|
+
type ExtendedChatOnToolCallCallback = (args: {
|
|
6
|
+
toolCall: any;
|
|
7
|
+
addToolResult?: any;
|
|
8
|
+
}) => Promise<any> | any;
|
|
6
9
|
export type AiSliceState = {
|
|
7
10
|
ai: {
|
|
8
11
|
config: AiSliceConfig;
|
|
9
12
|
analysisPrompt: string;
|
|
10
13
|
isRunningAnalysis: boolean;
|
|
11
|
-
tools:
|
|
14
|
+
tools: OpenAssistantToolSet;
|
|
12
15
|
analysisAbortController?: AbortController;
|
|
13
16
|
setAnalysisPrompt: (prompt: string) => void;
|
|
14
|
-
|
|
17
|
+
addAnalysisResult: (message: UIMessage) => void;
|
|
18
|
+
startAnalysis: (sendMessage: (message: {
|
|
19
|
+
text: string;
|
|
20
|
+
}) => void) => Promise<void>;
|
|
15
21
|
cancelAnalysis: () => void;
|
|
16
22
|
setAiModel: (modelProvider: string, model: string) => void;
|
|
17
23
|
createSession: (name?: string, modelProvider?: string, model?: string) => void;
|
|
@@ -19,12 +25,29 @@ export type AiSliceState = {
|
|
|
19
25
|
renameSession: (sessionId: string, name: string) => void;
|
|
20
26
|
deleteSession: (sessionId: string) => void;
|
|
21
27
|
getCurrentSession: () => AnalysisSessionSchema | undefined;
|
|
28
|
+
setSessionUiMessages: (sessionId: string, uiMessages: UIMessage[]) => void;
|
|
29
|
+
setSessionToolAdditionalData: (sessionId: string, toolCallId: string, additionalData: unknown) => void;
|
|
30
|
+
getAnalysisResults: () => AnalysisResultSchema[];
|
|
22
31
|
deleteAnalysisResult: (sessionId: string, resultId: string) => void;
|
|
32
|
+
getAssistantMessageParts: (analysisResultId: string) => UIMessage['parts'];
|
|
23
33
|
findToolComponent: (toolName: string) => React.ComponentType | undefined;
|
|
24
34
|
getApiKeyFromSettings: () => string;
|
|
25
35
|
getBaseUrlFromSettings: () => string | undefined;
|
|
26
36
|
getMaxStepsFromSettings: () => number;
|
|
27
37
|
getFullInstructions: () => string;
|
|
38
|
+
getLocalChatTransport: () => DefaultChatTransport<UIMessage>;
|
|
39
|
+
/** Optional remote endpoint to use for chat; if empty, local transport is used */
|
|
40
|
+
chatEndPoint: string;
|
|
41
|
+
chatHeaders: Record<string, string>;
|
|
42
|
+
getRemoteChatTransport: (endpoint: string, headers?: Record<string, string>) => DefaultChatTransport<UIMessage>;
|
|
43
|
+
onChatToolCall: ExtendedChatOnToolCallCallback;
|
|
44
|
+
onChatData: (dataPart: any) => void;
|
|
45
|
+
onChatFinish: (args: {
|
|
46
|
+
message: UIMessage;
|
|
47
|
+
messages: UIMessage[];
|
|
48
|
+
isError?: boolean;
|
|
49
|
+
}) => void;
|
|
50
|
+
onChatError: (error: unknown) => void;
|
|
28
51
|
};
|
|
29
52
|
};
|
|
30
53
|
/**
|
|
@@ -35,7 +58,7 @@ export interface AiSliceOptions {
|
|
|
35
58
|
/** Initial prompt to display in the analysis input */
|
|
36
59
|
initialAnalysisPrompt?: string;
|
|
37
60
|
/** Tools to add to the AI assistant */
|
|
38
|
-
tools:
|
|
61
|
+
tools: OpenAssistantToolSet;
|
|
39
62
|
/**
|
|
40
63
|
* Function to get custom instructions for the AI assistant
|
|
41
64
|
* @returns The instructions string to use
|
|
@@ -43,10 +66,17 @@ export interface AiSliceOptions {
|
|
|
43
66
|
getInstructions: () => string;
|
|
44
67
|
defaultProvider?: string;
|
|
45
68
|
defaultModel?: string;
|
|
69
|
+
/** Provide a pre-configured model client for a provider (e.g., Azure). */
|
|
70
|
+
getCustomModel?: () => LanguageModel | undefined;
|
|
46
71
|
maxSteps?: number;
|
|
47
72
|
getApiKey?: (modelProvider: string) => string;
|
|
48
73
|
getBaseUrl?: () => string;
|
|
74
|
+
/** Optional remote endpoint to use for chat; if empty, local transport is used */
|
|
75
|
+
chatEndPoint?: string;
|
|
76
|
+
/** Optional headers to send with remote endpoint */
|
|
77
|
+
chatHeaders?: Record<string, string>;
|
|
49
78
|
}
|
|
50
79
|
export declare function createAiSlice<PC extends BaseRoomConfig>(params: AiSliceOptions): StateCreator<AiSliceState>;
|
|
51
80
|
export declare function useStoreWithAi<T, PC extends BaseRoomConfig, S extends RoomState<PC> & AiSliceState>(selector: (state: S) => T): T;
|
|
81
|
+
export {};
|
|
52
82
|
//# sourceMappingURL=AiSlice.d.ts.map
|
package/dist/AiSlice.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiSlice.d.ts","sourceRoot":"","sources":["../src/AiSlice.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"AiSlice.d.ts","sourceRoot":"","sources":["../src/AiSlice.ts"],"names":[],"mappings":"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"}
|
package/dist/AiSlice.js
CHANGED
|
@@ -2,10 +2,10 @@ import { createId } from '@paralleldrive/cuid2';
|
|
|
2
2
|
import { createDefaultAiConfig, } from '@sqlrooms/ai-config';
|
|
3
3
|
import { createBaseSlice, useBaseRoomStore, } from '@sqlrooms/room-store';
|
|
4
4
|
import { produce } from 'immer';
|
|
5
|
-
import {
|
|
5
|
+
import { createLocalChatTransportFactory, createRemoteChatTransportFactory, createChatHandlers, } from './chatTransport';
|
|
6
6
|
import { hasAiSettingsConfig } from './hasAiSettingsConfig';
|
|
7
7
|
export function createAiSlice(params) {
|
|
8
|
-
const { defaultProvider = 'openai', defaultModel = 'gpt-4.1',
|
|
8
|
+
const { initialAnalysisPrompt = '', tools, getApiKey, getBaseUrl, maxSteps = 50, getInstructions, defaultProvider = 'openai', defaultModel = 'gpt-4.1', getCustomModel, chatEndPoint = '', chatHeaders = {}, } = params;
|
|
9
9
|
return createBaseSlice((set, get, store) => {
|
|
10
10
|
return {
|
|
11
11
|
ai: {
|
|
@@ -71,6 +71,9 @@ export function createAiSlice(params) {
|
|
|
71
71
|
model: model || currentSession?.model || 'gpt-4.1',
|
|
72
72
|
analysisResults: [],
|
|
73
73
|
createdAt: new Date(),
|
|
74
|
+
uiMessages: [],
|
|
75
|
+
toolAdditionalData: {},
|
|
76
|
+
messagesRevision: 0,
|
|
74
77
|
});
|
|
75
78
|
draft.ai.config.currentSessionId = newSessionId;
|
|
76
79
|
}));
|
|
@@ -119,93 +122,34 @@ export function createAiSlice(params) {
|
|
|
119
122
|
}));
|
|
120
123
|
},
|
|
121
124
|
/**
|
|
122
|
-
*
|
|
123
|
-
* TODO: how to pass the history analysisResults?
|
|
125
|
+
* Save the Ai SDK UI messages for a session
|
|
124
126
|
*/
|
|
125
|
-
|
|
126
|
-
const resultId = createId();
|
|
127
|
-
const abortController = new AbortController();
|
|
128
|
-
const currentSession = get().ai.getCurrentSession();
|
|
129
|
-
if (!currentSession) {
|
|
130
|
-
console.error('No current session found');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
127
|
+
setSessionUiMessages: (sessionId, uiMessages) => {
|
|
133
128
|
set((state) => produce(state, (draft) => {
|
|
134
|
-
draft.ai.
|
|
135
|
-
draft.ai.isRunningAnalysis = true;
|
|
136
|
-
const session = draft.ai.config.sessions.find((s) => s.id === draft.ai.config.currentSessionId);
|
|
129
|
+
const session = draft.ai.config.sessions.find((s) => s.id === sessionId);
|
|
137
130
|
if (session) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
streamMessage: {
|
|
142
|
-
parts: [
|
|
143
|
-
{
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: '',
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
isCompleted: false,
|
|
150
|
-
});
|
|
131
|
+
// store the latest UI messages from the chat hook
|
|
132
|
+
// Create a deep copy to avoid read-only property issues
|
|
133
|
+
session.uiMessages = JSON.parse(JSON.stringify(uiMessages));
|
|
151
134
|
}
|
|
152
135
|
}));
|
|
153
|
-
try {
|
|
154
|
-
await runAnalysis({
|
|
155
|
-
modelProvider: currentSession.modelProvider || defaultProvider,
|
|
156
|
-
model: currentSession.model || defaultModel,
|
|
157
|
-
apiKey: get().ai.getApiKeyFromSettings(),
|
|
158
|
-
baseUrl: get().ai.getBaseUrlFromSettings(),
|
|
159
|
-
prompt: get().ai.analysisPrompt,
|
|
160
|
-
abortController,
|
|
161
|
-
tools: get().ai.tools,
|
|
162
|
-
maxSteps: get().ai.getMaxStepsFromSettings(),
|
|
163
|
-
getInstructions: get().ai.getFullInstructions,
|
|
164
|
-
historyAnalysis: currentSession.analysisResults,
|
|
165
|
-
onStreamResult: (isCompleted, streamMessage) => {
|
|
166
|
-
set(makeResultsAppender({
|
|
167
|
-
resultId,
|
|
168
|
-
streamMessage,
|
|
169
|
-
isCompleted,
|
|
170
|
-
}));
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
catch (err) {
|
|
175
|
-
set(makeResultsAppender({
|
|
176
|
-
resultId,
|
|
177
|
-
isCompleted: true,
|
|
178
|
-
errorMessage: {
|
|
179
|
-
error: err instanceof Error ? err.message : String(err),
|
|
180
|
-
},
|
|
181
|
-
}));
|
|
182
|
-
}
|
|
183
|
-
finally {
|
|
184
|
-
set((state) => produce(state, (draft) => {
|
|
185
|
-
draft.ai.isRunningAnalysis = false;
|
|
186
|
-
draft.ai.analysisPrompt = '';
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
cancelAnalysis: () => {
|
|
191
|
-
set((state) => produce(state, (draft) => {
|
|
192
|
-
draft.ai.isRunningAnalysis = false;
|
|
193
|
-
}));
|
|
194
|
-
get().ai.analysisAbortController?.abort('Analysis cancelled');
|
|
195
136
|
},
|
|
196
137
|
/**
|
|
197
|
-
*
|
|
138
|
+
* Save additional data for a session
|
|
198
139
|
*/
|
|
199
|
-
|
|
140
|
+
setSessionToolAdditionalData: (sessionId, toolCallId, additionalData) => {
|
|
200
141
|
set((state) => produce(state, (draft) => {
|
|
201
142
|
const session = draft.ai.config.sessions.find((s) => s.id === sessionId);
|
|
202
143
|
if (session) {
|
|
203
|
-
|
|
144
|
+
if (!session.toolAdditionalData) {
|
|
145
|
+
session.toolAdditionalData = {};
|
|
146
|
+
}
|
|
147
|
+
session.toolAdditionalData[toolCallId] = additionalData;
|
|
204
148
|
}
|
|
205
149
|
}));
|
|
206
150
|
},
|
|
207
151
|
findToolComponent: (toolName) => {
|
|
208
|
-
return
|
|
152
|
+
return get().ai.tools[toolName]?.component;
|
|
209
153
|
},
|
|
210
154
|
getBaseUrlFromSettings: () => {
|
|
211
155
|
// First try the getBaseUrl function if provided
|
|
@@ -233,7 +177,7 @@ export function createAiSlice(params) {
|
|
|
233
177
|
const currentSession = getCurrentSessionFromState(store);
|
|
234
178
|
if (currentSession) {
|
|
235
179
|
// First try the getApiKey function if provided
|
|
236
|
-
const apiKeyFromFunction = getApiKey?.(currentSession.modelProvider ||
|
|
180
|
+
const apiKeyFromFunction = getApiKey?.(currentSession.modelProvider || 'openai');
|
|
237
181
|
if (apiKeyFromFunction) {
|
|
238
182
|
return apiKeyFromFunction;
|
|
239
183
|
}
|
|
@@ -264,7 +208,7 @@ export function createAiSlice(params) {
|
|
|
264
208
|
return settingsMaxSteps;
|
|
265
209
|
}
|
|
266
210
|
}
|
|
267
|
-
return
|
|
211
|
+
return 50;
|
|
268
212
|
},
|
|
269
213
|
getFullInstructions: () => {
|
|
270
214
|
const store = get();
|
|
@@ -279,6 +223,189 @@ export function createAiSlice(params) {
|
|
|
279
223
|
}
|
|
280
224
|
return instructions;
|
|
281
225
|
},
|
|
226
|
+
/**
|
|
227
|
+
* Start the analysis
|
|
228
|
+
* TODO: how to pass the history analysisResults?
|
|
229
|
+
*/
|
|
230
|
+
startAnalysis: async (sendMessage) => {
|
|
231
|
+
const abortController = new AbortController();
|
|
232
|
+
const currentSession = get().ai.getCurrentSession();
|
|
233
|
+
if (!currentSession) {
|
|
234
|
+
console.error('No current session found');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const promptText = get().ai.analysisPrompt;
|
|
238
|
+
set((state) => produce(state, (draft) => {
|
|
239
|
+
draft.ai.analysisAbortController = abortController;
|
|
240
|
+
draft.ai.isRunningAnalysis = true;
|
|
241
|
+
// Add incomplete analysis result to session immediately for instant UI rendering
|
|
242
|
+
const session = draft.ai.config.sessions.find((s) => s.id === currentSession.id);
|
|
243
|
+
if (session) {
|
|
244
|
+
// Add incomplete analysis result with a temporary ID
|
|
245
|
+
// This will be updated in onChatFinish with the actual user message ID
|
|
246
|
+
session.analysisResults.push({
|
|
247
|
+
id: '__pending__',
|
|
248
|
+
prompt: promptText,
|
|
249
|
+
isCompleted: false,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
// The pending analysis result will be updated in onChatFinish with the correct message ID
|
|
254
|
+
sendMessage({ text: promptText });
|
|
255
|
+
},
|
|
256
|
+
cancelAnalysis: () => {
|
|
257
|
+
const currentSession = get().ai.getCurrentSession();
|
|
258
|
+
set((state) => produce(state, (draft) => {
|
|
259
|
+
draft.ai.isRunningAnalysis = false;
|
|
260
|
+
// Remove pending analysis result if it exists
|
|
261
|
+
if (currentSession) {
|
|
262
|
+
const session = draft.ai.config.sessions.find((s) => s.id === currentSession.id);
|
|
263
|
+
if (session) {
|
|
264
|
+
const pendingIndex = session.analysisResults.findIndex((result) => result.id === '__pending__');
|
|
265
|
+
if (pendingIndex !== -1) {
|
|
266
|
+
session.analysisResults.splice(pendingIndex, 1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}));
|
|
271
|
+
get().ai.analysisAbortController?.abort('Analysis cancelled');
|
|
272
|
+
},
|
|
273
|
+
/**
|
|
274
|
+
* Get the assistant message parts for a given analysis result ID
|
|
275
|
+
* @param analysisResultId - The ID of the analysis result (user message ID)
|
|
276
|
+
* @returns Array of message parts from the assistant's response
|
|
277
|
+
*/
|
|
278
|
+
getAssistantMessageParts: (analysisResultId) => {
|
|
279
|
+
const currentSession = get().ai.getCurrentSession();
|
|
280
|
+
if (!currentSession)
|
|
281
|
+
return [];
|
|
282
|
+
const uiMessages = currentSession.uiMessages;
|
|
283
|
+
// Find the user message with analysisResultId
|
|
284
|
+
const userMessageIndex = uiMessages.findIndex((msg) => msg.id === analysisResultId && msg.role === 'user');
|
|
285
|
+
if (userMessageIndex === -1)
|
|
286
|
+
return [];
|
|
287
|
+
// Find the next assistant message after this user message
|
|
288
|
+
for (let i = userMessageIndex + 1; i < uiMessages.length; i++) {
|
|
289
|
+
const msg = uiMessages[i];
|
|
290
|
+
if (msg?.role === 'assistant') {
|
|
291
|
+
return msg.parts;
|
|
292
|
+
}
|
|
293
|
+
if (msg?.role === 'user') {
|
|
294
|
+
// Hit next user message without finding assistant response
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return [];
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* Delete an analysis result from a session
|
|
302
|
+
* - remove the corresponding prompt-response pair from uiMessages
|
|
303
|
+
* - remove the associated toolAdditionalData
|
|
304
|
+
*/
|
|
305
|
+
deleteAnalysisResult: (sessionId, resultId) => {
|
|
306
|
+
set((state) => produce(state, (draft) => {
|
|
307
|
+
const session = draft.ai.config.sessions.find((s) => s.id === sessionId);
|
|
308
|
+
if (session) {
|
|
309
|
+
session.analysisResults = session.analysisResults.filter((r) => r.id !== resultId);
|
|
310
|
+
// Remove corresponding prompt-response pair from uiMessages
|
|
311
|
+
const uiMessages = session.uiMessages;
|
|
312
|
+
const userMessageIndex = uiMessages.findIndex((msg) => msg.id === resultId && msg.role === 'user');
|
|
313
|
+
if (userMessageIndex !== -1) {
|
|
314
|
+
// Find the next user message (or end of array) to determine response boundary
|
|
315
|
+
let nextUserIndex = userMessageIndex + 1;
|
|
316
|
+
const toolCallIdsToDelete = new Set();
|
|
317
|
+
while (nextUserIndex < uiMessages.length &&
|
|
318
|
+
uiMessages[nextUserIndex]?.role !== 'user') {
|
|
319
|
+
const msg = uiMessages[nextUserIndex];
|
|
320
|
+
// Extract toolCallId from message parts
|
|
321
|
+
if (msg?.parts) {
|
|
322
|
+
for (const part of msg.parts) {
|
|
323
|
+
// Check for tool-* or dynamic-tool parts that have toolCallId
|
|
324
|
+
if ('toolCallId' in part &&
|
|
325
|
+
typeof part.toolCallId === 'string') {
|
|
326
|
+
toolCallIdsToDelete.add(part.toolCallId);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
nextUserIndex++;
|
|
331
|
+
}
|
|
332
|
+
// Remove the user message and all assistant messages until the next user message
|
|
333
|
+
session.uiMessages.splice(userMessageIndex, nextUserIndex - userMessageIndex);
|
|
334
|
+
// Increment messagesRevision to force useChat reset
|
|
335
|
+
session.messagesRevision =
|
|
336
|
+
(session.messagesRevision || 0) + 1;
|
|
337
|
+
// Clean up toolAdditionalData for deleted messages
|
|
338
|
+
if (session.toolAdditionalData) {
|
|
339
|
+
// Remove data keyed by the toolCallId from the deleted messages
|
|
340
|
+
toolCallIdsToDelete.forEach((toolCallId) => {
|
|
341
|
+
if (session.toolAdditionalData[toolCallId]) {
|
|
342
|
+
delete session.toolAdditionalData[toolCallId];
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}));
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* Get analysis results for the current session by transforming UI messages
|
|
352
|
+
* into structured analysis results (user prompt → AI response pairs).
|
|
353
|
+
*
|
|
354
|
+
* @returns Array of analysis results for the current session
|
|
355
|
+
*/
|
|
356
|
+
getAnalysisResults: () => {
|
|
357
|
+
const currentSession = get().ai.getCurrentSession();
|
|
358
|
+
if (!currentSession)
|
|
359
|
+
return [];
|
|
360
|
+
return currentSession.analysisResults;
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Add an analysis result to the current session
|
|
364
|
+
* - add the message to the uiMessages
|
|
365
|
+
* - add the analysis result to the analysisResults
|
|
366
|
+
*/
|
|
367
|
+
addAnalysisResult: (message) => {
|
|
368
|
+
const currentSession = get().ai.getCurrentSession();
|
|
369
|
+
if (!currentSession) {
|
|
370
|
+
console.error('No current session found');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
set((state) => produce(state, (draft) => {
|
|
374
|
+
// Extract text content from message parts
|
|
375
|
+
const textContent = message.parts
|
|
376
|
+
?.filter((part) => part.type === 'text')
|
|
377
|
+
?.map((part) => part.text)
|
|
378
|
+
?.join('') || '';
|
|
379
|
+
draft.ai.config.sessions
|
|
380
|
+
.find((s) => s.id === currentSession?.id)
|
|
381
|
+
?.analysisResults.push({
|
|
382
|
+
id: message.id,
|
|
383
|
+
prompt: textContent,
|
|
384
|
+
isCompleted: true,
|
|
385
|
+
});
|
|
386
|
+
}));
|
|
387
|
+
},
|
|
388
|
+
// Chat transport configuration
|
|
389
|
+
chatEndPoint,
|
|
390
|
+
chatHeaders,
|
|
391
|
+
getLocalChatTransport: () => {
|
|
392
|
+
const state = get();
|
|
393
|
+
return createLocalChatTransportFactory({
|
|
394
|
+
store,
|
|
395
|
+
defaultProvider: defaultProvider,
|
|
396
|
+
defaultModel: defaultModel,
|
|
397
|
+
apiKey: state.ai.getApiKeyFromSettings(),
|
|
398
|
+
baseUrl: state.ai.getBaseUrlFromSettings(),
|
|
399
|
+
getInstructions: () => store.getState().ai.getFullInstructions(),
|
|
400
|
+
getCustomModel,
|
|
401
|
+
})();
|
|
402
|
+
},
|
|
403
|
+
getRemoteChatTransport: (endpoint, headers) => createRemoteChatTransportFactory({
|
|
404
|
+
store,
|
|
405
|
+
defaultProvider,
|
|
406
|
+
defaultModel,
|
|
407
|
+
})(endpoint, headers),
|
|
408
|
+
...createChatHandlers({ store }),
|
|
282
409
|
},
|
|
283
410
|
};
|
|
284
411
|
});
|
|
@@ -290,77 +417,6 @@ function getCurrentSessionFromState(state) {
|
|
|
290
417
|
const { currentSessionId, sessions } = state.ai.config;
|
|
291
418
|
return sessions.find((session) => session.id === currentSessionId);
|
|
292
419
|
}
|
|
293
|
-
function findResultById(analysisResults, id) {
|
|
294
|
-
return analysisResults.find((result) => result.id === id);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Appends the tool results, tool calls, and analysis to the state
|
|
298
|
-
*
|
|
299
|
-
* @param resultId - The id of the result to append to
|
|
300
|
-
* @param message - The message to append to the state. The structure of the message is defined as:
|
|
301
|
-
* - reasoning: string The reasoning of the assistant
|
|
302
|
-
* - toolCallMessages: ToolCallMessage[] The tool call messages
|
|
303
|
-
* - text: string The final text message
|
|
304
|
-
* @param isCompleted - Whether the analysis is completed
|
|
305
|
-
* @returns The new state
|
|
306
|
-
*/
|
|
307
|
-
function makeResultsAppender({ resultId, streamMessage, errorMessage, isCompleted, }) {
|
|
308
|
-
return (state) => produce(state, (draft) => {
|
|
309
|
-
const currentSession = getCurrentSessionFromState(draft);
|
|
310
|
-
if (!currentSession) {
|
|
311
|
-
console.error('No current session found');
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const result = findResultById(currentSession.analysisResults, resultId);
|
|
315
|
-
if (result) {
|
|
316
|
-
if (streamMessage && streamMessage.parts) {
|
|
317
|
-
// copy all properties from streamMessage
|
|
318
|
-
const newStreamMessage = {
|
|
319
|
-
parts: streamMessage.parts.map((part) => {
|
|
320
|
-
if (part.type === 'text') {
|
|
321
|
-
return {
|
|
322
|
-
type: 'text',
|
|
323
|
-
text: part.text,
|
|
324
|
-
additionalData: part.additionalData,
|
|
325
|
-
isCompleted: part.isCompleted,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
else if (part.type === 'tool-invocation') {
|
|
329
|
-
return {
|
|
330
|
-
type: 'tool-invocation',
|
|
331
|
-
toolInvocation: {
|
|
332
|
-
toolCallId: part.toolInvocation.toolCallId,
|
|
333
|
-
toolName: part.toolInvocation.toolName,
|
|
334
|
-
args: part.toolInvocation.args,
|
|
335
|
-
state: part.toolInvocation.state,
|
|
336
|
-
result: part.toolInvocation.state === 'result'
|
|
337
|
-
? part.toolInvocation.result
|
|
338
|
-
: undefined,
|
|
339
|
-
},
|
|
340
|
-
additionalData: part.additionalData,
|
|
341
|
-
isCompleted: part.isCompleted,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
// TODO: handle other part types later
|
|
346
|
-
return part;
|
|
347
|
-
}
|
|
348
|
-
}),
|
|
349
|
-
};
|
|
350
|
-
result.streamMessage = newStreamMessage;
|
|
351
|
-
}
|
|
352
|
-
if (errorMessage) {
|
|
353
|
-
result.errorMessage = errorMessage;
|
|
354
|
-
}
|
|
355
|
-
if (isCompleted) {
|
|
356
|
-
result.isCompleted = isCompleted;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
console.error('Result not found', resultId);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
420
|
export function useStoreWithAi(selector) {
|
|
365
421
|
return useBaseRoomStore((state) => selector(state));
|
|
366
422
|
}
|