@stack-spot/ai-chat-widget 2.0.0-betacitric.9 → 3.0.0-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.
Files changed (62) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/app-metadata.json +7 -7
  3. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  4. package/dist/chat-interceptors/send-message.js +78 -0
  5. package/dist/chat-interceptors/send-message.js.map +1 -1
  6. package/dist/state/ChatState.d.ts +4 -0
  7. package/dist/state/ChatState.d.ts.map +1 -1
  8. package/dist/state/ChatState.js.map +1 -1
  9. package/dist/utils/date.d.ts +1 -2
  10. package/dist/utils/date.d.ts.map +1 -1
  11. package/dist/utils/date.js +3 -3
  12. package/dist/utils/date.js.map +1 -1
  13. package/dist/utils/error.js +2 -2
  14. package/dist/utils/error.js.map +1 -1
  15. package/dist/utils/planning-tool.d.ts +14 -0
  16. package/dist/utils/planning-tool.d.ts.map +1 -0
  17. package/dist/utils/planning-tool.js +25 -0
  18. package/dist/utils/planning-tool.js.map +1 -0
  19. package/dist/utils/update-tool-step.d.ts +3 -0
  20. package/dist/utils/update-tool-step.d.ts.map +1 -0
  21. package/dist/utils/update-tool-step.js +23 -0
  22. package/dist/utils/update-tool-step.js.map +1 -0
  23. package/dist/views/Chat/ChatMessage.d.ts +1 -1
  24. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  25. package/dist/views/Chat/ChatMessage.js +4 -3
  26. package/dist/views/Chat/ChatMessage.js.map +1 -1
  27. package/dist/views/Chat/StepsList.d.ts +6 -1
  28. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  29. package/dist/views/Chat/StepsList.js +118 -13
  30. package/dist/views/Chat/StepsList.js.map +1 -1
  31. package/dist/views/Chat/styled.d.ts.map +1 -1
  32. package/dist/views/Chat/styled.js +13 -6
  33. package/dist/views/Chat/styled.js.map +1 -1
  34. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  35. package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
  36. package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
  37. package/dist/views/Steps/FlowChart/layout.d.ts +1 -1
  38. package/dist/views/Steps/FlowChart/layout.d.ts.map +1 -1
  39. package/dist/views/Steps/FlowChart/layout.js +1 -0
  40. package/dist/views/Steps/FlowChart/layout.js.map +1 -1
  41. package/dist/views/Steps/FlowChart/types.d.ts +1 -1
  42. package/dist/views/Steps/FlowChart/types.d.ts.map +1 -1
  43. package/dist/views/Steps/StepModal.js +2 -2
  44. package/dist/views/Steps/StepModal.js.map +1 -1
  45. package/dist/views/Steps/dictionary.d.ts +1 -1
  46. package/dist/views/Steps/utils.d.ts +1 -1
  47. package/dist/views/Steps/utils.d.ts.map +1 -1
  48. package/package.json +12 -12
  49. package/src/app-metadata.json +7 -7
  50. package/src/chat-interceptors/send-message.ts +91 -1
  51. package/src/state/ChatState.ts +4 -0
  52. package/src/utils/date.ts +3 -3
  53. package/src/utils/error.ts +2 -2
  54. package/src/utils/planning-tool.ts +32 -0
  55. package/src/utils/update-tool-step.tsx +27 -0
  56. package/src/views/Chat/ChatMessage.tsx +8 -4
  57. package/src/views/Chat/StepsList.tsx +284 -31
  58. package/src/views/Chat/styled.ts +13 -6
  59. package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
  60. package/src/views/Steps/FlowChart/layout.ts +1 -0
  61. package/src/views/Steps/FlowChart/types.ts +1 -1
  62. package/src/views/Steps/StepModal.tsx +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "2.0.0-betacitric.9",
4
- "date": "Tue Sep 09 2025 10:13:25 GMT-0300 (Horário Padrão de Brasília)",
3
+ "version": "3.0.0-beta.1",
4
+ "date": "Wed Oct 01 2025 15:13:49 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.2"
112
+ "version": "0.2.3"
113
113
  },
