@stack-spot/ai-chat-widget 2.1.0-beta.1 → 2.1.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 +6 -75
- package/dist/app-metadata.json +5 -5
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +2 -0
- 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 +1 -99
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +1 -1
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +1 -2
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/ChatState.d.ts +0 -4
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.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 +3 -4
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts +1 -6
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +13 -117
- 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 +6 -13
- package/dist/views/Chat/styled.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
- package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
- package/dist/views/Steps/FlowChart/layout.d.ts +1 -1
- package/dist/views/Steps/FlowChart/layout.d.ts.map +1 -1
- package/dist/views/Steps/FlowChart/layout.js +0 -1
- package/dist/views/Steps/FlowChart/layout.js.map +1 -1
- package/dist/views/Steps/FlowChart/types.d.ts +1 -1
- package/dist/views/Steps/FlowChart/types.d.ts.map +1 -1
- package/dist/views/Steps/StepModal.js +2 -2
- package/dist/views/Steps/StepModal.js.map +1 -1
- package/dist/views/Steps/dictionary.d.ts +1 -1
- package/dist/views/Steps/utils.d.ts +1 -1
- package/dist/views/Steps/utils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/app-metadata.json +5 -5
- package/src/chat-interceptors/quick-commands.ts +8 -4
- package/src/chat-interceptors/send-message.ts +2 -113
- package/src/state/ChatEntry.ts +1 -2
- package/src/state/ChatState.ts +0 -4
- package/src/views/Chat/ChatMessage.tsx +4 -8
- package/src/views/Chat/StepsList.tsx +31 -282
- package/src/views/Chat/styled.ts +6 -13
- package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
- package/src/views/Steps/FlowChart/layout.ts +0 -1
- package/src/views/Steps/FlowChart/types.ts +1 -1
- package/src/views/Steps/StepModal.tsx +2 -2
- package/dist/utils/planning-tool.d.ts +0 -14
- package/dist/utils/planning-tool.d.ts.map +0 -1
- package/dist/utils/planning-tool.js +0 -25
- package/dist/utils/planning-tool.js.map +0 -1
- package/dist/utils/update-tool-step.d.ts +0 -3
- package/dist/utils/update-tool-step.d.ts.map +0 -1
- package/dist/utils/update-tool-step.js +0 -23
- package/dist/utils/update-tool-step.js.map +0 -1
- package/src/utils/planning-tool.ts +0 -32
- package/src/utils/update-tool-step.tsx +0 -27
|
@@ -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,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aiClient, ChatResponseWithSteps, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
|
|
2
2
|
import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
|
|
3
3
|
import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
|
|
4
4
|
import { ChatState } from '../state/ChatState'
|
|
@@ -6,7 +6,6 @@ import { LabeledWithImage } from '../state/types'
|
|
|
6
6
|
import { buildConversationContext } from '../utils/chat'
|
|
7
7
|
import { treatHTMLInErrorMessage } from '../utils/error'
|
|
8
8
|
import { genericSourcesToKnowledgeSources } from '../utils/knowledge-source'
|
|
9
|
-
import { planningToolDictionaryHelper } from '../utils/planning-tool'
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Transforms a chat response from the backend into a chat entry that can be added to the chat.
|
|
@@ -23,7 +22,7 @@ function createEntryValueFromChatResponse(
|
|
|
23
22
|
agent: LabeledWithImage | undefined,
|
|
24
23
|
includeDate = false,
|
|
25
24
|
): TextChatEntry {
|
|
26
|
-
|
|
25
|
+
return {
|
|
27
26
|
agentType: 'bot',
|
|
28
27
|
type: 'md',
|
|
29
28
|
content: response.answer ?? '',
|
|
@@ -34,7 +33,6 @@ function createEntryValueFromChatResponse(
|
|
|
34
33
|
steps: response.steps,
|
|
35
34
|
tools: response.tools,
|
|
36
35
|
}
|
|
37
|
-
return entry as TextChatEntry
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
function buildPrompt(content: string, data?: any) {
|
|
@@ -63,18 +61,6 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
63
61
|
chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
|
|
64
62
|
chat.untitled = false
|
|
65
63
|
}
|
|
66
|
-
|
|
67
|
-
//Verify if the last planning in the messages has status awaiting_approval
|
|
68
|
-
const messages = chat.getMessages()
|
|
69
|
-
const lastPlanningAwaiting = messages.slice().reverse().find(item => {
|
|
70
|
-
const steps = item.getValue().steps
|
|
71
|
-
if (steps) {
|
|
72
|
-
const hasPlanning = steps.find((step) => step.type === 'planning')
|
|
73
|
-
return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
|
|
74
|
-
}
|
|
75
|
-
return false
|
|
76
|
-
})
|
|
77
|
-
|
|
78
64
|
const stream = aiClient.sendChatMessage({ context, user_prompt: buildPrompt(content, data) })
|
|
79
65
|
signal.addEventListener('abort', () => stream.cancel())
|
|
80
66
|
const botEntry = ChatEntry.createStreamedBotEntry()
|
|
@@ -83,109 +69,12 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
83
69
|
chat.pushMessage(botEntry)
|
|
84
70
|
}
|
|
85
71
|
let knowledgeSources: KnowledgeSource[] | undefined
|
|
86
|
-
|
|
87
|
-
const updatePlanningMessage = () => {
|
|
88
|
-
if (lastPlanningAwaiting) {
|
|
89
|
-
const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
|
|
90
|
-
const originalItemValue = originalItem?.getValue()
|
|
91
|
-
originalItemValue?.steps?.map((step) => {
|
|
92
|
-
if (step.type === 'planning' && step.status === 'awaiting_approval') {
|
|
93
|
-
step.status = 'success'
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const updateToolStatus = (agentInfo: AgentInfo) => {
|
|
101
|
-
const executionId = agentInfo.id
|
|
102
|
-
if (executionId) {
|
|
103
|
-
//Update message with type step which contains the planning steps
|
|
104
|
-
const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
|
|
105
|
-
const originalItem = messages.find((message) => `${message.id}` === messageId)
|
|
106
|
-
const originalItemValue = originalItem?.getValue()
|
|
107
|
-
let update = false
|
|
108
|
-
const status = agentInfo.action === 'start' ? 'running' : 'success'
|
|
109
|
-
const step = originalItemValue?.steps?.find(step => step.type === 'step' && step.attempts?.[0].tools?.[0].executionId === executionId)
|
|
110
|
-
if (step && step.status !== status) {
|
|
111
|
-
step.status = status
|
|
112
|
-
update = true
|
|
113
|
-
}
|
|
114
|
-
if (update) {
|
|
115
|
-
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
//Updates message with type tool which contains the actually tool steps
|
|
119
|
-
//We only want to show tools banner when they are awaiting_approval, by removing the step
|
|
120
|
-
// we avoid the entire bot message to be visible
|
|
121
|
-
const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
|
|
122
|
-
const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
|
|
123
|
-
const toolOriginalItemValue = toolOriginalItem?.getValue()
|
|
124
|
-
const toolStep = toolOriginalItemValue?.steps?.find(step =>
|
|
125
|
-
step.type === 'tool' && step.attempts?.[0].tools?.[0].executionId === executionId)
|
|
126
|
-
update = false
|
|
127
|
-
if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
|
|
128
|
-
toolOriginalItemValue.steps = undefined
|
|
129
|
-
update = true
|
|
130
|
-
}
|
|
131
|
-
if (update) {
|
|
132
|
-
toolOriginalItem?.setValue({ ...toolOriginalItemValue as TextChatEntry })
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
72
|
stream.onChange(value => {
|
|
138
|
-
if (value.agent_info?.type === 'planning') {
|
|
139
|
-
if (value.agent_info.action === 'start') {
|
|
140
|
-
chat.set('isPlaning', true)
|
|
141
|
-
} else {
|
|
142
|
-
chat.set('isPlaning', false)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
73
|
if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
|
|
147
74
|
knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
|
|
148
75
|
}
|
|
149
|
-
|
|
150
|
-
// When there is a planning with status awaiting_approval and we receive a new one
|
|
151
|
-
// we do not not want to add it again.
|
|
152
|
-
if (lastPlanningAwaiting && value.steps) {
|
|
153
|
-
const hasPlanningAwaiting = value.steps.find((item) => item.type === 'planning' && item.status === 'awaiting_approval')
|
|
154
|
-
value.steps = value.steps?.filter(step => {
|
|
155
|
-
if (step.type === 'planning') {
|
|
156
|
-
updatePlanningMessage()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return hasPlanningAwaiting ? true : (step.type !== 'planning' && step.type !== 'step' &&
|
|
160
|
-
(step.type !== 'answer' || (step.type === 'answer' && step.status === 'pending')))
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
|
|
165
|
-
updateToolStatus(value.agent_info)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (value.steps) {
|
|
169
|
-
const tool = value.steps.find((item) => item.type === 'tool')
|
|
170
|
-
if (tool && tool.status === 'running') {
|
|
171
|
-
const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
|
|
172
|
-
const originalItem = messages.find((message) => `${message.id}` === messageId)
|
|
173
|
-
const originalItemValue = originalItem?.getValue()
|
|
174
|
-
let update = false
|
|
175
|
-
const step = originalItemValue?.steps?.find(step => step.type === 'step')
|
|
176
|
-
if (step && step.attempts?.[0].tools?.[0].executionId === tool.id) {
|
|
177
|
-
step.attempts = JSON.parse(JSON.stringify(tool.attempts))
|
|
178
|
-
update = true
|
|
179
|
-
}
|
|
180
|
-
if (update) {
|
|
181
|
-
originalItem?.setValue({ ...originalItemValue as TextChatEntry })
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
76
|
botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
|
|
187
77
|
})
|
|
188
|
-
|
|
189
78
|
let finalValue: Partial<ChatResponse3> | undefined
|
|
190
79
|
try {
|
|
191
80
|
finalValue = await stream.getValue()
|
package/src/state/ChatEntry.ts
CHANGED
|
@@ -205,14 +205,13 @@ 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[]) {
|
|
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,
|
|
216
215
|
updated: new Date().toISOString(),
|
|
217
216
|
})
|
|
218
217
|
}
|
package/src/state/ChatState.ts
CHANGED
|
@@ -33,10 +33,6 @@ export interface ChatPropertiesWithOptionalFeatures {
|
|
|
33
33
|
* Whether or not the chat is in a loading state.
|
|
34
34
|
*/
|
|
35
35
|
isLoading?: boolean,
|
|
36
|
-
/**
|
|
37
|
-
* Whether or not the chat is planning.
|
|
38
|
-
*/
|
|
39
|
-
isPlaning?: boolean,
|
|
40
36
|
/**
|
|
41
37
|
* The value of the next message. This is the value of the text typed in the textarea below the chat.
|
|
42
38
|
*/
|
|
@@ -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,
|
|
12
|
+
import { useChatEntry, useCurrentChat, 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
|
|
20
|
+
import { StepsList } from './StepsList'
|
|
21
21
|
|
|
22
22
|
export interface CustomRenderResult {
|
|
23
23
|
/**
|
|
@@ -218,7 +218,6 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
218
218
|
{ searchAgentsRequest: { ids: entry.tools || [''] } }, { enabled: !!entry.tools })
|
|
219
219
|
const [copied, setCopied] = useState(false)
|
|
220
220
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
|
-
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
221
|
|
|
223
222
|
useChatScrollToBottomEffect(ref, [entry])
|
|
224
223
|
useMidnightUpdateView()
|
|
@@ -344,7 +343,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
344
343
|
widget.set('panel', 'resources')
|
|
345
344
|
}
|
|
346
345
|
|
|
347
|
-
return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length
|
|
346
|
+
return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length) && (
|
|
348
347
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
349
348
|
<div className="chat-message-container"
|
|
350
349
|
onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
|
|
@@ -357,14 +356,11 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
357
356
|
{!!entry.badges?.length && <div className="badges">
|
|
358
357
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
359
358
|
</div>}
|
|
360
|
-
|
|
361
|
-
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
|
|
362
|
-
|
|
363
359
|
{renderContent()}
|
|
364
360
|
|
|
361
|
+
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
|
|
365
362
|
</div>
|
|
366
363
|
)}
|
|
367
|
-
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder /> }
|
|
368
364
|
|
|
369
365
|
{entry.error && <Alert type="error">{entry.error}</Alert>}
|
|
370
366
|
</div>
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Icon } from '@stack-spot/citric-icons'
|
|
2
|
+
import { Button, ProgressCircular, Text } from '@stack-spot/citric-react'
|
|
2
3
|
import { AnimatedHeight } from '@stack-spot/portal-components/AnimatedHeight'
|
|
3
|
-
import { ChatStep, StepChatStep
|
|
4
|
+
import { ChatStep, StepChatStep } from '@stack-spot/portal-network'
|
|
4
5
|
import { theme } from '@stack-spot/portal-theme'
|
|
5
6
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
6
7
|
import { findLastIndex } from 'lodash'
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { Markdown } from '../../components/Markdown'
|
|
10
|
-
import { useCurrentChat, useCurrentChatMessages, useWidget } from '../../context/hooks'
|
|
11
|
-
import { ChatEntry } from '../../state/ChatEntry'
|
|
12
|
-
import { planningToolDictionaryHelper } from '../../utils/planning-tool'
|
|
13
|
-
import { updateToolStep } from '../../utils/update-tool-step'
|
|
14
|
-
import { onCopyCode } from './events'
|
|
8
|
+
import { useState } from 'react'
|
|
9
|
+
import { useWidget } from '../../context/hooks'
|
|
15
10
|
|
|
16
11
|
interface Props {
|
|
17
12
|
steps: ChatStep[],
|
|
@@ -19,241 +14,39 @@ interface Props {
|
|
|
19
14
|
chatId: string,
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
interface StepChatStepWithTarget extends Omit<StepChatStep, 'status' | 'id' | 'type'> {
|
|
23
|
-
status: 'pending' | 'running' | 'success' | 'error' | 'target' | 'awaiting_approval',
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
interface StepProps {
|
|
27
|
-
step:
|
|
18
|
+
step: StepChatStep,
|
|
28
19
|
index: number,
|
|
29
|
-
total
|
|
30
|
-
totalTools?: number,
|
|
31
|
-
isAllDone?: boolean,
|
|
20
|
+
total: number,
|
|
32
21
|
onClick?: () => void,
|
|
33
22
|
}
|
|
34
23
|
|
|
35
|
-
function getStatusIcon(status:
|
|
24
|
+
function getStatusIcon(status: ChatStep['status']) {
|
|
36
25
|
const iconProps = { style: { color: theme.color.light[700] }, size: 'xs' } as const
|
|
37
26
|
switch (status) {
|
|
38
27
|
case 'error': return <Icon group="fill" icon="TimesCircle" {...iconProps} />
|
|
39
28
|
case 'success': return <Icon group="fill" icon="CheckCircle" {...iconProps} />
|
|
40
|
-
case 'pending': return <Icon
|
|
41
|
-
case 'awaiting_approval': return <Icon group="fill" icon="ExclamationTriangle" {...iconProps} />
|
|
42
|
-
case 'target': return <Icon icon="Target" {...iconProps} style={{ color: isDone ? theme.color.light[700] : theme.color.light[600] }} />
|
|
29
|
+
case 'pending': return <Icon icon="Spaces" {...iconProps} />
|
|
43
30
|
case 'running': return <ProgressCircular className="loading" colorScheme="inverse" size="xs" />
|
|
44
31
|
}
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
const
|
|
34
|
+
const Step = ({ step, index, total, onClick }: StepProps) => {
|
|
48
35
|
const t = useTranslate(dictionary)
|
|
49
|
-
return <Row gap="8px">
|
|
50
|
-
{expand}
|
|
51
|
-
{step.status === 'target' ? <Text className="step-title" appearance="body2" color="light.700">{t.planGoal}:</Text> :
|
|
52
|
-
<Badge colorScheme="inverse" appearance="square">
|
|
53
|
-
{t.step} {index}
|
|
54
|
-
</Badge>}
|
|
55
|
-
<Text className="step-title" appearance="body2" >
|
|
56
|
-
{step.input}
|
|
57
|
-
</Text>
|
|
58
|
-
{step.status === 'awaiting_approval' &&
|
|
59
|
-
<Badge appearance="square" style={{ backgroundColor: theme.color.gray[800], color: theme.color.gray[50] }}>
|
|
60
|
-
<Icon icon="Security" />
|
|
61
|
-
{t.pendingReview}
|
|
62
|
-
</Badge>}
|
|
63
|
-
</Row>
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const StyledCard = styled(Card)`
|
|
67
|
-
&:hover {
|
|
68
|
-
background-color: ${theme.color.light[500]}
|
|
69
|
-
}
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
const Step = ({ step, index, onClick, total, totalTools, isAllDone }: StepProps) => {
|
|
73
|
-
const t = useTranslate(dictionary)
|
|
74
|
-
const status = getStatusIcon(step.status, isAllDone)
|
|
75
|
-
|
|
76
36
|
return (
|
|
77
37
|
<li tabIndex={onClick ? 0 : undefined} onClick={onClick} role={onClick ? 'button' : 'listitem'}>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<Column pt="12px">
|
|
83
|
-
{total ?
|
|
84
|
-
<Row gap="40px">
|
|
85
|
-
<Row gap="4px">
|
|
86
|
-
<Icon icon="Hashtag" size="sm" color="light.700" />
|
|
87
|
-
<Text color="light.700">{t.totalSteps}</Text>
|
|
88
|
-
{total}
|
|
89
|
-
</Row>
|
|
90
|
-
<Row gap="4px">
|
|
91
|
-
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
92
|
-
<Text color="light.700">{t.totalTools}</Text>
|
|
93
|
-
{totalTools}
|
|
94
|
-
</Row>
|
|
95
|
-
</Row>
|
|
96
|
-
: <>
|
|
97
|
-
<Row pb="8px" gap="4px">
|
|
98
|
-
<Icon icon="Target" size="sm" color="light.700" />
|
|
99
|
-
<Text color="light.700">{t.stepGoal}:</Text>
|
|
100
|
-
<Text>
|
|
101
|
-
{step.input}
|
|
102
|
-
</Text>
|
|
103
|
-
</Row>
|
|
104
|
-
<Row gap="4px">
|
|
105
|
-
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
106
|
-
<Text color="light.700">{t.toolsToBeExecuted}:</Text>
|
|
107
|
-
</Row>
|
|
108
|
-
<ul className="tools-list">
|
|
109
|
-
{step.attempts?.[0]?.tools?.map((tool) => (<li key={tool.id}>
|
|
110
|
-
<Text>{tool.name}: {tool.goal}</Text>
|
|
111
|
-
</li>),
|
|
112
|
-
)}
|
|
113
|
-
</ul>
|
|
114
|
-
</>
|
|
115
|
-
}
|
|
116
|
-
</Column>
|
|
117
|
-
</Accordion>
|
|
118
|
-
</StyledCard>
|
|
119
|
-
</Row>
|
|
38
|
+
<div className="step-status-icon">{getStatusIcon(step.status)}</div>
|
|
39
|
+
<Text className="step-title" appearance="microtext1" color="light.700">
|
|
40
|
+
{t.step} {index}/{total}: {step.input}
|
|
41
|
+
</Text>
|
|
120
42
|
</li>
|
|
121
43
|
)
|
|
122
44
|
}
|
|
123
45
|
|
|
124
|
-
export const StepsPlaceholder = () => {
|
|
125
|
-
const t = useTranslate(dictionary)
|
|
126
|
-
return <Card gap="8px">
|
|
127
|
-
<Row gap="8px">
|
|
128
|
-
<ProgressCircular colorScheme="inverse" size="xs" />
|
|
129
|
-
<Text color="light.700">{t.generatingPlan}</Text>
|
|
130
|
-
</Row>
|
|
131
|
-
<Text color="light.700">
|
|
132
|
-
{t.analyzingRequirements}
|
|
133
|
-
</Text>
|
|
134
|
-
<Row gap="12px">
|
|
135
|
-
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
136
|
-
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
137
|
-
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
138
|
-
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
139
|
-
</Row>
|
|
140
|
-
</Card>
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const AwaitingApproval = ({ customApproveText }: { customApproveText?: string }) => {
|
|
144
|
-
const t = useTranslate(dictionary)
|
|
145
|
-
const chat = useCurrentChat()
|
|
146
|
-
|
|
147
|
-
const onAnswer = (response: string) => {
|
|
148
|
-
chat.pushMessage(ChatEntry.createUserEntry('', false, undefined, undefined, response))
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return <>
|
|
152
|
-
<Row gap="8px">
|
|
153
|
-
<Button colorScheme="light" onClick={() => onAnswer(t.cancel)}>
|
|
154
|
-
<Row gap="8px">
|
|
155
|
-
<Icon icon="Stop" />
|
|
156
|
-
{t.cancel}
|
|
157
|
-
</Row>
|
|
158
|
-
</Button>
|
|
159
|
-
|
|
160
|
-
<Button colorScheme="inverse" onClick={() => onAnswer(customApproveText ?? t.approve)}>
|
|
161
|
-
<Row gap="8px">
|
|
162
|
-
<Icon group="fill" icon="Play" />
|
|
163
|
-
{customApproveText ?? t.approve}
|
|
164
|
-
</Row>
|
|
165
|
-
</Button>
|
|
166
|
-
</Row>
|
|
167
|
-
</>
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export const ToolStepsList = ({ toolStep, messageId }: { toolStep: ToolChatStep, messageId: number }) => {
|
|
171
|
-
const t = useTranslate(dictionary)
|
|
172
|
-
const chat = useCurrentChat()
|
|
173
|
-
const messages = useCurrentChatMessages()
|
|
174
|
-
const inputParsed = `\`\`\`json
|
|
175
|
-
${JSON.stringify(toolStep?.input, null, 2)}
|
|
176
|
-
\`\`\``
|
|
177
|
-
|
|
178
|
-
const tool = useMemo(() => {
|
|
179
|
-
if (!toolStep) return undefined
|
|
180
|
-
return toolStep.attempts?.[0].tools?.[0]
|
|
181
|
-
}, [toolStep])
|
|
182
|
-
|
|
183
|
-
useMemo(() => {
|
|
184
|
-
if (!toolStep) return undefined
|
|
185
|
-
const executionId = toolStep.attempts?.[0].tools?.[0].executionId
|
|
186
|
-
if (!executionId) return
|
|
187
|
-
|
|
188
|
-
updateToolStep(messages, executionId, toolStep.status)
|
|
189
|
-
|
|
190
|
-
}, [messages, toolStep, toolStep.status])
|
|
191
|
-
|
|
192
|
-
return <>
|
|
193
|
-
{toolStep && tool ? <AnimatedHeight>
|
|
194
|
-
<div className="steps">
|
|
195
|
-
<Badge colorPalette="yellow" appearance="square">
|
|
196
|
-
<Icon icon="StopWatch" />
|
|
197
|
-
<Text>{tool.name} {t.keepWorking}</Text>
|
|
198
|
-
</Badge>
|
|
199
|
-
<Card mt="16px" gap="8px" bgLevel={500}>
|
|
200
|
-
<Row>
|
|
201
|
-
<ImageWithFallback src={tool.image} width="32px" fallback={<IconBox appearance="circle" icon="StackSpot" />} />
|
|
202
|
-
<Text>{tool.name}</Text>
|
|
203
|
-
</Row>
|
|
204
|
-
|
|
205
|
-
<Text>
|
|
206
|
-
{toolStep.user_question}
|
|
207
|
-
</Text>
|
|
208
|
-
|
|
209
|
-
<Accordion header={expand => <Row gap="8px" mb="4px">
|
|
210
|
-
<Card p="4px" bgLevel={400}>{expand}</Card>
|
|
211
|
-
<Text > {t.viewDetails} </Text>
|
|
212
|
-
</Row>}>
|
|
213
|
-
<Markdown onCopyCode={(code) => onCopyCode(code, `${messageId}`, chat)}>
|
|
214
|
-
{inputParsed}
|
|
215
|
-
</Markdown>
|
|
216
|
-
</Accordion>
|
|
217
|
-
|
|
218
|
-
<AwaitingApproval customApproveText={t.approveTool} />
|
|
219
|
-
</Card>
|
|
220
|
-
</div>
|
|
221
|
-
</AnimatedHeight> : null}
|
|
222
|
-
</>
|
|
223
|
-
}
|
|
224
|
-
|
|
225
46
|
export const StepsList = ({ steps, chatId, messageId }: Props) => {
|
|
226
47
|
const t = useTranslate(dictionary)
|
|
48
|
+
const [isExpanded, setExpanded] = useState(false)
|
|
227
49
|
const actualSteps = steps.filter(s => s.type === 'step')
|
|
228
|
-
const planning = steps.filter(s => s.type === 'planning')
|
|
229
|
-
|
|
230
|
-
useMemo(() => {
|
|
231
|
-
actualSteps.map((item) => {
|
|
232
|
-
const executionId = item.attempts[0]?.tools?.[0].executionId
|
|
233
|
-
if (executionId) {
|
|
234
|
-
planningToolDictionaryHelper.setMessageIdPlanningStepToolExecutionId(`${messageId}`, executionId)
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
}, [actualSteps, messageId])
|
|
238
|
-
|
|
239
|
-
const toolsStep = steps.find(s => s.type === 'tool')
|
|
240
|
-
|
|
241
|
-
useMemo(() => {
|
|
242
|
-
const executionId = toolsStep?.attempts?.[0]?.tools?.[0]?.executionId
|
|
243
|
-
if (executionId) {
|
|
244
|
-
planningToolDictionaryHelper.setMessageIdToolStepToolExecutionId(`${messageId}`, executionId)
|
|
245
|
-
}
|
|
246
|
-
}, [toolsStep, messageId])
|
|
247
|
-
|
|
248
|
-
const planningGoal = steps.filter(s => s.type === 'planning')?.[0]?.goal
|
|
249
|
-
const isLastStepDone = actualSteps[actualSteps.length - 1]?.status !== 'running' &&
|
|
250
|
-
actualSteps[actualSteps.length - 1]?.status !== 'pending'
|
|
251
|
-
const totalTools = useMemo(() => actualSteps?.reduce((sum, step) => {
|
|
252
|
-
const firstAttempt = step.attempts && step.attempts[0]
|
|
253
|
-
const toolsCount = firstAttempt.tools?.length ?? 0
|
|
254
|
-
return sum + toolsCount
|
|
255
|
-
}, 0), [steps])
|
|
256
|
-
|
|
257
50
|
let currentStepIndex = findLastIndex(actualSteps, s => s.status === 'running' || s.status === 'success')
|
|
258
51
|
if (currentStepIndex === -1) currentStepIndex = 0
|
|
259
52
|
const widget = useWidget()
|
|
@@ -262,46 +55,30 @@ export const StepsList = ({ steps, chatId, messageId }: Props) => {
|
|
|
262
55
|
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
263
56
|
widget.set('panel', 'steps')
|
|
264
57
|
}
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<AnimatedHeight>
|
|
268
61
|
<div className="steps">
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<Step
|
|
280
|
-
step={{ status: 'target', input: planningGoal, attempts: [] }}
|
|
281
|
-
index={currentStepIndex + 1}
|
|
282
|
-
total={actualSteps.length}
|
|
283
|
-
totalTools={totalTools}
|
|
284
|
-
isAllDone={isLastStepDone}
|
|
285
|
-
/>
|
|
62
|
+
<ul>
|
|
63
|
+
{isExpanded
|
|
64
|
+
? actualSteps.map((s, i) => <Step step={s} key={i} index={i + 1} total={actualSteps.length} />)
|
|
65
|
+
: <Step
|
|
66
|
+
step={actualSteps[currentStepIndex]}
|
|
67
|
+
index={currentStepIndex + 1}
|
|
68
|
+
total={actualSteps.length}
|
|
69
|
+
onClick={() => setExpanded(true)}
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
286
72
|
</ul>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<Divider colorScheme="light" />
|
|
290
|
-
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
291
|
-
{planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval />}
|
|
292
|
-
</Column>
|
|
293
|
-
|
|
294
|
-
{isLastStepDone && <div className="step-actions">
|
|
73
|
+
{isExpanded && <div className="step-actions">
|
|
74
|
+
<Button colorScheme="light" size="sm" onClick={() => setExpanded(false)}>{t.hideSteps}</Button>
|
|
295
75
|
<Button colorScheme="light" size="sm" className="icon-button details" onClick={openToolsPanel}>
|
|
296
76
|
<Icon group="fill" icon="Play" size="xs" />
|
|
297
77
|
{t.detailed}
|
|
298
78
|
</Button>
|
|
299
79
|
</div>}
|
|
300
80
|
</div>
|
|
301
|
-
</AnimatedHeight>
|
|
302
|
-
|
|
303
|
-
{toolsStep && toolsStep.status === 'awaiting_approval' && <ToolStepsList toolStep={toolsStep} messageId={messageId} />}
|
|
304
|
-
</>
|
|
81
|
+
</AnimatedHeight>
|
|
305
82
|
)
|
|
306
83
|
}
|
|
307
84
|
|
|
@@ -310,38 +87,10 @@ const dictionary = {
|
|
|
310
87
|
step: 'Step',
|
|
311
88
|
hideSteps: 'Hide steps',
|
|
312
89
|
detailed: 'View detailed mode',
|
|
313
|
-
generatingPlan: 'Generating execution plan...',
|
|
314
|
-
analyzingRequirements: 'Analyzing task requirements',
|
|
315
|
-
executionPlan: 'Execution Plan',
|
|
316
|
-
planGoal: 'Plan Goal',
|
|
317
|
-
toolsToBeExecuted: 'Tools to be executed',
|
|
318
|
-
totalSteps: 'Total Steps',
|
|
319
|
-
totalTools: 'Total Tools',
|
|
320
|
-
stepGoal: 'Step Goal',
|
|
321
|
-
cancel: 'Cancel execution',
|
|
322
|
-
approve: 'Approve & Execute Plan',
|
|
323
|
-
keepWorking: 'will keep working after your answer',
|
|
324
|
-
viewDetails: 'View details',
|
|
325
|
-
approveTool: 'Approve execution',
|
|
326
|
-
pendingReview: 'Pending review',
|
|
327
90
|
},
|
|
328
91
|
pt: {
|
|
329
92
|
step: 'Passo',
|
|
330
93
|
hideSteps: 'Esconder passos',
|
|
331
94
|
detailed: 'Ver modo detalhado',
|
|
332
|
-
generatingPlan: 'Gerando plano de execução...',
|
|
333
|
-
analyzingRequirements: 'Analisando os requisitos da task',
|
|
334
|
-
executionPlan: 'Plano de Execução',
|
|
335
|
-
planGoal: 'Finalidade do Plano',
|
|
336
|
-
toolsToBeExecuted: 'Tools a serem executadas',
|
|
337
|
-
totalSteps: 'Total de Passos',
|
|
338
|
-
totalTools: 'Total de Tools',
|
|
339
|
-
stepGoal: 'Objetivo do passo',
|
|
340
|
-
cancel: 'Cancelar execução',
|
|
341
|
-
approve: 'Aprovar & Executar plano',
|
|
342
|
-
keepWorking: 'continuará trabalhando após a sua resposta',
|
|
343
|
-
viewDetails: 'Ver detalhes',
|
|
344
|
-
approveTool: 'Aprovar execução',
|
|
345
|
-
pendingReview: 'Revisão pendente',
|
|
346
95
|
},
|
|
347
96
|
} satisfies Dictionary
|