@stack-spot/ai-chat-widget 2.1.0-beta.1 → 2.1.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 +17 -72
- package/dist/app-metadata.json +3 -3
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +36 -10
- package/dist/chat-interceptors/send-message.js.map +1 -1
- 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/Chat/ChatMessage.d.ts +1 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +20 -6
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts +7 -2
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +53 -20
- 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 +4 -3
- package/dist/views/Chat/styled.js.map +1 -1
- package/package.json +2 -2
- package/src/app-metadata.json +3 -3
- package/src/chat-interceptors/send-message.ts +36 -12
- package/src/utils/planning-tool.ts +9 -0
- package/src/views/Chat/ChatMessage.tsx +21 -5
- package/src/views/Chat/StepsList.tsx +98 -56
- package/src/views/Chat/styled.ts +4 -3
|
@@ -3,11 +3,11 @@ import { AnimatedHeight } from '@stack-spot/portal-components/AnimatedHeight'
|
|
|
3
3
|
import { ChatStep, StepChatStep, ToolChatStep } from '@stack-spot/portal-network'
|
|
4
4
|
import { theme } from '@stack-spot/portal-theme'
|
|
5
5
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
-
import { findLastIndex } from 'lodash'
|
|
7
|
-
import React, { useMemo } from 'react'
|
|
6
|
+
import { findLast, findLastIndex } from 'lodash'
|
|
7
|
+
import React, { useEffect, useMemo } from 'react'
|
|
8
8
|
import styled from 'styled-components'
|
|
9
9
|
import { Markdown } from '../../components/Markdown'
|
|
10
|
-
import { useCurrentChat, useCurrentChatMessages, useWidget } from '../../context/hooks'
|
|
10
|
+
import { useChat, useChatMessages, useCurrentChat, useCurrentChatMessages, useWidget } from '../../context/hooks'
|
|
11
11
|
import { ChatEntry } from '../../state/ChatEntry'
|
|
12
12
|
import { planningToolDictionaryHelper } from '../../utils/planning-tool'
|
|
13
13
|
import { updateToolStep } from '../../utils/update-tool-step'
|
|
@@ -17,6 +17,7 @@ interface Props {
|
|
|
17
17
|
steps: ChatStep[],
|
|
18
18
|
messageId: number,
|
|
19
19
|
chatId: string,
|
|
20
|
+
userHasAlreadyAnswered?: boolean,
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
interface StepChatStepWithTarget extends Omit<StepChatStep, 'status' | 'id' | 'type'> {
|
|
@@ -52,14 +53,14 @@ const StepAccordionHeader = ({ step, index, expand }: Pick<StepProps, 'step' | '
|
|
|
52
53
|
<Badge colorScheme="inverse" appearance="square">
|
|
53
54
|
{t.step} {index}
|
|
54
55
|
</Badge>}
|
|
55
|
-
<Text className="step-title" appearance="body2"
|
|
56
|
+
<Text className="step-title" appearance="body2">
|
|
56
57
|
{step.input}
|
|
57
58
|
</Text>
|
|
58
|
-
{step.status === 'awaiting_approval' &&
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
{step.status === 'awaiting_approval' &&
|
|
60
|
+
<Badge appearance="square" style={{ backgroundColor: theme.color.gray[800], color: theme.color.gray[50] }}>
|
|
61
|
+
<Icon icon="Security" />
|
|
62
|
+
{t.pendingReview}
|
|
63
|
+
</Badge>}
|
|
63
64
|
</Row>
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -72,12 +73,13 @@ const StyledCard = styled(Card)`
|
|
|
72
73
|
const Step = ({ step, index, onClick, total, totalTools, isAllDone }: StepProps) => {
|
|
73
74
|
const t = useTranslate(dictionary)
|
|
74
75
|
const status = getStatusIcon(step.status, isAllDone)
|
|
76
|
+
const hasTools = step.attempts?.[0]?.tools && step.attempts?.[0]?.tools?.length > 0
|
|
75
77
|
|
|
76
78
|
return (
|
|
77
79
|
<li tabIndex={onClick ? 0 : undefined} onClick={onClick} role={onClick ? 'button' : 'listitem'}>
|
|
78
80
|
<Row gap="4px" alignItems="center">
|
|
79
81
|
<div className="step-status-icon">{status}</div>
|
|
80
|
-
<StyledCard p="8px" w="
|
|
82
|
+
<StyledCard p="8px" w="80%">
|
|
81
83
|
<Accordion header={expand => <StepAccordionHeader step={step} index={index} expand={expand} />}>
|
|
82
84
|
<Column pt="12px">
|
|
83
85
|
{total ?
|
|
@@ -90,27 +92,32 @@ const Step = ({ step, index, onClick, total, totalTools, isAllDone }: StepProps)
|
|
|
90
92
|
<Row gap="4px">
|
|
91
93
|
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
92
94
|
<Text color="light.700">{t.totalTools}</Text>
|
|
93
|
-
{totalTools}
|
|
95
|
+
{totalTools ?? 0}
|
|
94
96
|
</Row>
|
|
95
97
|
</Row>
|
|
96
98
|
: <>
|
|
97
|
-
<Row pb="8px"
|
|
99
|
+
<Row pb="8px">
|
|
98
100
|
<Icon icon="Target" size="sm" color="light.700" />
|
|
99
|
-
<Text color="light.700">{t.stepGoal}:</Text>
|
|
100
|
-
<Text>
|
|
101
|
+
<Text color="light.700" tag="span" style={{ margin: '0 4px' }}>{t.stepGoal}:</Text>
|
|
102
|
+
<Text tag="span">
|
|
101
103
|
{step.input}
|
|
102
104
|
</Text>
|
|
103
105
|
</Row>
|
|
104
|
-
|
|
106
|
+
{hasTools ? <>
|
|
107
|
+
<Row gap="4px">
|
|
108
|
+
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
109
|
+
<Text color="light.700">{t.toolsToBeExecuted}:</Text>
|
|
110
|
+
</Row>
|
|
111
|
+
<ul className="tools-list">
|
|
112
|
+
{step.attempts?.[0]?.tools?.map((tool, index) => (<li key={`${tool.id}-${index}`}>
|
|
113
|
+
<Text>{tool.name}: {tool.goal}</Text>
|
|
114
|
+
</li>))}
|
|
115
|
+
</ul>
|
|
116
|
+
</> : <Row gap="4px">
|
|
105
117
|
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
106
|
-
<Text color="light.700">{t.
|
|
118
|
+
<Text color="light.700">{t.noToolToBeUsed}</Text>
|
|
107
119
|
</Row>
|
|
108
|
-
|
|
109
|
-
{step.attempts?.[0]?.tools?.map((tool) => (<li key={tool.id}>
|
|
110
|
-
<Text>{tool.name}: {tool.goal}</Text>
|
|
111
|
-
</li>),
|
|
112
|
-
)}
|
|
113
|
-
</ul>
|
|
120
|
+
}
|
|
114
121
|
</>
|
|
115
122
|
}
|
|
116
123
|
</Column>
|
|
@@ -140,9 +147,9 @@ export const StepsPlaceholder = () => {
|
|
|
140
147
|
</Card>
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
const AwaitingApproval = ({ customApproveText }: { customApproveText?: string }) => {
|
|
150
|
+
const AwaitingApproval = ({ customApproveText, chatId }: { chatId: string, customApproveText?: string }) => {
|
|
144
151
|
const t = useTranslate(dictionary)
|
|
145
|
-
const chat =
|
|
152
|
+
const chat = useChat(chatId)
|
|
146
153
|
|
|
147
154
|
const onAnswer = (response: string) => {
|
|
148
155
|
chat.pushMessage(ChatEntry.createUserEntry('', false, undefined, undefined, response))
|
|
@@ -167,7 +174,7 @@ const AwaitingApproval = ({ customApproveText }: { customApproveText?: string })
|
|
|
167
174
|
</>
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
export const ToolStepsList = ({ toolStep, messageId }: { toolStep: ToolChatStep, messageId: number }) => {
|
|
177
|
+
export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolChatStep, messageId: number, chatId: string }) => {
|
|
171
178
|
const t = useTranslate(dictionary)
|
|
172
179
|
const chat = useCurrentChat()
|
|
173
180
|
const messages = useCurrentChatMessages()
|
|
@@ -180,13 +187,13 @@ export const ToolStepsList = ({ toolStep, messageId }: { toolStep: ToolChatStep,
|
|
|
180
187
|
return toolStep.attempts?.[0].tools?.[0]
|
|
181
188
|
}, [toolStep])
|
|
182
189
|
|
|
183
|
-
|
|
190
|
+
useEffect(() => {
|
|
184
191
|
if (!toolStep) return undefined
|
|
185
192
|
const executionId = toolStep.attempts?.[0].tools?.[0].executionId
|
|
186
193
|
if (!executionId) return
|
|
187
194
|
|
|
188
195
|
updateToolStep(messages, executionId, toolStep.status)
|
|
189
|
-
|
|
196
|
+
|
|
190
197
|
}, [messages, toolStep, toolStep.status])
|
|
191
198
|
|
|
192
199
|
return <>
|
|
@@ -215,37 +222,52 @@ export const ToolStepsList = ({ toolStep, messageId }: { toolStep: ToolChatStep,
|
|
|
215
222
|
</Markdown>
|
|
216
223
|
</Accordion>
|
|
217
224
|
|
|
218
|
-
<AwaitingApproval customApproveText={t.approveTool} />
|
|
225
|
+
<AwaitingApproval customApproveText={t.approveTool} chatId={chatId} />
|
|
219
226
|
</Card>
|
|
220
227
|
</div>
|
|
221
228
|
</AnimatedHeight> : null}
|
|
222
229
|
</>
|
|
223
230
|
}
|
|
224
231
|
|
|
225
|
-
export const StepsList = ({ steps, chatId,
|
|
232
|
+
export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }: Props) => {
|
|
226
233
|
const t = useTranslate(dictionary)
|
|
227
|
-
|
|
234
|
+
|
|
235
|
+
const filteredSteps = steps.filter(s => s.type === 'step')
|
|
236
|
+
const actualSteps = useMemo(() => filteredSteps.filter((item) => {
|
|
237
|
+
const messageIdForStep = planningToolDictionaryHelper.getMessageIdFromStepId(item.id)
|
|
238
|
+
if (!messageIdForStep) {
|
|
239
|
+
planningToolDictionaryHelper.setMessageIdForStepId(messageId, item.id)
|
|
240
|
+
return true
|
|
241
|
+
} else if (messageIdForStep === messageId) {
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
244
|
+
// If a step is from a planning and it is already inserted in the planningToolDictionaryHelper it means the step is already in a
|
|
245
|
+
// previous message and we do not want to show it again (for instance, when required a approval in the planning, we will have
|
|
246
|
+
// two messages with the same step id one for the planning awaiting and one for the planning end, so we want to show only one)
|
|
247
|
+
return false
|
|
248
|
+
}), [filteredSteps])
|
|
249
|
+
|
|
228
250
|
const planning = steps.filter(s => s.type === 'planning')
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
|
|
252
|
+
useEffect(() => {
|
|
231
253
|
actualSteps.map((item) => {
|
|
232
|
-
const executionId = item.attempts[0]?.tools?.[0].executionId
|
|
254
|
+
const executionId = item.attempts[0]?.tools?.[0].executionId
|
|
233
255
|
if (executionId) {
|
|
234
256
|
planningToolDictionaryHelper.setMessageIdPlanningStepToolExecutionId(`${messageId}`, executionId)
|
|
235
257
|
}
|
|
236
258
|
})
|
|
237
259
|
}, [actualSteps, messageId])
|
|
238
260
|
|
|
239
|
-
const toolsStep = steps
|
|
240
|
-
|
|
241
|
-
|
|
261
|
+
const toolsStep = findLast(steps, s => s.type === 'tool')
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
242
264
|
const executionId = toolsStep?.attempts?.[0]?.tools?.[0]?.executionId
|
|
243
265
|
if (executionId) {
|
|
244
266
|
planningToolDictionaryHelper.setMessageIdToolStepToolExecutionId(`${messageId}`, executionId)
|
|
245
267
|
}
|
|
246
268
|
}, [toolsStep, messageId])
|
|
247
269
|
|
|
248
|
-
const planningGoal =
|
|
270
|
+
const planningGoal = planning?.[0]?.goal
|
|
249
271
|
const isLastStepDone = actualSteps[actualSteps.length - 1]?.status !== 'running' &&
|
|
250
272
|
actualSteps[actualSteps.length - 1]?.status !== 'pending'
|
|
251
273
|
const totalTools = useMemo(() => actualSteps?.reduce((sum, step) => {
|
|
@@ -256,12 +278,6 @@ export const StepsList = ({ steps, chatId, messageId }: Props) => {
|
|
|
256
278
|
|
|
257
279
|
let currentStepIndex = findLastIndex(actualSteps, s => s.status === 'running' || s.status === 'success')
|
|
258
280
|
if (currentStepIndex === -1) currentStepIndex = 0
|
|
259
|
-
const widget = useWidget()
|
|
260
|
-
|
|
261
|
-
function openToolsPanel() {
|
|
262
|
-
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
263
|
-
widget.set('panel', 'steps')
|
|
264
|
-
}
|
|
265
281
|
|
|
266
282
|
return (<>
|
|
267
283
|
{actualSteps.length > 0 ? <AnimatedHeight>
|
|
@@ -270,12 +286,10 @@ export const StepsList = ({ steps, chatId, messageId }: Props) => {
|
|
|
270
286
|
<Icon icon="Target" size="sm" color="light.600" />
|
|
271
287
|
<Text>{t.executionPlan}</Text>
|
|
272
288
|
</Row>
|
|
289
|
+
|
|
273
290
|
<ul className="steps-list">
|
|
274
|
-
<Step
|
|
275
|
-
|
|
276
|
-
index={currentStepIndex + 1}
|
|
277
|
-
/>
|
|
278
|
-
|
|
291
|
+
{actualSteps.map((s, i) => <Step step={s} key={i} index={i + 1} />)}
|
|
292
|
+
|
|
279
293
|
<Step
|
|
280
294
|
step={{ status: 'target', input: planningGoal, attempts: [] }}
|
|
281
295
|
index={currentStepIndex + 1}
|
|
@@ -288,23 +302,49 @@ export const StepsList = ({ steps, chatId, messageId }: Props) => {
|
|
|
288
302
|
<Column gap="8px" mt="8px">
|
|
289
303
|
<Divider colorScheme="light" />
|
|
290
304
|
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
291
|
-
{planning?.[0]?.status === 'awaiting_approval' &&
|
|
305
|
+
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
|
|
292
306
|
</Column>
|
|
293
307
|
|
|
294
|
-
{isLastStepDone && <div className="step-actions">
|
|
295
|
-
<Button colorScheme="light" size="sm" className="icon-button details" onClick={openToolsPanel}>
|
|
296
|
-
<Icon group="fill" icon="Play" size="xs" />
|
|
297
|
-
{t.detailed}
|
|
298
|
-
</Button>
|
|
299
|
-
</div>}
|
|
300
308
|
</div>
|
|
301
309
|
</AnimatedHeight> : null}
|
|
302
310
|
|
|
303
|
-
{toolsStep && toolsStep.status === 'awaiting_approval' &&
|
|
311
|
+
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
312
|
+
<ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
304
313
|
</>
|
|
305
314
|
)
|
|
306
315
|
}
|
|
307
316
|
|
|
317
|
+
export const ViewToolsDetails = ({ chatId }: { chatId: string }) => {
|
|
318
|
+
const t = useTranslate(dictionary)
|
|
319
|
+
const messages = useChatMessages(chatId)
|
|
320
|
+
const messageId = useMemo(() => {
|
|
321
|
+
const messageWithPlanning = findLast(messages, (message) => {
|
|
322
|
+
const steps = message.getValue().steps
|
|
323
|
+
const planningStep = steps?.find((step) => step.type === 'planning' && step.status === 'success')
|
|
324
|
+
return planningStep ? true : false
|
|
325
|
+
})
|
|
326
|
+
return messageWithPlanning?.id
|
|
327
|
+
|
|
328
|
+
}, [messages])
|
|
329
|
+
const widget = useWidget()
|
|
330
|
+
|
|
331
|
+
function openToolsPanel() {
|
|
332
|
+
if (messageId) {
|
|
333
|
+
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
334
|
+
widget.set('panel', 'steps')
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return <>
|
|
339
|
+
<div className="step-actions">
|
|
340
|
+
<Button colorScheme="light" size="sm" className="icon-button details" onClick={openToolsPanel}>
|
|
341
|
+
<Icon group="fill" icon="Play" size="xs" />
|
|
342
|
+
{t.detailed}
|
|
343
|
+
</Button>
|
|
344
|
+
</div>
|
|
345
|
+
</>
|
|
346
|
+
}
|
|
347
|
+
|
|
308
348
|
const dictionary = {
|
|
309
349
|
en: {
|
|
310
350
|
step: 'Step',
|
|
@@ -324,6 +364,7 @@ const dictionary = {
|
|
|
324
364
|
viewDetails: 'View details',
|
|
325
365
|
approveTool: 'Approve execution',
|
|
326
366
|
pendingReview: 'Pending review',
|
|
367
|
+
noToolToBeUsed: 'No tool will be needed',
|
|
327
368
|
},
|
|
328
369
|
pt: {
|
|
329
370
|
step: 'Passo',
|
|
@@ -343,5 +384,6 @@ const dictionary = {
|
|
|
343
384
|
viewDetails: 'Ver detalhes',
|
|
344
385
|
approveTool: 'Aprovar execução',
|
|
345
386
|
pendingReview: 'Revisão pendente',
|
|
387
|
+
noToolToBeUsed: 'Nenhuma tool será necessária',
|
|
346
388
|
},
|
|
347
389
|
} satisfies Dictionary
|
package/src/views/Chat/styled.ts
CHANGED
|
@@ -292,8 +292,10 @@ export const ChatList: IStyledComponentBase<
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.step-actions {
|
|
298
|
+
margin-top: 16px;
|
|
297
299
|
display: flex;
|
|
298
300
|
gap: 6px;
|
|
299
301
|
|
|
@@ -303,7 +305,6 @@ export const ChatList: IStyledComponentBase<
|
|
|
303
305
|
align-items: center;
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
|
-
}
|
|
307
308
|
|
|
308
309
|
.markdown img {
|
|
309
310
|
max-width: 70%;
|