@stack-spot/ai-chat-widget 2.3.0 → 2.3.1-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 +109 -8
- 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 +8 -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 +23 -8
- package/dist/chat-interceptors/send-message.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/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +1 -1
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +9 -8
- package/dist/views/Chat/StepsList.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/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.map +1 -1
- package/package.json +4 -4
- package/src/app-metadata.json +6 -6
- package/src/chat-interceptors/quick-commands.ts +10 -3
- package/src/chat-interceptors/send-message.ts +31 -12
- package/src/state/ChatState.ts +4 -0
- package/src/utils/chat.ts +1 -0
- package/src/utils/knowledge-source.ts +2 -2
- package/src/views/Chat/ChatMessage.tsx +23 -22
- package/src/views/Chat/StepsList.tsx +13 -10
- package/src/views/MessageInput/ButtonBar.tsx +2 -0
- package/src/views/MessageInput/ModelSwitcher/index.tsx +67 -0
- package/src/views/MessageInput/ModelSwitcher/utils.tsx +143 -0
- package/src/views/MessageInput/dictionary.ts +6 -0
- package/src/views/MessageInput/styled.ts +37 -0
- package/src/views/Resources.tsx +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
13
|
"@stack-spot/citric-react": "^0.36.0",
|
|
14
|
-
"@stack-spot/citric-icons": "^0.2.
|
|
14
|
+
"@stack-spot/citric-icons": "^0.2.5",
|
|
15
15
|
"@stack-spot/portal-theme": "^1.2.1",
|
|
16
16
|
"@citric/core": "^6.4.0",
|
|
17
|
-
"@stack-spot/portal-components": "^2.
|
|
17
|
+
"@stack-spot/portal-components": "^2.27.3",
|
|
18
18
|
"@citric/icons": "^5.13.0",
|
|
19
|
-
"@stack-spot/portal-network": "0.
|
|
19
|
+
"@stack-spot/portal-network": "0.190.0-beta.1",
|
|
20
20
|
"@citric/ui": "^6.10.2",
|
|
21
21
|
"@stack-spot/portal-translate": "^2.1.0",
|
|
22
22
|
"lodash": "^4.17.0",
|
package/src/app-metadata.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/ai-chat-widget",
|
|
3
|
-
"version": "2.3.
|
|
4
|
-
"date": "
|
|
3
|
+
"version": "2.3.1-beta.1",
|
|
4
|
+
"date": "Thu Nov 06 2025 13:51:17 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.190.0-beta.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",
|
|
@@ -245,9 +245,16 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
|
|
|
245
245
|
await (currentStep.type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
|
|
246
246
|
|
|
247
247
|
let nextIndex = currentIndex + 1
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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)
|
|
251
258
|
}
|
|
252
259
|
await runStepsRecursively(nextIndex, progress, ctx, iteration)
|
|
253
260
|
}
|
|
@@ -24,7 +24,7 @@ function createEntryValueFromChatResponse(
|
|
|
24
24
|
agent: LabeledWithImage | undefined,
|
|
25
25
|
includeDate = false,
|
|
26
26
|
): TextChatEntry {
|
|
27
|
-
const entry= {
|
|
27
|
+
const entry = {
|
|
28
28
|
agentType: 'bot',
|
|
29
29
|
type: 'md',
|
|
30
30
|
content: response.answer ?? '',
|
|
@@ -65,9 +65,10 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
65
65
|
chat.untitled = false
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
//Verify if the last planning in the messages has status awaiting_approval
|
|
69
68
|
const messages = chat.getMessages()
|
|
70
|
-
|
|
69
|
+
|
|
70
|
+
//Verify if the last planning in the messages has status awaiting_approval
|
|
71
|
+
const getLastPlanningAwaiting = () => findLast(messages, item => {
|
|
71
72
|
const steps = item.getValue().steps
|
|
72
73
|
if (steps) {
|
|
73
74
|
const hasPlanning = steps.find((step) => step.type === 'planning')
|
|
@@ -86,6 +87,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
86
87
|
let knowledgeSources: KnowledgeSource[] | undefined
|
|
87
88
|
|
|
88
89
|
const updatePlanningMessage = () => {
|
|
90
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
91
|
+
|
|
89
92
|
if (lastPlanningAwaiting) {
|
|
90
93
|
const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
|
|
91
94
|
const originalItemValue = originalItem?.getValue()
|
|
@@ -97,8 +100,10 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
97
100
|
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
|
-
|
|
103
|
+
|
|
101
104
|
const updateStepMessage = (step: ChatStep) => {
|
|
105
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
106
|
+
|
|
102
107
|
if (lastPlanningAwaiting) {
|
|
103
108
|
const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
|
|
104
109
|
const originalItemValue = originalItem?.getValue()
|
|
@@ -120,7 +125,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
120
125
|
const originalItemValue = originalItem?.getValue()
|
|
121
126
|
let update = false
|
|
122
127
|
const status = agentInfo.action === 'start' ? 'running' : 'success'
|
|
123
|
-
const step = originalItemValue?.steps
|
|
128
|
+
const step = originalItemValue?.steps
|
|
129
|
+
?.find(step => step.type === 'step' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
|
|
124
130
|
if (step && step.status !== status) {
|
|
125
131
|
step.status = status
|
|
126
132
|
update = true
|
|
@@ -135,8 +141,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
135
141
|
const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
|
|
136
142
|
const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
|
|
137
143
|
const toolOriginalItemValue = toolOriginalItem?.getValue()
|
|
138
|
-
const toolStep = toolOriginalItemValue?.steps?.find(step =>
|
|
139
|
-
step.type === 'tool' && step.attempts?.[0]
|
|
144
|
+
const toolStep = toolOriginalItemValue?.steps?.find(step =>
|
|
145
|
+
step.type === 'tool' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
|
|
140
146
|
update = false
|
|
141
147
|
if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
|
|
142
148
|
toolOriginalItemValue.steps = undefined
|
|
@@ -157,10 +163,19 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
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
|
+
|
|
160
174
|
if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
|
|
161
175
|
knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
|
|
162
176
|
}
|
|
163
177
|
|
|
178
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
164
179
|
if (lastPlanningAwaiting && value.steps) {
|
|
165
180
|
value.steps?.map(step => {
|
|
166
181
|
if (step.type === 'planning') {
|
|
@@ -177,24 +192,26 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
177
192
|
|
|
178
193
|
if (value.steps) {
|
|
179
194
|
const tool = findLast(value.steps, (item) => item.type === 'tool')
|
|
180
|
-
|
|
181
195
|
if (tool && tool.status === 'running') {
|
|
182
196
|
const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
|
|
183
197
|
const originalItem = messages.find((message) => `${message.id}` === messageId)
|
|
184
198
|
const originalItemValue = originalItem?.getValue()
|
|
185
199
|
let update = false
|
|
186
200
|
const step = originalItemValue?.steps?.find(step => step.type === 'step')
|
|
187
|
-
if (step && step.attempts?.[0]
|
|
188
|
-
step.attempts?.
|
|
201
|
+
if (step && step.attempts?.[0]?.tools?.[0]?.executionId === tool.id) {
|
|
202
|
+
step.attempts?.forEach((attempt, i) => {
|
|
189
203
|
const newAttempt = tool.attempts?.[i]
|
|
190
204
|
if (!newAttempt) return
|
|
191
|
-
attempt.tools?.map((origTool, j) => {
|
|
205
|
+
attempt.tools = attempt.tools?.map((origTool, j) => {
|
|
192
206
|
const newTool = newAttempt.tools?.[j]
|
|
193
|
-
if (!newTool || origTool
|
|
207
|
+
if (!newTool || origTool?.executionId !== newTool?.executionId) return origTool
|
|
194
208
|
update = true
|
|
195
209
|
return { ...origTool, ...newTool }
|
|
196
210
|
})
|
|
197
211
|
})
|
|
212
|
+
if (step.attempts.length < tool.attempts.length) {
|
|
213
|
+
step.attempts.push(tool.attempts[tool.attempts.length - 1])
|
|
214
|
+
}
|
|
198
215
|
}
|
|
199
216
|
if (update) {
|
|
200
217
|
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
@@ -208,6 +225,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
208
225
|
let finalValue: Partial<ChatResponse3> | undefined
|
|
209
226
|
try {
|
|
210
227
|
finalValue = await stream.getValue()
|
|
228
|
+
|
|
229
|
+
const lastPlanningAwaiting = getLastPlanningAwaiting()
|
|
211
230
|
if (lastPlanningAwaiting) {
|
|
212
231
|
const value = lastPlanningAwaiting.getValue()
|
|
213
232
|
value.content = finalValue.answer || value.content
|
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
|
|
@@ -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' }}
|
|
@@ -219,7 +219,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
219
219
|
const [copied, setCopied] = useState(false)
|
|
220
220
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
221
|
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
// when we have a steps but we are not showing any content of the step
|
|
224
224
|
// (because it is a tool and the user has already answered the question)
|
|
225
225
|
// we do not want to show an avatar with empty content, so we hide the entire message
|
|
@@ -227,12 +227,12 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
227
227
|
const messages = useChatMessages(chat.id)
|
|
228
228
|
const userHasAlreadyAnswered = useMemo(() => {
|
|
229
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()
|
|
230
|
+
if (messages.length - 1 === messageIndex) return false
|
|
231
|
+
const nextMessage = messages[messageIndex + 1].getValue()
|
|
232
232
|
return nextMessage.agentType === 'user'
|
|
233
233
|
}, [messages, messages.length])
|
|
234
234
|
const isMessageHidden = toolsStep && userHasAlreadyAnswered
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
useChatScrollToBottomEffect(ref, [entry])
|
|
237
237
|
useMidnightUpdateView()
|
|
238
238
|
|
|
@@ -277,7 +277,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const renderActions = useCallback(()=> <> {entry.actions?.length && (
|
|
280
|
+
const renderActions = useCallback(() => <> {entry.actions?.length && (
|
|
281
281
|
<div className="actions">
|
|
282
282
|
{entry.actions.map(
|
|
283
283
|
(a, index) => (<>
|
|
@@ -357,7 +357,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
357
357
|
widget.set('panel', 'resources')
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
return (entry.content || entry.error || !!entry.steps?.length
|
|
360
|
+
return (entry.content || entry.error || !!entry.steps?.length ||
|
|
361
361
|
entry.upload?.length) && (!isMessageHidden || !toolsStep || isPlanning) && (
|
|
362
362
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
363
363
|
<div className="chat-message-container"
|
|
@@ -371,16 +371,16 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
371
371
|
{!!entry.badges?.length && <div className="badges">
|
|
372
372
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
373
373
|
</div>}
|
|
374
|
-
|
|
375
|
-
{!!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
376
|
userHasAlreadyAnswered={userHasAlreadyAnswered} />}
|
|
377
377
|
|
|
378
378
|
{renderContent()}
|
|
379
|
-
|
|
379
|
+
|
|
380
380
|
</div>
|
|
381
381
|
)}
|
|
382
|
-
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />
|
|
383
|
-
|
|
382
|
+
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />}
|
|
383
|
+
|
|
384
384
|
{entry.error && <Alert type="error">{entry.error}</Alert>}
|
|
385
385
|
</div>
|
|
386
386
|
{afterMessage && createElement(afterMessage, { message })}
|
|
@@ -421,7 +421,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
421
421
|
/>
|
|
422
422
|
</ImageBox>
|
|
423
423
|
))}
|
|
424
|
-
{entry
|
|
424
|
+
{entry?.tools?.map((id) => {
|
|
425
425
|
const tool = toolById(id, toolKits)
|
|
426
426
|
return (
|
|
427
427
|
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
@@ -433,11 +433,12 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
433
433
|
title={tool?.name}
|
|
434
434
|
/>
|
|
435
435
|
</ImageBox>
|
|
436
|
-
)
|
|
436
|
+
)
|
|
437
|
+
})}
|
|
437
438
|
</Button>
|
|
438
439
|
<ViewToolsDetails chatId={chat.id} />
|
|
439
440
|
</div>}
|
|
440
|
-
|
|
441
|
+
|
|
441
442
|
{shouldShowFooter && <div className="message-footer">
|
|
442
443
|
{entry.agentType === 'bot' && !entry.error && <div className="message-actions">
|
|
443
444
|
{entry.type === 'md' && (
|
|
@@ -189,7 +189,7 @@ export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolC
|
|
|
189
189
|
|
|
190
190
|
useEffect(() => {
|
|
191
191
|
if (!toolStep) return undefined
|
|
192
|
-
const executionId = toolStep.attempts?.[0]
|
|
192
|
+
const executionId = toolStep.attempts?.[0]?.tools?.[0]?.executionId
|
|
193
193
|
if (!executionId) return
|
|
194
194
|
|
|
195
195
|
updateToolStep(messages, executionId, toolStep.status)
|
|
@@ -251,7 +251,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
251
251
|
|
|
252
252
|
useEffect(() => {
|
|
253
253
|
actualSteps.map((item) => {
|
|
254
|
-
const executionId = item.attempts[0]?.tools?.[0]
|
|
254
|
+
const executionId = item.attempts[0]?.tools?.[0]?.executionId
|
|
255
255
|
if (executionId) {
|
|
256
256
|
planningToolDictionaryHelper.setMessageIdPlanningStepToolExecutionId(`${messageId}`, executionId)
|
|
257
257
|
}
|
|
@@ -271,8 +271,8 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
271
271
|
const isLastStepDone = actualSteps[actualSteps.length - 1]?.status !== 'running' &&
|
|
272
272
|
actualSteps[actualSteps.length - 1]?.status !== 'pending'
|
|
273
273
|
const totalTools = useMemo(() => actualSteps?.reduce((sum, step) => {
|
|
274
|
-
const firstAttempt = step.attempts && step.attempts[0]
|
|
275
|
-
const toolsCount = firstAttempt
|
|
274
|
+
const firstAttempt = !!step.attempts && !!step.attempts.length ? step.attempts[0] : undefined
|
|
275
|
+
const toolsCount = firstAttempt?.tools?.length ?? 0
|
|
276
276
|
return sum + toolsCount
|
|
277
277
|
}, 0), [steps])
|
|
278
278
|
|
|
@@ -302,13 +302,15 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
302
302
|
<Column gap="8px" mt="8px">
|
|
303
303
|
<Divider colorScheme="light" />
|
|
304
304
|
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
305
|
-
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' &&
|
|
305
|
+
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
|
|
306
306
|
</Column>
|
|
307
307
|
|
|
308
|
+
{userHasAlreadyAnswered && planning?.[0]?.status === 'success' && <ViewToolsDetails chatId={chatId} />}
|
|
309
|
+
|
|
308
310
|
</div>
|
|
309
311
|
</AnimatedHeight> : null}
|
|
310
312
|
|
|
311
|
-
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
313
|
+
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
312
314
|
<ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
313
315
|
</>
|
|
314
316
|
)
|
|
@@ -317,18 +319,19 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
317
319
|
export const ViewToolsDetails = ({ chatId }: { chatId: string }) => {
|
|
318
320
|
const t = useTranslate(dictionary)
|
|
319
321
|
const messages = useChatMessages(chatId)
|
|
320
|
-
const
|
|
322
|
+
const widget = useWidget()
|
|
323
|
+
const getPlanningMessageId = () => {
|
|
321
324
|
const messageWithPlanning = findLast(messages, (message) => {
|
|
322
325
|
const steps = message.getValue().steps
|
|
323
326
|
const planningStep = steps?.find((step) => step.type === 'planning' && step.status === 'success')
|
|
324
327
|
return planningStep ? true : false
|
|
325
328
|
})
|
|
326
329
|
return messageWithPlanning?.id
|
|
327
|
-
|
|
328
|
-
}, [messages])
|
|
329
|
-
const widget = useWidget()
|
|
330
|
+
}
|
|
330
331
|
|
|
331
332
|
function openToolsPanel() {
|
|
333
|
+
const messageId = getPlanningMessageId()
|
|
334
|
+
|
|
332
335
|
if (messageId) {
|
|
333
336
|
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
334
337
|
widget.set('panel', 'steps')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IconButton, Row } from '@stack-spot/citric-react'
|
|
2
2
|
import { useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
|
|
3
3
|
import { useMessageInputDictionary } from './dictionary'
|
|
4
|
+
import { ModelSwitcher } from './ModelSwitcher'
|
|
4
5
|
import { SelectContent } from './SelectContent'
|
|
5
6
|
import { SelectionBarWrapper } from './styled'
|
|
6
7
|
|
|
@@ -29,6 +30,7 @@ export const ButtonBar = ({ onSend, isLoading }: SelectionBarProps) => {
|
|
|
29
30
|
<IconButton icon="Code" appearance="square" aria-label={t.code} title={t.code} onClick={() => widget.set('panel', 'editor')} />
|
|
30
31
|
)}
|
|
31
32
|
</Row>
|
|
33
|
+
<ModelSwitcher />
|
|
32
34
|
{isLoading ? (
|
|
33
35
|
<IconButton
|
|
34
36
|
icon="Stop"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CitricIconOutline, CitricIconSocial } from '@stack-spot/citric-icons'
|
|
2
|
+
import { AsyncContent, Button, Column, FieldGroup, Icon, Input, Row } from '@stack-spot/citric-react'
|
|
3
|
+
import { SelectionList } from '@stack-spot/portal-components/SelectionList'
|
|
4
|
+
import { agentToolsClient, genAiInferenceClient } from '@stack-spot/portal-network'
|
|
5
|
+
import { useMemo, useState } from 'react'
|
|
6
|
+
import { CSSProperties } from 'styled-components'
|
|
7
|
+
import { useCurrentChat, useCurrentChatState } from '../../../context/hooks'
|
|
8
|
+
import { useMessageInputDictionary } from '../dictionary'
|
|
9
|
+
import { RowWrapperStyled, stylesModelSwitcher } from '../styled'
|
|
10
|
+
import { getModelData, handleFilter, providerIcon } from './utils'
|
|
11
|
+
|
|
12
|
+
export const ModelSwitcher = () => {
|
|
13
|
+
const t = useMessageInputDictionary()
|
|
14
|
+
const agentCurrentChat = useCurrentChatState('agent')
|
|
15
|
+
const chat = useCurrentChat()
|
|
16
|
+
const [visibleMenu, setVisibleMenu] = useState(false)
|
|
17
|
+
const [agentData, isLoadingAgentData] = agentToolsClient.agent.useStatefulQuery({ agentId: agentCurrentChat?.id || '' })
|
|
18
|
+
const [listModels, isLoadingModels] = genAiInferenceClient.listModels.useStatefulQuery({ pageSize: 999, active: true })
|
|
19
|
+
const [filter, setFilter] = useState('')
|
|
20
|
+
|
|
21
|
+
const { modelName, modelProviderType, listItemsData } =
|
|
22
|
+
getModelData(chat, setVisibleMenu, agentData, listModels)
|
|
23
|
+
|
|
24
|
+
const data = useMemo(() => {
|
|
25
|
+
const items = listItemsData ?? []
|
|
26
|
+
return handleFilter(items, t, filter)
|
|
27
|
+
}, [filter, agentCurrentChat?.id, chat.get('selected_model_id'), listItemsData])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<RowWrapperStyled>
|
|
31
|
+
<AsyncContent loading={isLoadingAgentData && isLoadingModels}>
|
|
32
|
+
<Button
|
|
33
|
+
className="button-select-model"
|
|
34
|
+
colorScheme="light"
|
|
35
|
+
size="sm"
|
|
36
|
+
aria-label={t.agent}
|
|
37
|
+
title={t.agent}
|
|
38
|
+
onClick={() => setVisibleMenu(state => !state)}
|
|
39
|
+
>
|
|
40
|
+
<Icon
|
|
41
|
+
icon={providerIcon[modelProviderType as CitricIconOutline | CitricIconSocial]}
|
|
42
|
+
group={modelProviderType === 'stackspot' ? 'outline' : 'social'}
|
|
43
|
+
/>
|
|
44
|
+
{modelName}
|
|
45
|
+
<Icon icon="ChevronDown" group="fill" size="sm" />
|
|
46
|
+
</Button>
|
|
47
|
+
</AsyncContent>
|
|
48
|
+
<SelectionList
|
|
49
|
+
id="menuModelSwitcher"
|
|
50
|
+
items={data || []}
|
|
51
|
+
visible={visibleMenu}
|
|
52
|
+
onHide={() => setVisibleMenu(false)}
|
|
53
|
+
showListAsCard
|
|
54
|
+
style={stylesModelSwitcher.selection as CSSProperties}
|
|
55
|
+
before={
|
|
56
|
+
<Column>
|
|
57
|
+
<FieldGroup fullWidth style={{ marginTop: '8px' }}>
|
|
58
|
+
<Icon icon="Search" />
|
|
59
|
+
<Input type="search" value={filter} onChange={(value) => (setFilter(value))} />
|
|
60
|
+
</FieldGroup>
|
|
61
|
+
{!data.length ? <Row m="16px 8px">{t.nothingFound}</Row> : undefined}
|
|
62
|
+
</Column>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
</RowWrapperStyled>
|
|
66
|
+
)
|
|
67
|
+
}
|