114
114
  {
115
115
  "name": "@stack-spot/citric-react",
116
- "version": "0.35.0(@stack-spot/citric-icons@0.2.2)(@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@1.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)"
116
+ "version": "0.36.0(@stack-spot/citric-icons@0.2.3)(@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.25.5(@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@1.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)"
120
+ "version": "2.26.0(@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.173.1(@stack-spot/auth@5.3.2)(@stack-spot/opa@2.5.0(@stack-spot/auth@5.3.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@stack-spot/portal-translate@1.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)"
124
+ "version": "0.179.1-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",
@@ -129,7 +129,7 @@
129
129
  },
130
130
  {
131
131
  "name": "@stack-spot/portal-translate",
132
- "version": "1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
132
+ "version": "2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
133
133
  },
134
134
  {
135
135
  "name": "@xyflow/react",
@@ -1,4 +1,4 @@
1
- import { aiClient, ChatResponseWithSteps, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
1
+ import { AgentInfo, 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,6 +6,7 @@ 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'
9
10
 
10
11
  /**
11
12
  * Transforms a chat response from the backend into a chat entry that can be added to the chat.
@@ -61,6 +62,18 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
61
62
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
62
63
  chat.untitled = false
63
64
  }
65
+
66
+ //Verify if the last planning in the messages has status awaiting_approval
67
+ const messages = chat.getMessages()
68
+ const lastPlanningAwaiting = messages.slice().reverse().find(item => {
69
+ const steps = item.getValue().steps
70
+ if (steps) {
71
+ const hasPlanning = steps.find((step) => step.type === 'planning')
72
+ return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
73
+ }
74
+ return false
75
+ })
76
+
64
77
  const stream = aiClient.sendChatMessage({ context, user_prompt: buildPrompt(content, data) })
65
78
  signal.addEventListener('abort', () => stream.cancel())
66
79
  const botEntry = ChatEntry.createStreamedBotEntry()
@@ -69,12 +82,89 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
69
82
  chat.pushMessage(botEntry)
70
83
  }
71
84
  let knowledgeSources: KnowledgeSource[] | undefined
85
+
86
+ const updatePlanningMessage = () => {
87
+ if (lastPlanningAwaiting) {
88
+ const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
89
+ const originalItemValue = originalItem?.getValue()
90
+ originalItemValue?.steps?.map((step) => {
91
+ if (step.type === 'planning' && step.status === 'awaiting_approval') {
92
+ step.status = 'success'
93
+ }
94
+ })
95
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
96
+ }
97
+ }
98
+
99
+ const updateToolStatus = (agentInfo: AgentInfo) => {
100
+ const executionId = agentInfo.id
101
+ if (executionId) {
102
+ //Update message with type step which contains the planning steps
103
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
104
+ const originalItem = messages.find((message) => `${message.id}` === messageId)
105
+ const originalItemValue = originalItem?.getValue()
106
+ let update = false
107
+ const status = agentInfo.action === 'start' ? 'pending' : 'success'
108
+ const step = originalItemValue?.steps?.find(step => step.type === 'step' && step.attempts?.[0].tools?.[0].executionId === executionId)
109
+ if (step && step.status !== status) {
110
+ step.status = status
111
+ update = true
112
+ }
113
+ if (update) {
114
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
115
+ }
116
+
117
+ //Updates message with type tool which contains the actually tool steps
118
+ const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
119
+ const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
120
+ const toolOriginalItemValue = toolOriginalItem?.getValue()
121
+ const toolStep = toolOriginalItemValue?.steps?.find(step =>
122
+ step.type === 'tool' && step.attempts?.[0].tools?.[0].executionId === executionId)
123
+ update = false
124
+ if (toolStep && toolStep.status !== status) {
125
+ toolStep.status = status
126
+ update = true
127
+ }
128
+ if (update) {
129
+ toolOriginalItem?.setValue({ ...toolOriginalItemValue as TextChatEntry })
130
+ }
131
+ }
132
+ }
133
+
72
134
  stream.onChange(value => {
135
+ if (value.agent_info?.type === 'planning') {
136
+ if (value.agent_info.action === 'start') {
137
+ chat.set('isPlaning', true)
138
+ } else {
139
+ chat.set('isPlaning', false)
140
+ }
141
+ }
142
+
73
143
  if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
74
144
  knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
75
145
  }
146
+
147
+ // When there is a planning with status awaiting_approval and we receive a new one
148
+ // we do not not want to add it again.
149
+ if (lastPlanningAwaiting && value.steps) {
150
+ const hasPlanningAwaiting = value.steps.find((item) => item.type === 'planning' && item.status === 'awaiting_approval')
151
+ value.steps = value.steps?.filter(step => {
152
+ if (step.type === 'planning') {
153
+ updatePlanningMessage()
154
+ }
155
+
156
+ return hasPlanningAwaiting ? true : (step.type !== 'planning' && step.type !== 'step' &&
157
+ (step.type !== 'answer' || (step.type === 'answer' && step.status === 'pending')))
158
+ })
159
+ }
160
+
161
+ if (value.agent_info?.type === 'tool') {
162
+ updateToolStatus(value.agent_info)
163
+ }
164
+
76
165
  botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
77
166
  })
167
+
78
168
  let finalValue: Partial<ChatResponse3> | undefined
79
169
  try {
80
170
  finalValue = await stream.getValue()
@@ -33,6 +33,10 @@ 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,
36
40
  /**
37
41
  * The value of the next message. This is the value of the text typed in the textarea below the chat.
38
42
  */
package/src/utils/date.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Dictionary, Language, getLanguage, useLanguage } from '@stack-spot/portal-translate'
1
+ import { Dictionary, getLanguage, ptEn, useLanguage } from '@stack-spot/portal-translate'
2
2
 
3
3
  const OneDay = 24 * 60 * 60 * 1000
4
4
  const timeFormatOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute:'2-digit' }
@@ -19,7 +19,7 @@ const fullFormatOptions: Intl.DateTimeFormatOptions = {
19
19
  * @param language the language to use.
20
20
  * @returns the formatted date.
21
21
  */
22
- export function formatDateForChatMessage(date: Date, language: Language = getLanguage()) {
22
+ export function formatDateForChatMessage(date: Date, language = getLanguage(ptEn)) {
23
23
  const formatted: string[] = []
24
24
  const now = new Date()
25
25
  const isToday = date.toDateString() === now.toDateString()
@@ -36,7 +36,7 @@ export function formatDateForChatMessage(date: Date, language: Language = getLan
36
36
  * @returns an object containing functions for formatting dates.
37
37
  */
38
38
  export function useDateFormatter() {
39
- const language = useLanguage()
39
+ const language = useLanguage(ptEn)
40
40
  return {
41
41
  /**
42
42
  * @param date the date to format using {@link formatDateForChatMessage}.
@@ -1,4 +1,4 @@
1
- import { getLanguage } from '@stack-spot/portal-translate'
1
+ import { getLanguage, ptEn } from '@stack-spot/portal-translate'
2
2
 
3
3
  const httpErrors: Record<'en' | 'pt', Record<number, string>> = {
4
4
  en: {
@@ -48,7 +48,7 @@ const httpErrors: Record<'en' | 'pt', Record<number, string>> = {
48
48
  }
49
49
 
50
50
  function getGenericErrorBasedOnStatus(status: number) {
51
- return httpErrors[getLanguage()][status] ?? `Unknown error. Status code: ${status}.`
51
+ return httpErrors[getLanguage(ptEn)][status] ?? `Unknown error. Status code: ${status}.`
52
52
  }
53
53
 
54
54
  export function treatHTMLInErrorMessage(text: string, status: number) {
@@ -0,0 +1,32 @@
1
+ class PlanningToolDictionaryHelper {
2
+ static instance: PlanningToolDictionaryHelper | undefined
3
+ private toolExecutionIdPlanningStep: Record<string, string> = {}
4
+ private toolExecutionIdToolStep: Record<string, string> = {}
5
+
6
+ private constructor() {
7
+ PlanningToolDictionaryHelper.instance = this
8
+ }
9
+
10
+ static create() {
11
+ return PlanningToolDictionaryHelper.instance ?? new PlanningToolDictionaryHelper()
12
+ }
13
+
14
+ setMessageIdPlanningStepToolExecutionId(messageId: string, toolExecutionId: string){
15
+ this.toolExecutionIdPlanningStep[toolExecutionId] = messageId
16
+ }
17
+
18
+ setMessageIdToolStepToolExecutionId(messageId: string, toolExecutionId: string){
19
+ this.toolExecutionIdToolStep[toolExecutionId] = messageId
20
+ }
21
+
22
+ getMessageIdPlanningStepFromToolExecutionId(toolExecutionId: string){
23
+ return this.toolExecutionIdPlanningStep[toolExecutionId]
24
+ }
25
+
26
+ getMessageIdToolStepFromToolExecutionId(toolExecutionId: string){
27
+ return this.toolExecutionIdToolStep[toolExecutionId]
28
+ }
29
+ }
30
+
31
+ export const planningToolDictionaryHelper = PlanningToolDictionaryHelper.create()
32
+
@@ -0,0 +1,27 @@
1
+ import { ChatEntry, TextChatEntry } from '../state/ChatEntry'
2
+ import { planningToolDictionaryHelper } from './planning-tool'
3
+
4
+ export const updateToolStep = (messages: ChatEntry[], executionId: string,
5
+ newStatus: 'pending' | 'running' | 'success' | 'error' | 'awaiting_approval') => {
6
+
7
+ // if last message is a user message, no update in tool status is needed
8
+ if (messages[messages.length-1].getValue().agentType === 'user') return
9
+
10
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
11
+ const message = messages.find((message) => `${message.id}` === messageId)
12
+ let update = false
13
+ const messageValue = message?.getValue()
14
+ messageValue?.steps?.map((step) => {
15
+ if (step.type === 'step') {
16
+ const tool = step.attempts?.[0].tools?.[0]
17
+ if (tool?.executionId === executionId && step.status !== newStatus) {
18
+ step.status = newStatus
19
+ update = true
20
+ }
21
+ }
22
+ })
23
+
24
+ if (update) {
25
+ message?.setValue({ ...messageValue as TextChatEntry })
26
+ }
27
+ }
@@ -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, useWidget } from '../../context/hooks'
12
+ import { useChatEntry, useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
13
13
  import { useMidnightUpdateView } from '../../hooks/midnight-update-view'
14
14
  import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
15
15
  import { useDateFormatter } from '../../utils/date'
@@ -17,7 +17,7 @@ import { toolById } from '../../utils/tools'
17
17
  import { AgentInfo } from './AgentInfo'
18
18
  import { useChatScrollToBottomEffect } from './chat-scroll'
19
19
  import { onCopyAll, onCopyCode, onLikeOrDislike } from './events'
20
- import { StepsList } from './StepsList'
20
+ import { StepsList, StepsPlaceholder } from './StepsList'
21
21
 
22
22
  export interface CustomRenderResult {
23
23
  /**
@@ -218,6 +218,7 @@ 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
221
222
 
222
223
  useChatScrollToBottomEffect(ref, [entry])
223
224
  useMidnightUpdateView()
@@ -343,7 +344,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
343
344
  widget.set('panel', 'resources')
344
345
  }
345
346
 
346
- return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length) && (
347
+ return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length || isPlanning) && (
347
348
  <li key={entry.messageId} className={entry.agentType} ref={ref}>
348
349
  <div className="chat-message-container"
349
350
  onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
@@ -356,11 +357,14 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
356
357
  {!!entry.badges?.length && <div className="badges">
357
358
  {entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
358
359
  </div>}
360
+
361
+ {!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
362
+
359
363
  {renderContent()}
360
364
 
361
- {!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
362
365
  </div>
363
366
  )}
367
+ {isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder /> }
364
368
 
365
369
  {entry.error && <Alert type="error">{entry.error}</Alert>}
366
370
  </div>