@stack-spot/ai-chat-widget 3.0.0-beta.1 → 3.0.2-beta.1
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 +85 -19
- package/dist/app-metadata.json +6 -6
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +10 -3
- 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 +76 -15
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/layout.css +6 -0
- package/dist/state/ChatEntry.d.ts +1 -1
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +2 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/ChatState.d.ts +4 -0
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js.map +1 -1
- package/dist/utils/chat.d.ts.map +1 -1
- package/dist/utils/chat.js +1 -0
- package/dist/utils/chat.js.map +1 -1
- package/dist/utils/knowledge-source.d.ts +2 -2
- package/dist/utils/planning-tool.d.ts +3 -0
- package/dist/utils/planning-tool.d.ts.map +1 -1
- package/dist/utils/planning-tool.js +7 -0
- package/dist/utils/planning-tool.js.map +1 -1
- package/dist/views/Agents/styled.d.ts.map +1 -1
- package/dist/views/Agents/styled.js +1 -2
- package/dist/views/Agents/styled.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts +1 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +23 -6
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts +8 -3
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +67 -34
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/Chat/styled.d.ts.map +1 -1
- package/dist/views/Chat/styled.js +8 -12
- package/dist/views/Chat/styled.js.map +1 -1
- package/dist/views/MessageInput/ButtonBar.d.ts.map +1 -1
- package/dist/views/MessageInput/ButtonBar.js +2 -1
- package/dist/views/MessageInput/ButtonBar.js.map +1 -1
- package/dist/views/MessageInput/ModelSwitcher/index.d.ts +2 -0
- package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/index.js +25 -0
- package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.d.ts +30 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.d.ts.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.js +91 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.js.map +1 -0
- package/dist/views/MessageInput/SelectContent.js +1 -1
- package/dist/views/MessageInput/SelectContent.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
- package/dist/views/MessageInput/dictionary.js +6 -0
- package/dist/views/MessageInput/dictionary.js.map +1 -1
- package/dist/views/MessageInput/styled.d.ts +12 -0
- package/dist/views/MessageInput/styled.d.ts.map +1 -1
- package/dist/views/MessageInput/styled.js +35 -0
- package/dist/views/MessageInput/styled.js.map +1 -1
- package/dist/views/Resources.js +12 -5
- package/dist/views/Resources.js.map +1 -1
- package/package.json +4 -4
- package/src/app-metadata.json +6 -6
- package/src/chat-interceptors/quick-commands.ts +18 -7
- package/src/chat-interceptors/send-message.ts +82 -18
- package/src/layout.css +6 -0
- package/src/state/ChatEntry.ts +2 -1
- package/src/state/ChatState.ts +4 -0
- package/src/utils/chat.ts +1 -0
- package/src/utils/knowledge-source.ts +2 -2
- package/src/utils/planning-tool.ts +9 -0
- package/src/views/Agents/styled.ts +1 -2
- package/src/views/Chat/ChatMessage.tsx +63 -57
- package/src/views/Chat/StepsList.tsx +115 -72
- package/src/views/Chat/styled.ts +8 -12
- package/src/views/MessageInput/ButtonBar.tsx +2 -0
- package/src/views/MessageInput/ModelSwitcher/index.tsx +68 -0
- package/src/views/MessageInput/ModelSwitcher/utils.tsx +143 -0
- package/src/views/MessageInput/SelectContent.tsx +1 -1
- package/src/views/MessageInput/dictionary.ts +6 -0
- package/src/views/MessageInput/styled.ts +37 -0
- package/src/views/Resources.tsx +18 -10
package/src/app-metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"date": "
|
|
3
|
+
"version": "3.0.2-beta.1",
|
|
4
|
+
"date": "Mon Nov 10 2025 19:36:38 GMT+0000 (Coordinated Universal Time)",
|
|
5
5
|
"dependencies": [
|
|
6
6
|
{
|
|
7
7
|
"name": "@stack-spot/app-metadata",
|
|
@@ -109,19 +109,19 @@
|
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
"name": "@stack-spot/citric-icons",
|
|
112
|
-
"version": "0.2.
|
|
112
|
+
"version": "0.2.5"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "@stack-spot/citric-react",
|
|
116
|
-
"version": "0.36.0(@stack-spot/citric-icons@0.2.
|
|
116
|
+
"version": "0.36.0(@stack-spot/citric-icons@0.2.5)(@stack-spot/portal-theme@1.2.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
"name": "@stack-spot/portal-components",
|
|
120
|
-
"version": "2.
|
|
120
|
+
"version": "2.27.3(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.13.0(react@18.2.0))(@citric/ui@6.10.2(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.13.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.2.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
"name": "@stack-spot/portal-network",
|
|
124
|
-
"version": "0.
|
|
124
|
+
"version": "0.195.1(@stack-spot/auth@6.1.0)(@stack-spot/opa@2.5.0(@stack-spot/auth@6.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@tanstack/react-query@5.59.16(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"name": "@stack-spot/portal-theme",
|
|
@@ -67,6 +67,10 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
67
67
|
chat.set('isLoading', true)
|
|
68
68
|
CustomInputs.deleteCustomInputsFromChat(chat)
|
|
69
69
|
ctx.customInputs = { ...ctx.customInputs, ...inputsValues }
|
|
70
|
+
ctx.context.upload_ids = chat.getMessages()
|
|
71
|
+
.flatMap(message =>
|
|
72
|
+
(message.getValue().upload || []).map(upload => upload.id),
|
|
73
|
+
)
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -101,9 +105,9 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
101
105
|
return runStepsRecursively(stepIndex, progress, ctx, iteration)
|
|
102
106
|
}
|
|
103
107
|
const nextStepIndex = steps?.findIndex((step) => step.slug === next_step_slug)
|
|
104
|
-
|
|
108
|
+
|
|
105
109
|
if (isNil(nextStepIndex) || nextStepIndex === -1) return
|
|
106
|
-
|
|
110
|
+
|
|
107
111
|
return runStepsRecursively(nextStepIndex, progress, ctx, iteration)
|
|
108
112
|
}
|
|
109
113
|
catch (error: any) {
|
|
@@ -173,8 +177,8 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
const stream = aiClient.streamLlmStepOfQuickCommand(
|
|
176
|
-
slug,
|
|
177
|
-
step.slug,
|
|
180
|
+
slug,
|
|
181
|
+
step.slug,
|
|
178
182
|
{
|
|
179
183
|
input_data: code,
|
|
180
184
|
context: stepContext,
|
|
@@ -241,9 +245,16 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
241
245
|
await (currentStep.type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
|
|
242
246
|
|
|
243
247
|
let nextIndex = currentIndex + 1
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
|
|
249
|
+
let nextStepSlug = currentStep.next_step_slug
|
|
250
|
+
const stepResult = ctx.resultMap[currentStep.slug]
|
|
251
|
+
if (stepResult && typeof stepResult !== 'string' && 'answer_status' in stepResult && !!stepResult.answer_status?.next_step_slug) {
|
|
252
|
+
nextStepSlug = stepResult.answer_status.next_step_slug
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (nextStepSlug) {
|
|
256
|
+
nextIndex =nextStepSlug === 'end' ?
|
|
257
|
+
qc.steps.length : qc.steps?.findIndex((step) => step.slug === nextStepSlug)
|
|
247
258
|
}
|
|
248
259
|
await runStepsRecursively(nextIndex, progress, ctx, iteration)
|
|
249
260
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AgentInfo, aiClient, ChatResponseWithSteps, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
|
|
1
|
+
import { AgentInfo, aiClient, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
|
|
2
2
|
import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
|
|
3
|
+
import { findLast } from 'lodash'
|
|
3
4
|
import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
|
|
4
5
|
import { ChatState } from '../state/ChatState'
|
|
5
6
|
import { LabeledWithImage } from '../state/types'
|
|
@@ -23,7 +24,7 @@ function createEntryValueFromChatResponse(
|
|
|
23
24
|
agent: LabeledWithImage | undefined,
|
|
24
25
|
includeDate = false,
|
|
25
26
|
): TextChatEntry {
|
|
26
|
-
|
|
27
|
+
const entry = {
|
|
27
28
|
agentType: 'bot',
|
|
28
29
|
type: 'md',
|
|
29
30
|
content: response.answer ?? '',
|
|
@@ -34,6 +35,7 @@ function createEntryValueFromChatResponse(
|
|
|
34
35
|
steps: response.steps,
|
|
35
36
|
tools: response.tools,
|
|
36
37
|
}
|
|
38
|
+
return entry as TextChatEntry
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function buildPrompt(content: string, data?: any) {
|
|
@@ -63,9 +65,10 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
63
65
|
chat.untitled = false
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
//Verify if the last planning in the messages has status awaiting_approval
|
|
67
68
|
const messages = chat.getMessages()
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
//Verify if the last planning in the messages has status awaiting_approval
|
|
71
|
+
const getLastPlanningAwaiting = () => findLast(messages, item => {
|
|
69
72
|
const steps = item.getValue().steps
|
|
70
73
|
if (steps) {
|
|
71
74
|
const hasPlanning = steps.find((step) => step.type === 'planning')
|
|
@@ -84,6 +87,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
84
87
|
let knowledgeSources: KnowledgeSource[] | undefined
|
|
85
88
|
|
|
86
89
|
const updatePlanningMessage = () => {
|
|
90
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
91
|
+
|
|
87
92
|
if (lastPlanningAwaiting) {
|
|
88
93
|
const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
|
|
89
94
|
const originalItemValue = originalItem?.getValue()
|
|
@@ -96,6 +101,21 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
const updateStepMessage = (step: ChatStep) => {
|
|
105
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
106
|
+
|
|
107
|
+
if (lastPlanningAwaiting) {
|
|
108
|
+
const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
|
|
109
|
+
const originalItemValue = originalItem?.getValue()
|
|
110
|
+
originalItemValue?.steps?.filter((messageStep) => {
|
|
111
|
+
if (messageStep.id === step.id) {
|
|
112
|
+
messageStep = { ...step }
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
const updateToolStatus = (agentInfo: AgentInfo) => {
|
|
100
120
|
const executionId = agentInfo.id
|
|
101
121
|
if (executionId) {
|
|
@@ -104,8 +124,9 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
104
124
|
const originalItem = messages.find((message) => `${message.id}` === messageId)
|
|
105
125
|
const originalItemValue = originalItem?.getValue()
|
|
106
126
|
let update = false
|
|
107
|
-
const status = agentInfo.action === 'start' ? '
|
|
108
|
-
const step = originalItemValue?.steps
|
|
127
|
+
const status = agentInfo.action === 'start' ? 'running' : 'success'
|
|
128
|
+
const step = originalItemValue?.steps
|
|
129
|
+
?.find(step => step.type === 'step' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
|
|
109
130
|
if (step && step.status !== status) {
|
|
110
131
|
step.status = status
|
|
111
132
|
update = true
|
|
@@ -115,14 +136,16 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
//Updates message with type tool which contains the actually tool steps
|
|
139
|
+
//We only want to show tools banner when they are awaiting_approval, by removing the step
|
|
140
|
+
// we avoid the entire bot message to be visible
|
|
118
141
|
const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
|
|
119
142
|
const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
|
|
120
143
|
const toolOriginalItemValue = toolOriginalItem?.getValue()
|
|
121
|
-
const toolStep = toolOriginalItemValue?.steps?.find(step =>
|
|
122
|
-
step.type === 'tool' && step.attempts?.[0]
|
|
144
|
+
const toolStep = toolOriginalItemValue?.steps?.find(step =>
|
|
145
|
+
step.type === 'tool' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
|
|
123
146
|
update = false
|
|
124
|
-
if (toolStep && toolStep.status !== status) {
|
|
125
|
-
|
|
147
|
+
if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
|
|
148
|
+
toolOriginalItemValue.steps = undefined
|
|
126
149
|
update = true
|
|
127
150
|
}
|
|
128
151
|
if (update) {
|
|
@@ -140,34 +163,75 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
140
163
|
}
|
|
141
164
|
}
|
|
142
165
|
|
|
166
|
+
if (value.agent_info?.type === 'chat' && value.agent_info?.action === 'end') {
|
|
167
|
+
//When an error happens, the step can still be running, so we enforce the error
|
|
168
|
+
const stepRunning = findLast(value.steps, (item) => item.status === 'running')
|
|
169
|
+
if (stepRunning?.status) {
|
|
170
|
+
stepRunning.status = 'error'
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
143
174
|
if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
|
|
144
175
|
knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
|
|
145
176
|
}
|
|
146
177
|
|
|
147
|
-
|
|
148
|
-
// we do not not want to add it again.
|
|
178
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
149
179
|
if (lastPlanningAwaiting && value.steps) {
|
|
150
|
-
|
|
151
|
-
value.steps = value.steps?.filter(step => {
|
|
180
|
+
value.steps?.map(step => {
|
|
152
181
|
if (step.type === 'planning') {
|
|
153
182
|
updatePlanningMessage()
|
|
183
|
+
} else if (step.type === 'step') {
|
|
184
|
+
updateStepMessage(step)
|
|
154
185
|
}
|
|
155
|
-
|
|
156
|
-
return hasPlanningAwaiting ? true : (step.type !== 'planning' && step.type !== 'step' &&
|
|
157
|
-
(step.type !== 'answer' || (step.type === 'answer' && step.status === 'pending')))
|
|
158
186
|
})
|
|
159
187
|
}
|
|
160
188
|
|
|
161
|
-
if (value.agent_info?.type === 'tool') {
|
|
189
|
+
if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
|
|
162
190
|
updateToolStatus(value.agent_info)
|
|
163
191
|
}
|
|
164
192
|
|
|
193
|
+
if (value.steps) {
|
|
194
|
+
const tool = findLast(value.steps, (item) => item.type === 'tool')
|
|
195
|
+
if (tool && tool.status === 'running') {
|
|
196
|
+
const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
|
|
197
|
+
const originalItem = messages.find((message) => `${message.id}` === messageId)
|
|
198
|
+
const originalItemValue = originalItem?.getValue()
|
|
199
|
+
let update = false
|
|
200
|
+
const step = originalItemValue?.steps?.find(step => step.type === 'step')
|
|
201
|
+
if (step && step.attempts?.[0]?.tools?.[0]?.executionId === tool.id) {
|
|
202
|
+
step.attempts?.forEach((attempt, i) => {
|
|
203
|
+
const newAttempt = tool.attempts?.[i]
|
|
204
|
+
if (!newAttempt) return
|
|
205
|
+
attempt.tools = attempt.tools?.map((origTool, j) => {
|
|
206
|
+
const newTool = newAttempt.tools?.[j]
|
|
207
|
+
if (!newTool || origTool?.executionId !== newTool?.executionId) return origTool
|
|
208
|
+
update = true
|
|
209
|
+
return { ...origTool, ...newTool }
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
if (step.attempts.length < tool.attempts.length) {
|
|
213
|
+
step.attempts.push(tool.attempts[tool.attempts.length - 1])
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (update) {
|
|
217
|
+
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
165
222
|
botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
|
|
166
223
|
})
|
|
167
224
|
|
|
168
225
|
let finalValue: Partial<ChatResponse3> | undefined
|
|
169
226
|
try {
|
|
170
227
|
finalValue = await stream.getValue()
|
|
228
|
+
|
|
229
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
230
|
+
if (lastPlanningAwaiting) {
|
|
231
|
+
const value = lastPlanningAwaiting.getValue()
|
|
232
|
+
value.content = finalValue.answer || value.content
|
|
233
|
+
lastPlanningAwaiting.setValue(value)
|
|
234
|
+
}
|
|
171
235
|
// if the streaming feature is not enabled, we only add the chat entry once the streaming has finished
|
|
172
236
|
if (!chat.get('features').streaming) {
|
|
173
237
|
chat.pushMessage(botEntry)
|
package/src/layout.css
CHANGED
package/src/state/ChatEntry.ts
CHANGED
|
@@ -205,13 +205,14 @@ export class ChatEntry {
|
|
|
205
205
|
* @param hiddenContent the message's content.
|
|
206
206
|
* @returns a new ChatEntry.
|
|
207
207
|
*/
|
|
208
|
-
static createUserEntry(content: string, isMd = false, fieldName?: string, hiddenContent?: string[]) {
|
|
208
|
+
static createUserEntry(content: string, isMd = false, fieldName?: string, hiddenContent?: string[], data?: any) {
|
|
209
209
|
return new ChatEntry({
|
|
210
210
|
agentType: 'user',
|
|
211
211
|
type: isMd ? 'md' : 'text',
|
|
212
212
|
content,
|
|
213
213
|
name: fieldName,
|
|
214
214
|
hiddenContent,
|
|
215
|
+
data,
|
|
215
216
|
updated: new Date().toISOString(),
|
|
216
217
|
})
|
|
217
218
|
}
|
package/src/state/ChatState.ts
CHANGED
|
@@ -59,6 +59,10 @@ export interface ChatPropertiesWithOptionalFeatures {
|
|
|
59
59
|
* If a feature is marked as false, it's disabled, otherwise it's enabled.
|
|
60
60
|
*/
|
|
61
61
|
features?: Partial<ChatFeatures>,
|
|
62
|
+
/**
|
|
63
|
+
* The current LLM (Large Language Model) being used for this chat.
|
|
64
|
+
*/
|
|
65
|
+
selected_model_id?: string,
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
export interface ChatProperties extends ChatPropertiesWithOptionalFeatures {
|
package/src/utils/chat.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocumentResponse, SourceKnowledgeSource,
|
|
1
|
+
import { DocumentResponse, SourceKnowledgeSource, SourceProjectFile3, SourceStackAi } from '@stack-spot/portal-network/api/ai'
|
|
2
2
|
import { KnowledgeSource } from '../state/ChatEntry'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -60,7 +60,7 @@ export function extractCodeFromKSDocument(document: DocumentResponse): { languag
|
|
|
60
60
|
* @returns a list of knowledge sources in the format expected by the chat.
|
|
61
61
|
*/
|
|
62
62
|
export function genericSourcesToKnowledgeSources(
|
|
63
|
-
sources: (SourceStackAi | SourceKnowledgeSource |
|
|
63
|
+
sources: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile3)[] | undefined,
|
|
64
64
|
): KnowledgeSource[] | undefined {
|
|
65
65
|
return sources?.filter(s => s.type === 'knowledge_source').map(ks => {
|
|
66
66
|
const { document_id: documentId, document_score: documentScore, name, slug } = ks as SourceKnowledgeSource
|
|
@@ -2,6 +2,7 @@ class PlanningToolDictionaryHelper {
|
|
|
2
2
|
static instance: PlanningToolDictionaryHelper | undefined
|
|
3
3
|
private toolExecutionIdPlanningStep: Record<string, string> = {}
|
|
4
4
|
private toolExecutionIdToolStep: Record<string, string> = {}
|
|
5
|
+
private stepId: Record<string, number> = {}
|
|
5
6
|
|
|
6
7
|
private constructor() {
|
|
7
8
|
PlanningToolDictionaryHelper.instance = this
|
|
@@ -18,6 +19,14 @@ class PlanningToolDictionaryHelper {
|
|
|
18
19
|
setMessageIdToolStepToolExecutionId(messageId: string, toolExecutionId: string){
|
|
19
20
|
this.toolExecutionIdToolStep[toolExecutionId] = messageId
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
setMessageIdForStepId(messageId: number, stepId: string){
|
|
24
|
+
this.stepId[stepId] = messageId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getMessageIdFromStepId(stepId: string){
|
|
28
|
+
return this.stepId[stepId]
|
|
29
|
+
}
|
|
21
30
|
|
|
22
31
|
getMessageIdPlanningStepFromToolExecutionId(toolExecutionId: string){
|
|
23
32
|
return this.toolExecutionIdPlanningStep[toolExecutionId]
|
|
@@ -9,7 +9,7 @@ import { PhoneInput } from 'react-international-phone'
|
|
|
9
9
|
import 'react-international-phone/style.css'
|
|
10
10
|
import { FileDescription } from '../../components/FileDescription'
|
|
11
11
|
import { Markdown } from '../../components/Markdown'
|
|
12
|
-
import { useChatEntry, useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
|
|
12
|
+
import { useChatEntry, useChatMessages, useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
|
|
13
13
|
import { useMidnightUpdateView } from '../../hooks/midnight-update-view'
|
|
14
14
|
import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
|
|
15
15
|
import { useDateFormatter } from '../../utils/date'
|
|
@@ -17,7 +17,7 @@ import { toolById } from '../../utils/tools'
|
|
|
17
17
|
import { AgentInfo } from './AgentInfo'
|
|
18
18
|
import { useChatScrollToBottomEffect } from './chat-scroll'
|
|
19
19
|
import { onCopyAll, onCopyCode, onLikeOrDislike } from './events'
|
|
20
|
-
import { StepsList, StepsPlaceholder } from './StepsList'
|
|
20
|
+
import { StepsList, StepsPlaceholder, ViewToolsDetails } from './StepsList'
|
|
21
21
|
|
|
22
22
|
export interface CustomRenderResult {
|
|
23
23
|
/**
|
|
@@ -69,12 +69,12 @@ interface Props extends CustomMessage {
|
|
|
69
69
|
isLast: boolean,
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
interface RenderInputsEntryProps {
|
|
73
|
-
isLast: boolean,
|
|
74
|
-
entry: TextChatEntry,
|
|
75
|
-
value: string[],
|
|
72
|
+
interface RenderInputsEntryProps {
|
|
73
|
+
isLast: boolean,
|
|
74
|
+
entry: TextChatEntry,
|
|
75
|
+
value: string[],
|
|
76
76
|
setValue: Dispatch<React.SetStateAction<string[]>>,
|
|
77
|
-
labels: string[],
|
|
77
|
+
labels: string[],
|
|
78
78
|
setLabels: Dispatch<React.SetStateAction<string[]>>,
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -113,7 +113,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
113
113
|
<Input name={entry.name} onChange={v => setValue([v])} required style={{ height: '30px', width: '33%' }} />}
|
|
114
114
|
</Row>
|
|
115
115
|
)}
|
|
116
|
-
/>
|
|
116
|
+
/>
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (entry.type === 'button-list') {
|
|
@@ -145,7 +145,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
145
145
|
renderLabel={o => (
|
|
146
146
|
<Row gap={3}>
|
|
147
147
|
<Text>{o.label}</Text>
|
|
148
|
-
{o.hasInput && o.value && labels.findIndex((label) => label === o.value)!== -1 &&
|
|
148
|
+
{o.hasInput && o.value && labels.findIndex((label) => label === o.value) !== -1 &&
|
|
149
149
|
<div style={{ width: '33%' }}>
|
|
150
150
|
<Input
|
|
151
151
|
name={entry.name}
|
|
@@ -158,7 +158,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
158
158
|
const newValue = [...value]
|
|
159
159
|
newValue[customIndex] = v
|
|
160
160
|
setValue(newValue)
|
|
161
|
-
}
|
|
161
|
+
}
|
|
162
162
|
}}
|
|
163
163
|
required={true}
|
|
164
164
|
style={{ height: '30px' }}
|
|
@@ -220,6 +220,19 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
220
220
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
221
|
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
222
|
|
|
223
|
+
// when we have a steps but we are not showing any content of the step
|
|
224
|
+
// (because it is a tool and the user has already answered the question)
|
|
225
|
+
// we do not want to show an avatar with empty content, so we hide the entire message
|
|
226
|
+
const toolsStep = entry.steps?.find(s => s.type === 'tool')
|
|
227
|
+
const messages = useChatMessages(chat.id)
|
|
228
|
+
const userHasAlreadyAnswered = useMemo(() => {
|
|
229
|
+
const messageIndex = messages.findIndex((messageItem) => messageItem.id === message.id)
|
|
230
|
+
if (messages.length - 1 === messageIndex) return false
|
|
231
|
+
const nextMessage = messages[messageIndex + 1].getValue()
|
|
232
|
+
return nextMessage.agentType === 'user'
|
|
233
|
+
}, [messages, messages.length])
|
|
234
|
+
const isMessageHidden = toolsStep && userHasAlreadyAnswered
|
|
235
|
+
|
|
223
236
|
useChatScrollToBottomEffect(ref, [entry])
|
|
224
237
|
useMidnightUpdateView()
|
|
225
238
|
|
|
@@ -264,7 +277,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
264
277
|
}
|
|
265
278
|
}
|
|
266
279
|
|
|
267
|
-
const renderActions = useCallback(()=> <> {entry.actions?.length && (
|
|
280
|
+
const renderActions = useCallback(() => <> {entry.actions?.length && (
|
|
268
281
|
<div className="actions">
|
|
269
282
|
{entry.actions.map(
|
|
270
283
|
(a, index) => (<>
|
|
@@ -344,7 +357,8 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
344
357
|
widget.set('panel', 'resources')
|
|
345
358
|
}
|
|
346
359
|
|
|
347
|
-
return (entry.content || entry.error || !!entry.steps?.length ||
|
|
360
|
+
return (entry.content || entry.error || !!entry.steps?.length ||
|
|
361
|
+
entry.upload?.length) && (!isMessageHidden || !toolsStep || isPlanning) && (
|
|
348
362
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
349
363
|
<div className="chat-message-container"
|
|
350
364
|
onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
|
|
@@ -357,32 +371,19 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
357
371
|
{!!entry.badges?.length && <div className="badges">
|
|
358
372
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
359
373
|
</div>}
|
|
360
|
-
|
|
361
|
-
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
374
|
+
|
|
375
|
+
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
376
|
+
userHasAlreadyAnswered={userHasAlreadyAnswered} />}
|
|
362
377
|
|
|
363
378
|
{renderContent()}
|
|
364
|
-
|
|
379
|
+
|
|
365
380
|
</div>
|
|
366
381
|
)}
|
|
367
|
-
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />
|
|
368
|
-
|
|
382
|
+
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />}
|
|
383
|
+
|
|
369
384
|
{entry.error && <Alert type="error">{entry.error}</Alert>}
|
|
370
385
|
</div>
|
|
371
386
|
{afterMessage && createElement(afterMessage, { message })}
|
|
372
|
-
{/* {!!entry.tools?.length && <StackedBadge
|
|
373
|
-
aria-label={t.openToolsPanel}
|
|
374
|
-
title={t.openToolsPanel}
|
|
375
|
-
tabIndex={0}
|
|
376
|
-
role="button"
|
|
377
|
-
className="tools-badge"
|
|
378
|
-
label={t.tools}
|
|
379
|
-
images={entry.tools.slice(0, 3).map((id) => {
|
|
380
|
-
const tool = toolById(id, toolKits)
|
|
381
|
-
return { key: id, name: tool?.name || id, icon: <Icon icon="Cog" />, url: tool?.image }
|
|
382
|
-
})}
|
|
383
|
-
onClick={openToolsPanel}
|
|
384
|
-
style={{ marginTop: '12px', width: 'fit-content' }}
|
|
385
|
-
/>} */}
|
|
386
387
|
|
|
387
388
|
{!!entry.knowledgeSources?.length && <div className="ks-box">
|
|
388
389
|
<Text appearance="microtext1" color="light.700">Knowledge Sources:</Text>
|
|
@@ -393,35 +394,40 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
393
394
|
))}</ul>
|
|
394
395
|
</div>}
|
|
395
396
|
|
|
396
|
-
{(!!agentsTools?.length || !!entry.tools?.length) &&
|
|
397
|
-
<
|
|
398
|
-
{
|
|
399
|
-
|
|
400
|
-
<
|
|
401
|
-
src={agent.avatar ?? undefined}
|
|
402
|
-
fallback={<Icon icon="Agent" />}
|
|
403
|
-
alt={agent.name}
|
|
404
|
-
aria-label={agent.name}
|
|
405
|
-
title={agent.name}
|
|
406
|
-
/>
|
|
407
|
-
</ImageBox>
|
|
408
|
-
))}
|
|
409
|
-
{entry.tools?.map((id) => {
|
|
410
|
-
const tool = toolById(id, toolKits)
|
|
411
|
-
return (
|
|
412
|
-
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
397
|
+
{(!!agentsTools?.length || !!entry.tools?.length) &&
|
|
398
|
+
<div className="tools-box">
|
|
399
|
+
<Button appearance="none" onClick={openResourcesPanel} aria-label={t.openResourcesPanel}>
|
|
400
|
+
{agentsTools?.map((agent) => (
|
|
401
|
+
<ImageBox key={agent.id} className="agent-info-avatar-resource">
|
|
413
402
|
<ImageWithFallback
|
|
414
|
-
src={
|
|
415
|
-
fallback={<Icon icon="
|
|
416
|
-
alt={
|
|
417
|
-
aria-label={
|
|
418
|
-
title={
|
|
403
|
+
src={agent.avatar ?? undefined}
|
|
404
|
+
fallback={<Icon icon="Agent" />}
|
|
405
|
+
alt={agent.name}
|
|
406
|
+
aria-label={agent.name}
|
|
407
|
+
title={agent.name}
|
|
419
408
|
/>
|
|
420
409
|
</ImageBox>
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
410
|
+
))}
|
|
411
|
+
{entry?.tools?.map((id) => {
|
|
412
|
+
if (agentsTools?.some((agent) => agent.id === id)) return null
|
|
413
|
+
const tool = toolById(id, toolKits)
|
|
414
|
+
return (
|
|
415
|
+
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
416
|
+
<ImageWithFallback
|
|
417
|
+
src={tool?.image}
|
|
418
|
+
fallback={<Icon icon="Cog" />}
|
|
419
|
+
alt={tool?.name}
|
|
420
|
+
aria-label={tool?.name}
|
|
421
|
+
title={tool?.name}
|
|
422
|
+
/>
|
|
423
|
+
</ImageBox>
|
|
424
|
+
)
|
|
425
|
+
})}
|
|
426
|
+
</Button>
|
|
427
|
+
<ViewToolsDetails chatId={chat.id} />
|
|
428
|
+
</div>
|
|
429
|
+
}
|
|
430
|
+
|
|
425
431
|
{shouldShowFooter && <div className="message-footer">
|
|
426
432
|
{entry.agentType === 'bot' && !entry.error && <div className="message-actions">
|
|
427
433
|
{entry.type === 'md' && (
|