@stack-spot/ai-chat-widget 1.0.0-dev.1769120820021 → 1.0.0-dev.1769797270860
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/CHANGELOG.md +27 -0
- package/README.md +2 -1
- package/dist/StackspotAIWidget.d.ts +1 -0
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +1 -0
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/app-metadata.json +15 -3
- package/dist/chat-interceptors/quick-commands.d.ts +15 -0
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +29 -234
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +39 -38
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/FileDescription.js +1 -1
- package/dist/components/FileDescription.js.map +1 -1
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +22 -6
- package/dist/components/Markdown.js.map +1 -1
- package/dist/components/Selector/index.d.ts +1 -1
- package/dist/components/Selector/index.d.ts.map +1 -1
- package/dist/components/Selector/index.js +3 -3
- package/dist/components/Selector/index.js.map +1 -1
- package/dist/components/TabManager.d.ts.map +1 -1
- package/dist/components/TabManager.js +20 -4
- package/dist/components/TabManager.js.map +1 -1
- package/dist/state/ChatState.d.ts +5 -0
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js.map +1 -1
- package/dist/utils/chat.d.ts +4 -12
- package/dist/utils/chat.d.ts.map +1 -1
- package/dist/utils/chat.js +20 -19
- package/dist/utils/chat.js.map +1 -1
- package/dist/utils/knowledge-source.js +1 -1
- package/dist/utils/knowledge-source.js.map +1 -1
- package/dist/views/ChatHistory/utils.d.ts.map +1 -1
- package/dist/views/ChatHistory/utils.js +9 -5
- package/dist/views/ChatHistory/utils.js.map +1 -1
- package/dist/views/Editor.d.ts.map +1 -1
- package/dist/views/Editor.js +16 -2
- package/dist/views/Editor.js.map +1 -1
- package/dist/views/KnowledgeSources.js +1 -1
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/ContextBar.js +1 -1
- package/dist/views/MessageInput/ContextBar.js.map +1 -1
- package/dist/views/Steps/dictionary.d.ts +1 -1
- package/package.json +5 -2
- package/src/StackspotAIWidget.tsx +1 -0
- package/src/app-metadata.json +15 -3
- package/src/chat-interceptors/quick-commands.ts +38 -278
- package/src/chat-interceptors/send-message.ts +40 -40
- package/src/components/FileDescription.tsx +1 -1
- package/src/components/Markdown.tsx +42 -23
- package/src/components/Selector/index.tsx +6 -6
- package/src/components/TabManager.tsx +31 -8
- package/src/state/ChatState.ts +6 -0
- package/src/utils/chat.ts +23 -22
- package/src/utils/knowledge-source.ts +1 -1
- package/src/views/ChatHistory/utils.ts +11 -6
- package/src/views/Editor.tsx +20 -3
- package/src/views/KnowledgeSources.tsx +1 -1
- package/src/views/MessageInput/ContextBar.tsx +1 -1
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
import { aiClient, CancelledError,
|
|
2
|
-
import {
|
|
1
|
+
import { aiClient, CancelledError, QCContext as QCContextLib, StackspotAPIError } from '@stack-spot/portal-network'
|
|
2
|
+
import { QuickCommandResponse, QuickCommandStepResult } from '@stack-spot/portal-network/api/ai'
|
|
3
3
|
import { Dictionary, interpolate, translate } from '@stack-spot/portal-translate'
|
|
4
|
-
import { isNil } from 'lodash'
|
|
5
4
|
import type { editor } from 'monaco-editor'
|
|
6
5
|
import { ulid } from 'ulid'
|
|
7
6
|
import { AbortedError } from '../AbortedError'
|
|
7
|
+
import appData from '../app-metadata.json'
|
|
8
8
|
import { quickCommandRegex } from '../regex'
|
|
9
9
|
import { ChatEntry } from '../state/ChatEntry'
|
|
10
10
|
import { ChatState } from '../state/ChatState'
|
|
11
11
|
import { LabeledWithImage } from '../state/types'
|
|
12
12
|
import { WidgetState } from '../state/WidgetState'
|
|
13
|
-
import {
|
|
14
|
-
import { getSizeOfString } from '../utils/string'
|
|
13
|
+
import { defaultLanguage } from '../utils/programming-languages'
|
|
15
14
|
import { CustomInputs } from './CustomInputs'
|
|
16
15
|
|
|
16
|
+
export function buildQuickCommandContext(state: ChatState, message?: ChatEntry) {
|
|
17
|
+
return {
|
|
18
|
+
workspace: state.get('workspace')?.id,
|
|
19
|
+
conversation_id: state.id,
|
|
20
|
+
stack_id: state.get('stack')?.id,
|
|
21
|
+
language: state.get('codeLanguage') || (state.get('codeSelection') ? defaultLanguage : undefined),
|
|
22
|
+
knowledge_sources: state.get('knowledgeSources')?.map(ks => ks.id),
|
|
23
|
+
upload_ids: message?.getValue().upload?.map(f => f.id),
|
|
24
|
+
agent_id: state.get('agent')?.id,
|
|
25
|
+
agent_built_in: state.get('agent')?.builtIn,
|
|
26
|
+
os: navigator.userAgent,
|
|
27
|
+
platform: 'web-widget',
|
|
28
|
+
platform_version: navigator.userAgent,
|
|
29
|
+
stackspot_ai_version: appData.version,
|
|
30
|
+
selected_model_id: state.get('selected_model_id'),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type QuickCommandContext = ReturnType<typeof buildQuickCommandContext>
|
|
35
|
+
|
|
17
36
|
type SlugExecution = Record<string, QuickCommandStepResult>
|
|
18
37
|
|
|
19
38
|
interface QCContext {
|
|
20
39
|
qc: QuickCommandResponse,
|
|
21
|
-
context:
|
|
40
|
+
context: QuickCommandContext,
|
|
22
41
|
resultMap: SlugExecution,
|
|
23
42
|
customInputs: Record<string, string>,
|
|
24
43
|
chat: ChatState,
|
|
@@ -73,132 +92,6 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
73
92
|
)
|
|
74
93
|
}
|
|
75
94
|
|
|
76
|
-
/**
|
|
77
|
-
* Runs an Router step of a quick command.
|
|
78
|
-
*/
|
|
79
|
-
async function runRouterStep(
|
|
80
|
-
ctx: QCContext,
|
|
81
|
-
stepIndex: number, iteration: Record<string, number>,
|
|
82
|
-
progress: { update: (index: number) => void, remove: () => void },
|
|
83
|
-
) {
|
|
84
|
-
const { qc: { slug, steps }, code, resultMap, customInputs } = ctx
|
|
85
|
-
const step = steps![stepIndex]
|
|
86
|
-
const inputData = Object.keys(customInputs).length > 0 && code ? { ...customInputs, [code]: code } : code ?? customInputs
|
|
87
|
-
try {
|
|
88
|
-
if (step.slug in iteration) {
|
|
89
|
-
iteration[step.slug] = iteration[step.slug] + 1
|
|
90
|
-
} else {
|
|
91
|
-
iteration[step.slug] = 1
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const { next_step_slug } = await aiClient.calculateNextStep.mutate({
|
|
95
|
-
stepSlug: step.slug,
|
|
96
|
-
slug: slug,
|
|
97
|
-
quickCommandEvaluateStepRouterRequest: {
|
|
98
|
-
executions_count: iteration[step.slug],
|
|
99
|
-
input_data: inputData,
|
|
100
|
-
slugs_executions: resultMap,
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
if (next_step_slug === step.slug) {
|
|
105
|
-
return runStepsRecursively(stepIndex, progress, ctx, iteration)
|
|
106
|
-
}
|
|
107
|
-
const nextStepIndex = steps?.findIndex((step) => step.slug === next_step_slug)
|
|
108
|
-
|
|
109
|
-
if (isNil(nextStepIndex) || nextStepIndex === -1) return
|
|
110
|
-
|
|
111
|
-
return runStepsRecursively(nextStepIndex, progress, ctx, iteration)
|
|
112
|
-
}
|
|
113
|
-
catch (error: any) {
|
|
114
|
-
// eslint-disable-next-line no-console
|
|
115
|
-
console.error('Error executing QC step', error)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Runs a fetch step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
|
|
121
|
-
*/
|
|
122
|
-
async function runFetchStep(ctx: QCContext, stepIndex: number) {
|
|
123
|
-
const { qc: { slug, steps }, code, context, resultMap, customInputs, executionId, signal } = ctx
|
|
124
|
-
const step = steps![stepIndex] as QuickCommandStepFetchResponse
|
|
125
|
-
if (step.is_remote) {
|
|
126
|
-
ctx.isRemote = true
|
|
127
|
-
|
|
128
|
-
const { data } = await aiClient.fetchStepOfQuickCommandRemotely.mutate({
|
|
129
|
-
slug, stepSlug: step.slug,
|
|
130
|
-
quickCommandsExecutionRequest: {
|
|
131
|
-
code_selection: code, context, qc_execution_id: executionId,
|
|
132
|
-
slugs_executions: { ...resultMap, ...customInputs },
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
//data is the return of the request in the QC so we do not have full control over the response
|
|
137
|
-
//We handle the usual format with body, status_code and headers, but we might also handle other formats
|
|
138
|
-
const responseData = data as any
|
|
139
|
-
resultMap[step.slug] = {
|
|
140
|
-
status: responseData.status_code || 200,
|
|
141
|
-
data: JSON.stringify(responseData.body) ?? JSON.stringify(responseData),
|
|
142
|
-
headers: responseData.headers ?? {},
|
|
143
|
-
}
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const { headers, data, method, url } = await aiClient.fetchStepOfQuickCommand.mutate({
|
|
148
|
-
slug,
|
|
149
|
-
stepSlug: step.slug,
|
|
150
|
-
quickCommandsExecutionRequest: {
|
|
151
|
-
input_data: code, context, qc_execution_id: executionId,
|
|
152
|
-
slugs_executions: { ...resultMap, ...customInputs },
|
|
153
|
-
},
|
|
154
|
-
}, signal)
|
|
155
|
-
const body = ['get', 'head'].includes(method.toLowerCase()) ? undefined : data
|
|
156
|
-
const response = await fetch(url, { headers: headers || undefined, body, method, signal })
|
|
157
|
-
if (!response.ok) throw new Error(`Failed to execute step "${step.slug}" of quick command "${slug}". Status ${response.status}.`)
|
|
158
|
-
resultMap[step.slug] = {
|
|
159
|
-
status: response.status,
|
|
160
|
-
data: await response.text(),
|
|
161
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Runs an LLM step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
|
|
167
|
-
*/
|
|
168
|
-
async function runLLMStep(
|
|
169
|
-
{ qc: { slug, steps }, code, customInputs, context, executionId, resultMap, signal }: QCContext,
|
|
170
|
-
stepIndex: number,
|
|
171
|
-
) {
|
|
172
|
-
const step = steps![stepIndex] as QuickCommandStepLlmResponse
|
|
173
|
-
let stepContext = context
|
|
174
|
-
if (!step.use_uploaded_files) {
|
|
175
|
-
const { upload_ids: _upload_ids, ...contextDataProps } = context
|
|
176
|
-
stepContext = { ...contextDataProps }
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const stream = aiClient.streamLlmStepOfQuickCommand(
|
|
180
|
-
slug,
|
|
181
|
-
step.slug,
|
|
182
|
-
{
|
|
183
|
-
input_data: code,
|
|
184
|
-
context: stepContext,
|
|
185
|
-
qc_execution_id: executionId,
|
|
186
|
-
slugs_executions: { ...resultMap, ...customInputs },
|
|
187
|
-
},
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
signal.addEventListener('abort', () => stream.cancel())
|
|
191
|
-
|
|
192
|
-
let finalValue: QuickCommandPromptResponse2
|
|
193
|
-
try {
|
|
194
|
-
finalValue = await stream.getValue()
|
|
195
|
-
resultMap[step.slug] = finalValue
|
|
196
|
-
} catch (error: any) {
|
|
197
|
-
// eslint-disable-next-line no-console
|
|
198
|
-
console.error('Error executing QC step', error)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
95
|
function updateProgressMessageForStep(message: ChatEntry, qc: QuickCommandResponse, stepIndex: number) {
|
|
203
96
|
const t = translate(dictionary)
|
|
204
97
|
message.setValue({
|
|
@@ -230,115 +123,17 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
230
123
|
}, showImmediately ? 0 : progressMessageDelayMS)
|
|
231
124
|
return controller
|
|
232
125
|
}
|
|
233
|
-
async function getScriptStepStatus(
|
|
234
|
-
scriptExecutionId: string,
|
|
235
|
-
interval = 5000,
|
|
236
|
-
maxAttempts = 500,
|
|
237
|
-
currentAttempt = 0,
|
|
238
|
-
): Promise<QuickCommandScriptExecutionResponse> {
|
|
239
|
-
if (currentAttempt >= maxAttempts) {
|
|
240
|
-
throw new Error('Max attempts reached in verify script status')
|
|
241
|
-
}
|
|
242
|
-
await aiClient.getStatusScriptStep.invalidate({ scriptExecutionId })
|
|
243
|
-
const response = await aiClient.getStatusScriptStep.query({ scriptExecutionId })
|
|
244
|
-
|
|
245
|
-
if (response.status === 'success') {
|
|
246
|
-
return response
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (response.status === 'failure') {
|
|
250
|
-
throw response
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
await new Promise(resolve => {setTimeout(resolve, interval)})
|
|
254
|
-
|
|
255
|
-
return getScriptStepStatus(scriptExecutionId, interval, maxAttempts, currentAttempt + 1)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function runScriptStep(
|
|
259
|
-
{ qc: { slug, steps }, code, context, resultMap, customInputs, signal }: QCContext,
|
|
260
|
-
stepIndex: number,
|
|
261
|
-
) {
|
|
262
|
-
const step = steps![stepIndex] as QuickCommandStepLlmResponse
|
|
263
|
-
let stepContext = context
|
|
264
|
-
if (!step.use_uploaded_files) {
|
|
265
|
-
const { upload_ids: _upload_ids, ...contextDataProps } = context
|
|
266
|
-
stepContext = { ...contextDataProps }
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const { script_execution_id } = await aiClient.startScriptStep.mutate({
|
|
271
|
-
stepSlug: step.slug,
|
|
272
|
-
slug: slug,
|
|
273
|
-
quickCommandStartScriptRequest: {
|
|
274
|
-
input_data: code,
|
|
275
|
-
custom_inputs: customInputs,
|
|
276
|
-
context: stepContext,
|
|
277
|
-
slugs_executions: resultMap,
|
|
278
|
-
},
|
|
279
|
-
}, signal)
|
|
280
|
-
const scriptResult = await getScriptStepStatus(script_execution_id)
|
|
281
|
-
resultMap[step.slug] = scriptResult
|
|
282
|
-
}
|
|
283
|
-
catch (error: any) {
|
|
284
|
-
throw new Error(`Failed to execute step "${step.slug}" of quick command "${slug}". Error ${error}.`)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function runStepsRecursively(currentIndex: number, progress: { update: (index: number) => void, remove: () => void },
|
|
289
|
-
ctx: QCContext, iteration: Record<string, number>) {
|
|
290
|
-
const { qc } = ctx
|
|
291
|
-
|
|
292
|
-
if (!qc.steps || currentIndex >= qc.steps?.length) return
|
|
293
|
-
progress.update(currentIndex)
|
|
294
|
-
|
|
295
|
-
if (qc.steps[currentIndex].type === 'ROUTER') {
|
|
296
|
-
await runRouterStep(ctx, currentIndex, iteration, progress)
|
|
297
|
-
return
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (qc.steps[currentIndex].type === 'SCRIPT') {
|
|
301
|
-
await runScriptStep(ctx, currentIndex)
|
|
302
|
-
} else {
|
|
303
|
-
await (qc.steps[currentIndex].type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const currentStep = qc.steps?.[currentIndex] as QuickCommandStepFetchResponse | QuickCommandStepLlmResponse
|
|
307
|
-
let nextIndex = currentIndex + 1
|
|
308
|
-
|
|
309
|
-
let nextStepSlug = currentStep.next_step_slug
|
|
310
|
-
const stepResult = ctx.resultMap[currentStep.slug]
|
|
311
|
-
if (stepResult && typeof stepResult !== 'string' && 'answer_status' in stepResult && !!stepResult.answer_status?.next_step_slug) {
|
|
312
|
-
nextStepSlug = stepResult.answer_status.next_step_slug
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (nextStepSlug) {
|
|
316
|
-
nextIndex = nextStepSlug === 'end' ?
|
|
317
|
-
qc.steps.length : qc.steps?.findIndex((step) => step.slug === nextStepSlug)
|
|
318
|
-
}
|
|
319
|
-
await runStepsRecursively(nextIndex, progress, ctx, iteration)
|
|
320
|
-
}
|
|
321
126
|
|
|
322
|
-
async function
|
|
127
|
+
async function runQC(ctx: QCContext) {
|
|
323
128
|
const progress = showProgressMessage(ctx)
|
|
324
|
-
try {
|
|
325
|
-
await runStepsRecursively(0, progress, ctx, {})
|
|
326
|
-
} finally {
|
|
327
|
-
progress.remove()
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
129
|
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
slugs_executions: { ...resultMap, ...customInputs },
|
|
339
|
-
},
|
|
340
|
-
}, signal)
|
|
341
|
-
return formatted.result
|
|
130
|
+
// await runStepsRecursively(0, progress, ctx, {}
|
|
131
|
+
const { qc, ...props } = ctx
|
|
132
|
+
const context: QCContextLib = { ...props, slug: qc.slug }
|
|
133
|
+
const finalResult = await aiClient.runQuickCommand(context, progress)
|
|
134
|
+
|
|
135
|
+
progress.remove()
|
|
136
|
+
return finalResult
|
|
342
137
|
}
|
|
343
138
|
|
|
344
139
|
// opens a new chat tab if the quick command doesn't preserve the conversation and the current conversation isn't clean.
|
|
@@ -368,7 +163,7 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
368
163
|
} catch (error) {
|
|
369
164
|
throw error instanceof StackspotAPIError && error.status === 404 ? new Error(t.notFound) : error
|
|
370
165
|
}
|
|
371
|
-
|
|
166
|
+
|
|
372
167
|
if (ctx.qc.use_selected_code && (!code && ctx.context.upload_ids?.length === 0)) {
|
|
373
168
|
widget.set('panel', 'editor')
|
|
374
169
|
ctx.chat.pushMessage(new ChatEntry({
|
|
@@ -377,44 +172,12 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
377
172
|
agent: ctx.chat.get('agent'),
|
|
378
173
|
content: t.requiresSelection,
|
|
379
174
|
}))
|
|
380
|
-
return
|
|
175
|
+
return
|
|
381
176
|
}
|
|
382
177
|
manageConversationContext(ctx)
|
|
383
178
|
await computeCustomInputs(ctx)
|
|
384
|
-
await
|
|
385
|
-
return
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* This registers a quick command event in the backend (analytics).
|
|
390
|
-
*/
|
|
391
|
-
async function registerAnalyticsEvent({ qc, isRemote, executionId, code = '', context }: QCContext, status: string, start: number) {
|
|
392
|
-
const now = new Date().getTime()
|
|
393
|
-
try {
|
|
394
|
-
await aiClient.createEvent.mutate({
|
|
395
|
-
body: [{
|
|
396
|
-
type: 'custom_quick_command_execution',
|
|
397
|
-
quick_command_event: {
|
|
398
|
-
type: qc.type || '',
|
|
399
|
-
duration_execution: now - start,
|
|
400
|
-
status_execution: status,
|
|
401
|
-
slug: qc.slug,
|
|
402
|
-
qc_execution_id: executionId,
|
|
403
|
-
id: qc.id,
|
|
404
|
-
//@ts-ignore
|
|
405
|
-
is_remote: isRemote,
|
|
406
|
-
},
|
|
407
|
-
code,
|
|
408
|
-
context,
|
|
409
|
-
knowledge_sources: [],
|
|
410
|
-
size: getSizeOfString(code),
|
|
411
|
-
generated_at: now,
|
|
412
|
-
}],
|
|
413
|
-
})
|
|
414
|
-
} catch (error) {
|
|
415
|
-
// eslint-disable-next-line no-console
|
|
416
|
-
console.warn('Failed to register event: quick command.')
|
|
417
|
-
}
|
|
179
|
+
const result = await runQC(ctx)
|
|
180
|
+
return result
|
|
418
181
|
}
|
|
419
182
|
|
|
420
183
|
/**
|
|
@@ -455,19 +218,17 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
455
218
|
qc: { slug } as QuickCommandResponse,
|
|
456
219
|
chat,
|
|
457
220
|
code: chat.get('codeSelection'),
|
|
458
|
-
context:
|
|
221
|
+
context: buildQuickCommandContext(chat, entry) ?? {},
|
|
459
222
|
executionId: ulid(),
|
|
460
223
|
resultMap: {},
|
|
461
224
|
customInputs: {},
|
|
462
225
|
signal,
|
|
463
226
|
}
|
|
464
227
|
chat.set('isLoading', true)
|
|
465
|
-
const start = new Date().getTime()
|
|
466
228
|
try {
|
|
467
229
|
const result = await runQuickCommand(ctx)
|
|
468
230
|
if (result) {
|
|
469
231
|
outputResult(ctx, result)
|
|
470
|
-
registerAnalyticsEvent(ctx, '200', start)
|
|
471
232
|
}
|
|
472
233
|
} catch (error: any) {
|
|
473
234
|
let message = error.message || `${error}`
|
|
@@ -480,7 +241,6 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
480
241
|
agent: chat.get('agent'),
|
|
481
242
|
type: 'text',
|
|
482
243
|
}))
|
|
483
|
-
registerAnalyticsEvent(ctx, message, start)
|
|
484
244
|
}
|
|
485
245
|
ctx.chat.set('isLoading', false)
|
|
486
246
|
// prevents the next interceptors from running
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AgentInfo, aiClient, ChatResponseWithPMResources, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
|
|
2
|
-
import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
|
|
1
|
+
import { AgentInfo, aiClient, ChatResponse, ChatResponseWithPMResources, ChatResponseWithSteps, ChatStep, genAiInferenceClient, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
|
|
3
2
|
import { findLast } from 'lodash'
|
|
4
3
|
import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
|
|
5
4
|
import { ChatState } from '../state/ChatState'
|
|
@@ -27,13 +26,13 @@ export function createEntryValueFromChatResponse(
|
|
|
27
26
|
const entry = {
|
|
28
27
|
agentType: 'bot',
|
|
29
28
|
type: 'md',
|
|
30
|
-
content: response.
|
|
29
|
+
content: response.message ?? '',
|
|
31
30
|
messageId: response.message_id ?? undefined,
|
|
32
31
|
knowledgeSources,
|
|
33
32
|
agent: agent,
|
|
34
33
|
updated: includeDate ? new Date().toISOString() : undefined,
|
|
35
34
|
steps: response.steps,
|
|
36
|
-
tools: response.
|
|
35
|
+
tools: response.tools_id,
|
|
37
36
|
opportunities: response.opportunities,
|
|
38
37
|
hypothesis: response.hypothesis,
|
|
39
38
|
prfaq: response.prfaq,
|
|
@@ -112,39 +111,44 @@ const updatePlanningMessage = (messages: ChatEntry[]) => {
|
|
|
112
111
|
|
|
113
112
|
export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResponseWithSteps> & { opportunities?: any },
|
|
114
113
|
chat: ChatState, botEntry: ChatEntry, knowledgeSources: KnowledgeSource[] | undefined) {
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
for (const agent_info of value.agent_info ?? []) {
|
|
115
|
+
if (agent_info?.type === 'planning') {
|
|
116
|
+
if (agent_info.action === 'start') {
|
|
117
|
+
chat.set('isPlaning', true)
|
|
118
|
+
} else {
|
|
119
|
+
chat.set('isPlaning', false)
|
|
120
|
+
}
|
|
120
121
|
}
|
|
121
|
-
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
if (agent_info?.type === 'chat' && agent_info?.action === 'end') {
|
|
124
|
+
//When an error happens, the step can still be running, so we enforce the error
|
|
125
|
+
const stepRunning = findLast(value.steps, (item) => item.status === 'running')
|
|
126
|
+
if (stepRunning?.status) {
|
|
127
|
+
stepRunning.status = 'error'
|
|
128
|
+
}
|
|
128
129
|
}
|
|
129
|
-
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
|
|
132
|
+
if (lastPlanningAwaiting && value.steps) {
|
|
133
|
+
value.steps?.map(step => {
|
|
134
|
+
if (step.type === 'planning') {
|
|
135
|
+
updatePlanningMessage(messages)
|
|
136
|
+
} else if (step.type === 'step') {
|
|
137
|
+
updateStepMessage(step, messages)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
134
141
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (step.type === 'planning') {
|
|
139
|
-
updatePlanningMessage(messages)
|
|
140
|
-
} else if (step.type === 'step') {
|
|
141
|
-
updateStepMessage(step, messages)
|
|
142
|
-
}
|
|
143
|
-
})
|
|
142
|
+
if (agent_info?.type === 'tool' && agent_info?.action !== 'awaiting_approval') {
|
|
143
|
+
updateToolStatus(agent_info, messages)
|
|
144
|
+
}
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
if (
|
|
147
|
-
|
|
147
|
+
if (chat.get('features').showSourcesInResponse) {
|
|
148
|
+
const mergedSources = [...(value.source ?? []), ...(value.cross_account_source ?? [])]
|
|
149
|
+
if (mergedSources.length !== (knowledgeSources?.length ?? 0)) {
|
|
150
|
+
knowledgeSources = genericSourcesToKnowledgeSources(mergedSources)
|
|
151
|
+
}
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
if (value.steps) {
|
|
@@ -193,7 +197,7 @@ export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResp
|
|
|
193
197
|
export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState, signal: AbortSignal) {
|
|
194
198
|
const { agentType, content, data } = entry.getValue()
|
|
195
199
|
if (agentType !== 'user') return
|
|
196
|
-
const context = buildConversationContext(chat, entry)
|
|
200
|
+
const context = buildConversationContext(chat, entry, buildPrompt(content, data))
|
|
197
201
|
chat.set('isLoading', true)
|
|
198
202
|
const untitled = chat.untitled
|
|
199
203
|
const messages = chat.getMessages()
|
|
@@ -202,12 +206,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
202
206
|
chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
|
|
203
207
|
chat.untitled = false
|
|
204
208
|
}
|
|
205
|
-
|
|
206
|
-
const stream =
|
|
207
|
-
context,
|
|
208
|
-
user_prompt: buildPrompt(content, data),
|
|
209
|
-
agent_version_number: chat.get('agent')?.agent_version_number,
|
|
210
|
-
})
|
|
209
|
+
|
|
210
|
+
const stream = genAiInferenceClient.sendChatMessage(context)
|
|
211
211
|
signal.addEventListener('abort', () => stream.cancel())
|
|
212
212
|
const botEntry = ChatEntry.createStreamedBotEntry()
|
|
213
213
|
// we add the chat entry and show the streaming if the streaming feature is enabled
|
|
@@ -220,14 +220,14 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
220
220
|
helperSendMessage(messages, value, chat, botEntry, knowledgeSources)
|
|
221
221
|
})
|
|
222
222
|
|
|
223
|
-
let finalValue: Partial<
|
|
223
|
+
let finalValue: Partial<ChatResponse> | undefined
|
|
224
224
|
try {
|
|
225
225
|
finalValue = await stream.getValue()
|
|
226
226
|
|
|
227
227
|
const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
|
|
228
228
|
if (lastPlanningAwaiting) {
|
|
229
229
|
const value = lastPlanningAwaiting.getValue()
|
|
230
|
-
value.content = finalValue.
|
|
230
|
+
value.content = finalValue.message || value.content
|
|
231
231
|
lastPlanningAwaiting.setValue(value)
|
|
232
232
|
}
|
|
233
233
|
// if the streaming feature is not enabled, we only add the chat entry once the streaming has finished
|
|
@@ -247,7 +247,7 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
247
247
|
})
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
if (finalValue?.
|
|
250
|
+
if (finalValue?.message) {
|
|
251
251
|
botEntry.setValue({
|
|
252
252
|
...createEntryValueFromChatResponse(finalValue, botEntry.getValue().knowledgeSources, chat.get('agent'), true),
|
|
253
253
|
hasPlanning: botEntry.getValue().hasPlanning,
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { Children, Fragment } from 'react'
|
|
6
6
|
import ReactMarkdown from 'react-markdown'
|
|
7
|
+
import rehypeKatex from 'rehype-katex'
|
|
7
8
|
import remarkGfm from 'remark-gfm'
|
|
9
|
+
import remarkMath from 'remark-math'
|
|
8
10
|
import { WithChildren } from '../types'
|
|
9
11
|
import { Code, Props as CodeProps } from './Code'
|
|
10
12
|
|
|
@@ -29,26 +31,43 @@ export const Markdown = (
|
|
|
29
31
|
onCopyCode,
|
|
30
32
|
children,
|
|
31
33
|
}: Props,
|
|
32
|
-
) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
) => {
|
|
35
|
+
|
|
36
|
+
// fix parsing LaTex: https://github.com/remarkjs/react-markdown/issues/785
|
|
37
|
+
const content = typeof children === 'string'
|
|
38
|
+
? children
|
|
39
|
+
.replace(/\\\\\[/g, '$$$$') // Replace '\\[' with '$$'
|
|
40
|
+
.replace(/\\\\\]/g, '$$$$') // Replace '\\]' with '$$'
|
|
41
|
+
.replace(/\\\\\(/g, '$$$$') // Replace '\\(' with '$$'
|
|
42
|
+
.replace(/\\\\\)/g, '$$$$') // Replace '\\)' with '$$'
|
|
43
|
+
.replace(/\\\[/g, '$$$$') // Replace '\[' with '$$'
|
|
44
|
+
.replace(/\\\]/g, '$$$$') // Replace '\]' with '$$'
|
|
45
|
+
.replace(/\\\(/g, '$$$$') // Replace '\(' with '$$'
|
|
46
|
+
.replace(/\\\)/g, '$$$$') // Replace '\)' with '$$';
|
|
47
|
+
: children
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<ReactMarkdown
|
|
52
|
+
className="markdown apply-citric"
|
|
53
|
+
remarkPlugins={[[remarkMath, { singleDollarTextMath: true }], remarkGfm]}
|
|
54
|
+
rehypePlugins={[rehypeKatex]}
|
|
55
|
+
components={{
|
|
56
|
+
a: props => <a target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'underline' }} {...props} />,
|
|
57
|
+
code: props =>
|
|
58
|
+
<Code
|
|
59
|
+
{...props}
|
|
60
|
+
onInsertCode={onInsertCode}
|
|
61
|
+
onNewFile={onNewFile}
|
|
62
|
+
onCopyCode={onCopyCode}
|
|
63
|
+
showActionBar
|
|
64
|
+
/>,
|
|
65
|
+
pre: ({ children }) => <>{children}</>,
|
|
66
|
+
p: ({ children }) => <p>{Children.map(children, renderP)}</p>,
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{content}
|
|
70
|
+
</ReactMarkdown>
|
|
71
|
+
</>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -42,7 +42,7 @@ interface ListProps<T> {
|
|
|
42
42
|
filter?: string,
|
|
43
43
|
visibility?: SectionVisibility,
|
|
44
44
|
selectorConfig: SelectorConfig<T>,
|
|
45
|
-
onSelect: (item: T) => void,
|
|
45
|
+
onSelect: (item: T, selectedVersion?: number) => void,
|
|
46
46
|
favorite?: Favorite<T>,
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -76,7 +76,7 @@ interface SelectorConfig<T> {
|
|
|
76
76
|
isEnabled: boolean,
|
|
77
77
|
sections: SectionVisibility[],
|
|
78
78
|
renderComponentItem: FC<T>,
|
|
79
|
-
onSelect: (item: T) => void,
|
|
79
|
+
onSelect: (item: T, selectedVersion?: number) => void,
|
|
80
80
|
/**
|
|
81
81
|
* A react hook or a simple function that returns the selectable items.
|
|
82
82
|
*
|
|
@@ -104,7 +104,7 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }:
|
|
|
104
104
|
<li>
|
|
105
105
|
<button
|
|
106
106
|
className="selector"
|
|
107
|
-
onClick={() => onSelect(item)}
|
|
107
|
+
onClick={() => onSelect(item, selectedVersion)}
|
|
108
108
|
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
|
|
109
109
|
onFocus={(e) => e.target.closest('li')?.classList.add('focus')}
|
|
110
110
|
onBlur={(e) => e.target.closest('li')?.classList.remove('focus')}
|
|
@@ -157,14 +157,14 @@ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, fa
|
|
|
157
157
|
)
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentProps<
|
|
160
|
+
const SelectorContent = <T, > ({ filter, onClose, selectorConfig, favorite }: ContentProps<T>) => {
|
|
161
161
|
const t = useTranslate(dictionary)
|
|
162
162
|
const ref = useRef<HTMLDivElement>(null)
|
|
163
163
|
const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
|
|
164
164
|
const isGroupResourcesByScope = useWidgetState('features')?.groupResourcesByScope
|
|
165
165
|
const { resourceName, icon, onSelect, sections } = selectorConfig
|
|
166
|
-
const onSelectItem = useCallback((
|
|
167
|
-
onSelect(
|
|
166
|
+
const onSelectItem = useCallback((item: any, selectedVersion?: number) => {
|
|
167
|
+
onSelect(item, selectedVersion)
|
|
168
168
|
onClose()
|
|
169
169
|
}, [])
|
|
170
170
|
|