@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +6 -75
  2. package/dist/app-metadata.json +5 -5
  3. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  4. package/dist/chat-interceptors/quick-commands.js +2 -0
  5. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  6. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  7. package/dist/chat-interceptors/send-message.js +1 -99
  8. package/dist/chat-interceptors/send-message.js.map +1 -1
  9. package/dist/state/ChatEntry.d.ts +1 -1
  10. package/dist/state/ChatEntry.d.ts.map +1 -1
  11. package/dist/state/ChatEntry.js +1 -2
  12. package/dist/state/ChatEntry.js.map +1 -1
  13. package/dist/state/ChatState.d.ts +0 -4
  14. package/dist/state/ChatState.d.ts.map +1 -1
  15. package/dist/state/ChatState.js.map +1 -1
  16. package/dist/views/Chat/ChatMessage.d.ts +1 -1
  17. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  18. package/dist/views/Chat/ChatMessage.js +3 -4
  19. package/dist/views/Chat/ChatMessage.js.map +1 -1
  20. package/dist/views/Chat/StepsList.d.ts +1 -6
  21. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  22. package/dist/views/Chat/StepsList.js +13 -117
  23. package/dist/views/Chat/StepsList.js.map +1 -1
  24. package/dist/views/Chat/styled.d.ts.map +1 -1
  25. package/dist/views/Chat/styled.js +6 -13
  26. package/dist/views/Chat/styled.js.map +1 -1
  27. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  28. package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
  29. package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
  30. package/dist/views/Steps/FlowChart/layout.d.ts +1 -1
  31. package/dist/views/Steps/FlowChart/layout.d.ts.map +1 -1
  32. package/dist/views/Steps/FlowChart/layout.js +0 -1
  33. package/dist/views/Steps/FlowChart/layout.js.map +1 -1
  34. package/dist/views/Steps/FlowChart/types.d.ts +1 -1
  35. package/dist/views/Steps/FlowChart/types.d.ts.map +1 -1
  36. package/dist/views/Steps/StepModal.js +2 -2
  37. package/dist/views/Steps/StepModal.js.map +1 -1
  38. package/dist/views/Steps/dictionary.d.ts +1 -1
  39. package/dist/views/Steps/utils.d.ts +1 -1
  40. package/dist/views/Steps/utils.d.ts.map +1 -1
  41. package/package.json +3 -3
  42. package/src/app-metadata.json +5 -5
  43. package/src/chat-interceptors/quick-commands.ts +8 -4
  44. package/src/chat-interceptors/send-message.ts +2 -113
  45. package/src/state/ChatEntry.ts +1 -2
  46. package/src/state/ChatState.ts +0 -4
  47. package/src/views/Chat/ChatMessage.tsx +4 -8
  48. package/src/views/Chat/StepsList.tsx +31 -282
  49. package/src/views/Chat/styled.ts +6 -13
  50. package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
  51. package/src/views/Steps/FlowChart/layout.ts +0 -1
  52. package/src/views/Steps/FlowChart/types.ts +1 -1
  53. package/src/views/Steps/StepModal.tsx +2 -2
  54. package/dist/utils/planning-tool.d.ts +0 -14
  55. package/dist/utils/planning-tool.d.ts.map +0 -1
  56. package/dist/utils/planning-tool.js +0 -25
  57. package/dist/utils/planning-tool.js.map +0 -1
  58. package/dist/utils/update-tool-step.d.ts +0 -3
  59. package/dist/utils/update-tool-step.d.ts.map +0 -1
  60. package/dist/utils/update-tool-step.js +0 -23
  61. package/dist/utils/update-tool-step.js.map +0 -1
  62. package/src/utils/planning-tool.ts +0 -32
  63. 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 { AgentInfo, aiClient, ChatResponseWithSteps, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
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
- const entry= {
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()
@@ -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[], data?: any) {
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
  }
@@ -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, useCurrentChatState, useWidget } from '../../context/hooks'
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, StepsPlaceholder } from './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 || isPlanning) && (
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 { Accordion, Badge, Button, Card, Column, Divider, Icon, IconBox, ImageWithFallback, ProgressCircular, Row, Skeleton, Text } from '@stack-spot/citric-react'
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, ToolChatStep } from '@stack-spot/portal-network'
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 React, { useMemo } from 'react'
8
- import styled from 'styled-components'
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: StepChatStepWithTarget,
18
+ step: StepChatStep,
28
19
  index: number,
29
- total?: number,
30
- totalTools?: number,
31
- isAllDone?: boolean,
20
+ total: number,
32
21
  onClick?: () => void,
33
22
  }
34
23
 
35
- function getStatusIcon(status: StepChatStepWithTarget['status'] | 'target', isDone?: boolean) {
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 group="fill" icon="Circle" {...iconProps} style={{ color: theme.color.light[600] }} />
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 StepAccordionHeader = ({ step, index, expand }: Pick<StepProps, 'step' | 'index'> & { expand: React.ReactElement }) => {
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
- <Row gap="4px" alignItems="center">
79
- <div className="step-status-icon">{status}</div>
80
- <StyledCard p="8px" w="75%">
81
- <Accordion header={expand => <StepAccordionHeader step={step} index={index} expand={expand} />}>
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
- {actualSteps.length > 0 ? <AnimatedHeight>
58
+
59
+ return (
60
+ <AnimatedHeight>
268
61
  <div className="steps">
269
- <Row gap="14px" mb="8px" ml="5px">
270
- <Icon icon="Target" size="sm" color="light.600" />
271
- <Text>{t.executionPlan}</Text>
272
- </Row>
273
- <ul className="steps-list">
274
- <Step
275
- step={actualSteps[currentStepIndex]}
276
- index={currentStepIndex + 1}
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
- <Column gap="8px" mt="8px">
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> : null}
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