@stack-spot/ai-chat-widget 2.3.0-alpha.1 → 2.3.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +119 -4
  2. package/dist/StackspotAIWidget.d.ts +1 -5
  3. package/dist/StackspotAIWidget.d.ts.map +1 -1
  4. package/dist/StackspotAIWidget.js +2 -2
  5. package/dist/StackspotAIWidget.js.map +1 -1
  6. package/dist/app-metadata.json +6 -6
  7. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  8. package/dist/chat-interceptors/quick-commands.js +8 -3
  9. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  10. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  11. package/dist/chat-interceptors/send-message.js +140 -1
  12. package/dist/chat-interceptors/send-message.js.map +1 -1
  13. package/dist/state/ChatEntry.d.ts.map +1 -1
  14. package/dist/state/ChatState.d.ts +8 -0
  15. package/dist/state/ChatState.d.ts.map +1 -1
  16. package/dist/state/ChatState.js.map +1 -1
  17. package/dist/utils/chat.d.ts.map +1 -1
  18. package/dist/utils/chat.js +1 -0
  19. package/dist/utils/chat.js.map +1 -1
  20. package/dist/utils/check-is-trial.d.ts.map +1 -1
  21. package/dist/utils/check-is-trial.js +2 -6
  22. package/dist/utils/check-is-trial.js.map +1 -1
  23. package/dist/utils/knowledge-source.d.ts +2 -2
  24. package/dist/utils/planning-tool.d.ts +17 -0
  25. package/dist/utils/planning-tool.d.ts.map +1 -0
  26. package/dist/utils/planning-tool.js +32 -0
  27. package/dist/utils/planning-tool.js.map +1 -0
  28. package/dist/utils/update-tool-step.d.ts +3 -0
  29. package/dist/utils/update-tool-step.d.ts.map +1 -0
  30. package/dist/utils/update-tool-step.js +23 -0
  31. package/dist/utils/update-tool-step.js.map +1 -0
  32. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  33. package/dist/views/Agents/AgentsTab.js +3 -4
  34. package/dist/views/Agents/AgentsTab.js.map +1 -1
  35. package/dist/views/Agents/useAgentFavorites.d.ts.map +1 -1
  36. package/dist/views/Agents/useAgentFavorites.js +1 -3
  37. package/dist/views/Agents/useAgentFavorites.js.map +1 -1
  38. package/dist/views/Chat/ChatMessage.d.ts +1 -1
  39. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  40. package/dist/views/Chat/ChatMessage.js +21 -6
  41. package/dist/views/Chat/ChatMessage.js.map +1 -1
  42. package/dist/views/Chat/StepsList.d.ts +12 -2
  43. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  44. package/dist/views/Chat/StepsList.js +156 -18
  45. package/dist/views/Chat/StepsList.js.map +1 -1
  46. package/dist/views/Chat/styled.d.ts.map +1 -1
  47. package/dist/views/Chat/styled.js +17 -10
  48. package/dist/views/Chat/styled.js.map +1 -1
  49. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  50. package/dist/views/MessageInput/AgentSelector.js +2 -8
  51. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  52. package/dist/views/MessageInput/ButtonAgent.js +1 -1
  53. package/dist/views/MessageInput/ButtonAgent.js.map +1 -1
  54. package/dist/views/MessageInput/ButtonBar.d.ts +1 -2
  55. package/dist/views/MessageInput/ButtonBar.d.ts.map +1 -1
  56. package/dist/views/MessageInput/ButtonBar.js +3 -2
  57. package/dist/views/MessageInput/ButtonBar.js.map +1 -1
  58. package/dist/views/MessageInput/ModelSwitcher/index.d.ts +2 -0
  59. package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -0
  60. package/dist/views/MessageInput/ModelSwitcher/index.js +25 -0
  61. package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -0
  62. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts +30 -0
  63. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts.map +1 -0
  64. package/dist/views/MessageInput/ModelSwitcher/utils.js +91 -0
  65. package/dist/views/MessageInput/ModelSwitcher/utils.js.map +1 -0
  66. package/dist/views/MessageInput/QuickCommandSelector.js +1 -1
  67. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  68. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  69. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  70. package/dist/views/MessageInput/dictionary.js +6 -0
  71. package/dist/views/MessageInput/dictionary.js.map +1 -1
  72. package/dist/views/MessageInput/index.d.ts +1 -2
  73. package/dist/views/MessageInput/index.d.ts.map +1 -1
  74. package/dist/views/MessageInput/index.js +2 -2
  75. package/dist/views/MessageInput/index.js.map +1 -1
  76. package/dist/views/MessageInput/styled.d.ts +12 -0
  77. package/dist/views/MessageInput/styled.d.ts.map +1 -1
  78. package/dist/views/MessageInput/styled.js +35 -0
  79. package/dist/views/MessageInput/styled.js.map +1 -1
  80. package/dist/views/Resources.js.map +1 -1
  81. package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
  82. package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
  83. package/dist/views/Steps/FlowChart/layout.d.ts +1 -1
  84. package/dist/views/Steps/FlowChart/layout.d.ts.map +1 -1
  85. package/dist/views/Steps/FlowChart/layout.js +1 -0
  86. package/dist/views/Steps/FlowChart/layout.js.map +1 -1
  87. package/dist/views/Steps/FlowChart/types.d.ts +1 -1
  88. package/dist/views/Steps/FlowChart/types.d.ts.map +1 -1
  89. package/dist/views/Steps/StepModal.js +2 -2
  90. package/dist/views/Steps/StepModal.js.map +1 -1
  91. package/dist/views/Steps/dictionary.d.ts +1 -1
  92. package/dist/views/Steps/utils.d.ts +1 -1
  93. package/dist/views/Steps/utils.d.ts.map +1 -1
  94. package/package.json +4 -4
  95. package/src/StackspotAIWidget.tsx +1 -7
  96. package/src/app-metadata.json +6 -6
  97. package/src/chat-interceptors/quick-commands.ts +10 -3
  98. package/src/chat-interceptors/send-message.ts +156 -2
  99. package/src/state/ChatEntry.ts +6 -6
  100. package/src/state/ChatState.ts +8 -0
  101. package/src/utils/chat.ts +1 -0
  102. package/src/utils/check-is-trial.ts +2 -5
  103. package/src/utils/knowledge-source.ts +2 -2
  104. package/src/utils/planning-tool.ts +41 -0
  105. package/src/utils/update-tool-step.tsx +27 -0
  106. package/src/views/Agents/AgentsTab.tsx +3 -4
  107. package/src/views/Agents/useAgentFavorites.ts +1 -3
  108. package/src/views/Chat/ChatMessage.tsx +39 -18
  109. package/src/views/Chat/StepsList.tsx +340 -44
  110. package/src/views/Chat/styled.ts +17 -10
  111. package/src/views/MessageInput/AgentSelector.tsx +2 -7
  112. package/src/views/MessageInput/ButtonAgent.tsx +1 -1
  113. package/src/views/MessageInput/ButtonBar.tsx +3 -3
  114. package/src/views/MessageInput/ModelSwitcher/index.tsx +67 -0
  115. package/src/views/MessageInput/ModelSwitcher/utils.tsx +143 -0
  116. package/src/views/MessageInput/QuickCommandSelector.tsx +1 -1
  117. package/src/views/MessageInput/dictionary.ts +6 -0
  118. package/src/views/MessageInput/index.tsx +4 -4
  119. package/src/views/MessageInput/styled.ts +37 -0
  120. package/src/views/Resources.tsx +0 -1
  121. package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
  122. package/src/views/Steps/FlowChart/layout.ts +1 -0
  123. package/src/views/Steps/FlowChart/types.ts +1 -1
  124. 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.3.0-alpha.1",
4
- "date": "Mon Oct 27 2025 16:14:00 GMT-0300 (Horário Padrão de Brasília)",
3
+ "version": "2.3.1-beta.1",
4
+ "date": "Thu Nov 06 2025 13:51:17 GMT+0000 (Coordinated Universal Time)",
5
5
  "dependencies": [
6
6
  {
7
7
  "name": "@stack-spot/app-metadata",
@@ -109,19 +109,19 @@
109
109
  },
110
110
  {
111
111
  "name": "@stack-spot/citric-icons",
112
- "version": "0.2.2"
112
+ "version": "0.2.5"
113
113
  },
114
114
  {
115
115
  "name": "@stack-spot/citric-react",
116
- "version": "0.36.0(@stack-spot/citric-icons@0.2.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@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)"
116
+ "version": "0.36.0(@stack-spot/citric-icons@0.2.5)(@stack-spot/portal-theme@1.2.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
117
117
  },
118
118
  {
119
119
  "name": "@stack-spot/portal-components",
120
- "version": "2.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)"
120
+ "version": "2.27.3(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.13.0(react@18.2.0))(@citric/ui@6.10.2(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@citric/icons@5.13.0(react@18.2.0))(lodash@4.17.21)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-theme@1.2.1(@citric/core@6.4.0(lodash@4.17.21)(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(styled-components@6.1.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.3.11)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
121
121
  },
122
122
  {
123
123
  "name": "@stack-spot/portal-network",
124
- "version": "0.181.0(@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)"
124
+ "version": "0.190.0-beta.1(@stack-spot/auth@6.1.0)(@stack-spot/opa@2.5.0(@stack-spot/auth@6.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@stack-spot/portal-translate@2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@tanstack/react-query@5.59.16(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)"
125
125
  },
126
126
  {
127
127
  "name": "@stack-spot/portal-theme",
@@ -245,9 +245,16 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
245
245
  await (currentStep.type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
246
246
 
247
247
  let nextIndex = currentIndex + 1
248
- if (currentStep.next_step_slug) {
249
- nextIndex = currentStep.next_step_slug === 'end' ?
250
- qc.steps.length : qc.steps?.findIndex((step) => step.slug === currentStep.next_step_slug)
248
+
249
+ let nextStepSlug = currentStep.next_step_slug
250
+ const stepResult = ctx.resultMap[currentStep.slug]
251
+ if (stepResult && typeof stepResult !== 'string' && 'answer_status' in stepResult && !!stepResult.answer_status?.next_step_slug) {
252
+ nextStepSlug = stepResult.answer_status.next_step_slug
253
+ }
254
+
255
+ if (nextStepSlug) {
256
+ nextIndex =nextStepSlug === 'end' ?
257
+ qc.steps.length : qc.steps?.findIndex((step) => step.slug === nextStepSlug)
251
258
  }
252
259
  await runStepsRecursively(nextIndex, progress, ctx, iteration)
253
260
  }
@@ -1,11 +1,13 @@
1
- import { aiClient, ChatResponseWithSteps, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
1
+ import { AgentInfo, aiClient, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
2
2
  import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
3
+ import { findLast } from 'lodash'
3
4
  import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
4
5
  import { ChatState } from '../state/ChatState'
5
6
  import { LabeledWithImage } from '../state/types'
6
7
  import { buildConversationContext } from '../utils/chat'
7
8
  import { treatHTMLInErrorMessage } from '../utils/error'
8
9
  import { genericSourcesToKnowledgeSources } from '../utils/knowledge-source'
10
+ import { planningToolDictionaryHelper } from '../utils/planning-tool'
9
11
 
10
12
  /**
11
13
  * Transforms a chat response from the backend into a chat entry that can be added to the chat.
@@ -22,7 +24,7 @@ function createEntryValueFromChatResponse(
22
24
  agent: LabeledWithImage | undefined,
23
25
  includeDate = false,
24
26
  ): TextChatEntry {
25
- return {
27
+ const entry = {
26
28
  agentType: 'bot',
27
29
  type: 'md',
28
30
  content: response.answer ?? '',
@@ -33,6 +35,7 @@ function createEntryValueFromChatResponse(
33
35
  steps: response.steps,
34
36
  tools: response.tools,
35
37
  }
38
+ return entry as TextChatEntry
36
39
  }
37
40
 
38
41
  function buildPrompt(content: string, data?: any) {
@@ -61,6 +64,19 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
61
64
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
62
65
  chat.untitled = false
63
66
  }
67
+
68
+ const messages = chat.getMessages()
69
+
70
+ //Verify if the last planning in the messages has status awaiting_approval
71
+ const getLastPlanningAwaiting = () => findLast(messages, item => {
72
+ const steps = item.getValue().steps
73
+ if (steps) {
74
+ const hasPlanning = steps.find((step) => step.type === 'planning')
75
+ return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
76
+ }
77
+ return false
78
+ })
79
+
64
80
  const stream = aiClient.sendChatMessage({ context, user_prompt: buildPrompt(content, data) })
65
81
  signal.addEventListener('abort', () => stream.cancel())
66
82
  const botEntry = ChatEntry.createStreamedBotEntry()
@@ -69,15 +85,153 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
69
85
  chat.pushMessage(botEntry)
70
86
  }
71
87
  let knowledgeSources: KnowledgeSource[] | undefined
88
+
89
+ const updatePlanningMessage = () => {
90
+ const lastPlanningAwaiting = getLastPlanningAwaiting()
91
+
92
+ if (lastPlanningAwaiting) {
93
+ const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
94
+ const originalItemValue = originalItem?.getValue()
95
+ originalItemValue?.steps?.map((step) => {
96
+ if (step.type === 'planning' && step.status === 'awaiting_approval') {
97
+ step.status = 'success'
98
+ }
99
+ })
100
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
101
+ }
102
+ }
103
+
104
+ const updateStepMessage = (step: ChatStep) => {
105
+ const lastPlanningAwaiting = getLastPlanningAwaiting()
106
+
107
+ if (lastPlanningAwaiting) {
108
+ const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
109
+ const originalItemValue = originalItem?.getValue()
110
+ originalItemValue?.steps?.filter((messageStep) => {
111
+ if (messageStep.id === step.id) {
112
+ messageStep = { ...step }
113
+ }
114
+ })
115
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
116
+ }
117
+ }
118
+
119
+ const updateToolStatus = (agentInfo: AgentInfo) => {
120
+ const executionId = agentInfo.id
121
+ if (executionId) {
122
+ //Update message with type step which contains the planning steps
123
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
124
+ const originalItem = messages.find((message) => `${message.id}` === messageId)
125
+ const originalItemValue = originalItem?.getValue()
126
+ let update = false
127
+ const status = agentInfo.action === 'start' ? 'running' : 'success'
128
+ const step = originalItemValue?.steps
129
+ ?.find(step => step.type === 'step' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
130
+ if (step && step.status !== status) {
131
+ step.status = status
132
+ update = true
133
+ }
134
+ if (update) {
135
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
136
+ }
137
+
138
+ //Updates message with type tool which contains the actually tool steps
139
+ //We only want to show tools banner when they are awaiting_approval, by removing the step
140
+ // we avoid the entire bot message to be visible
141
+ const toolMessageId = planningToolDictionaryHelper.getMessageIdToolStepFromToolExecutionId(executionId)
142
+ const toolOriginalItem = messages.find((message) => `${message.id}` === toolMessageId)
143
+ const toolOriginalItemValue = toolOriginalItem?.getValue()
144
+ const toolStep = toolOriginalItemValue?.steps?.find(step =>
145
+ step.type === 'tool' && step.attempts?.[0]?.tools?.[0]?.executionId === executionId)
146
+ update = false
147
+ if (toolOriginalItemValue && toolStep && toolStep.status !== status) {
148
+ toolOriginalItemValue.steps = undefined
149
+ update = true
150
+ }
151
+ if (update) {
152
+ toolOriginalItem?.setValue({ ...toolOriginalItemValue as TextChatEntry })
153
+ }
154
+ }
155
+ }
156
+
72
157
  stream.onChange(value => {
158
+ if (value.agent_info?.type === 'planning') {
159
+ if (value.agent_info.action === 'start') {
160
+ chat.set('isPlaning', true)
161
+ } else {
162
+ chat.set('isPlaning', false)
163
+ }
164
+ }
165
+
166
+ if (value.agent_info?.type === 'chat' && value.agent_info?.action === 'end') {
167
+ //When an error happens, the step can still be running, so we enforce the error
168
+ const stepRunning = findLast(value.steps, (item) => item.status === 'running')
169
+ if (stepRunning?.status) {
170
+ stepRunning.status = 'error'
171
+ }
172
+ }
173
+
73
174
  if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
74
175
  knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
75
176
  }
177
+
178
+ const lastPlanningAwaiting = getLastPlanningAwaiting()
179
+ if (lastPlanningAwaiting && value.steps) {
180
+ value.steps?.map(step => {
181
+ if (step.type === 'planning') {
182
+ updatePlanningMessage()
183
+ } else if (step.type === 'step') {
184
+ updateStepMessage(step)
185
+ }
186
+ })
187
+ }
188
+
189
+ if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
190
+ updateToolStatus(value.agent_info)
191
+ }
192
+
193
+ if (value.steps) {
194
+ const tool = findLast(value.steps, (item) => item.type === 'tool')
195
+ if (tool && tool.status === 'running') {
196
+ const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(tool.id)
197
+ const originalItem = messages.find((message) => `${message.id}` === messageId)
198
+ const originalItemValue = originalItem?.getValue()
199
+ let update = false
200
+ const step = originalItemValue?.steps?.find(step => step.type === 'step')
201
+ if (step && step.attempts?.[0]?.tools?.[0]?.executionId === tool.id) {
202
+ step.attempts?.forEach((attempt, i) => {
203
+ const newAttempt = tool.attempts?.[i]
204
+ if (!newAttempt) return
205
+ attempt.tools = attempt.tools?.map((origTool, j) => {
206
+ const newTool = newAttempt.tools?.[j]
207
+ if (!newTool || origTool?.executionId !== newTool?.executionId) return origTool
208
+ update = true
209
+ return { ...origTool, ...newTool }
210
+ })
211
+ })
212
+ if (step.attempts.length < tool.attempts.length) {
213
+ step.attempts.push(tool.attempts[tool.attempts.length - 1])
214
+ }
215
+ }
216
+ if (update) {
217
+ originalItem?.setValue({ ...originalItemValue as TextChatEntry })
218
+ }
219
+ }
220
+ }
221
+
76
222
  botEntry.setValue(createEntryValueFromChatResponse(value, knowledgeSources, chat.get('agent')))
77
223
  })
224
+
78
225
  let finalValue: Partial<ChatResponse3> | undefined
79
226
  try {
80
227
  finalValue = await stream.getValue()
228
+
229
+ const lastPlanningAwaiting = getLastPlanningAwaiting()
230
+ if (lastPlanningAwaiting) {
231
+ const value = lastPlanningAwaiting.getValue()
232
+ value.content = finalValue.answer || value.content
233
+ lastPlanningAwaiting.setValue(value)
234
+ }
81
235
  // if the streaming feature is not enabled, we only add the chat entry once the streaming has finished
82
236
  if (!chat.get('features').streaming) {
83
237
  chat.pushMessage(botEntry)
@@ -4,7 +4,7 @@ import { pull } from 'lodash'
4
4
  import { LabeledAgent } from './types'
5
5
 
6
6
  export interface ActionDataClick {
7
- name?: string,
7
+ name?: string,
8
8
  value?: string[],
9
9
  }
10
10
 
@@ -44,9 +44,9 @@ export interface ChatAction extends SerializableAction {
44
44
  * @default button
45
45
  */
46
46
  buttonType?: 'submit' | 'button',
47
- /**
48
- * @default lg
49
- */
47
+ /**
48
+ * @default lg
49
+ */
50
50
  size?: 'lg' | 'md' | 'sm',
51
51
  }
52
52
 
@@ -149,7 +149,7 @@ export interface TextChatEntry {
149
149
  /*
150
150
  * Options for radio, checkbox or button type.
151
151
  */
152
- options?: { color?: ColorSchemeName, label: string, value?: string, hasInput?: boolean }[],
152
+ options?: { color?: ColorSchemeName, label: string, value?: string, hasInput?: boolean}[],
153
153
  /**
154
154
  * Name to be used in input type fields.
155
155
  */
@@ -211,7 +211,7 @@ export class ChatEntry {
211
211
  type: isMd ? 'md' : 'text',
212
212
  content,
213
213
  name: fieldName,
214
- hiddenContent,
214
+ hiddenContent,
215
215
  data,
216
216
  updated: new Date().toISOString(),
217
217
  })
@@ -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
  */
@@ -55,6 +59,10 @@ export interface ChatPropertiesWithOptionalFeatures {
55
59
  * If a feature is marked as false, it's disabled, otherwise it's enabled.
56
60
  */
57
61
  features?: Partial<ChatFeatures>,
62
+ /**
63
+ * The current LLM (Large Language Model) being used for this chat.
64
+ */
65
+ selected_model_id?: string,
58
66
  }
59
67
 
60
68
  export interface ChatProperties extends ChatPropertiesWithOptionalFeatures {
package/src/utils/chat.ts CHANGED
@@ -26,5 +26,6 @@ export function buildConversationContext(state: ChatState, message?: ChatEntry):
26
26
  platform: 'web-widget',
27
27
  platform_version: navigator.userAgent,
28
28
  stackspot_ai_version: appData.version,
29
+ selected_model_id: state.get('selected_model_id'),
29
30
  }
30
31
  }
@@ -1,9 +1,6 @@
1
1
  import { NetworkClient } from '@stack-spot/portal-network'
2
2
 
3
3
  export const checkIsTrial = () => {
4
- const hasSession = NetworkClient.sessionManager?.hasSession()
5
- if (hasSession){
6
- const trialInfo = NetworkClient.sessionManager?.getSession()?.getTokenData()?.trial_account_status
7
- return !!trialInfo
8
- } return false
4
+ const trialInfo = NetworkClient.sessionManager?.getSession()?.getTokenData()?.trial_account_status
5
+ return !!trialInfo
9
6
  }
@@ -1,4 +1,4 @@
1
- import { DocumentResponse, SourceKnowledgeSource, SourceProjectFile4, SourceStackAi } from '@stack-spot/portal-network/api/ai'
1
+ import { DocumentResponse, SourceKnowledgeSource, SourceProjectFile3, SourceStackAi } from '@stack-spot/portal-network/api/ai'
2
2
  import { KnowledgeSource } from '../state/ChatEntry'
3
3
 
4
4
  /**
@@ -60,7 +60,7 @@ export function extractCodeFromKSDocument(document: DocumentResponse): { languag
60
60
  * @returns a list of knowledge sources in the format expected by the chat.
61
61
  */
62
62
  export function genericSourcesToKnowledgeSources(
63
- sources: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile4)[] | undefined,
63
+ sources: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile3)[] | undefined,
64
64
  ): KnowledgeSource[] | undefined {
65
65
  return sources?.filter(s => s.type === 'knowledge_source').map(ks => {
66
66
  const { document_id: documentId, document_score: documentScore, name, slug } = ks as SourceKnowledgeSource
@@ -0,0 +1,41 @@
1
+ class PlanningToolDictionaryHelper {
2
+ static instance: PlanningToolDictionaryHelper | undefined
3
+ private toolExecutionIdPlanningStep: Record<string, string> = {}
4
+ private toolExecutionIdToolStep: Record<string, string> = {}
5
+ private stepId: Record<string, number> = {}
6
+
7
+ private constructor() {
8
+ PlanningToolDictionaryHelper.instance = this
9
+ }
10
+
11
+ static create() {
12
+ return PlanningToolDictionaryHelper.instance ?? new PlanningToolDictionaryHelper()
13
+ }
14
+
15
+ setMessageIdPlanningStepToolExecutionId(messageId: string, toolExecutionId: string){
16
+ this.toolExecutionIdPlanningStep[toolExecutionId] = messageId
17
+ }
18
+
19
+ setMessageIdToolStepToolExecutionId(messageId: string, toolExecutionId: string){
20
+ this.toolExecutionIdToolStep[toolExecutionId] = messageId
21
+ }
22
+
23
+ setMessageIdForStepId(messageId: number, stepId: string){
24
+ this.stepId[stepId] = messageId
25
+ }
26
+
27
+ getMessageIdFromStepId(stepId: string){
28
+ return this.stepId[stepId]
29
+ }
30
+
31
+ getMessageIdPlanningStepFromToolExecutionId(toolExecutionId: string){
32
+ return this.toolExecutionIdPlanningStep[toolExecutionId]
33
+ }
34
+
35
+ getMessageIdToolStepFromToolExecutionId(toolExecutionId: string){
36
+ return this.toolExecutionIdToolStep[toolExecutionId]
37
+ }
38
+ }
39
+
40
+ export const planningToolDictionaryHelper = PlanningToolDictionaryHelper.create()
41
+
@@ -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
+ }
@@ -7,7 +7,7 @@ import { useMemo, useState } from 'react'
7
7
  import { NavigationComponent } from '../../components/ComponentNavigator'
8
8
  import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
9
9
  import { WorkspaceTabNavigator } from '../../components/WorkspaceTabNavigator'
10
- import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
10
+ import { useCurrentChat } from '../../context/hooks'
11
11
  import { useRightPanel } from '../../right-panel/hooks'
12
12
  import { ChatProperties } from '../../state/ChatState'
13
13
  import { AgentDescription } from './AgentDescription'
@@ -28,11 +28,10 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
28
28
  const { useFavorites, onAddFavorite, onRemoveFavorite } = useAgentFavorites()
29
29
  const [submitEnabled, setSubmitEnabled] = useState(false)
30
30
  const listFavorites = useFavorites()
31
- const isAgentEnabled = useCurrentChatState('features').agent
32
- const agentDefault = agentToolsClient.agentDefault.useQuery({ enabled: isAgentEnabled })
31
+ const agentDefault = agentToolsClient.agentDefault.useQuery()
33
32
  const agents = workspaceId
34
33
  ? workspaceAiClient.getAgentFromWorkspaceAi.useQuery({ workspaceId }) as AgentResponseWithBuiltIn[]
35
- : agentToolsClient.agents.useQuery({ visibility }, { enabled: isAgentEnabled })
34
+ : agentToolsClient.agents.useQuery({ visibility })
36
35
 
37
36
  const initialValue = useMemo<AgentResponseWithBuiltIn | undefined>(
38
37
  () => {
@@ -1,10 +1,8 @@
1
1
  /* eslint-disable filenames/match-regex */
2
2
  import { agentToolsClient } from '@stack-spot/portal-network'
3
- import { useCurrentChatState } from '../../context/hooks'
4
3
 
5
4
  export function useAgentFavorites() {
6
- const isAgentEnabled = useCurrentChatState('features').agent
7
- const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' }, { enabled: isAgentEnabled })
5
+ const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' })
8
6
  const [addFavorite, pendingAddFav] = agentToolsClient.addFavorite.useMutation()
9
7
  const [removeFavorite, pendingRemoveFav] = agentToolsClient.removeFavorite.useMutation()
10
8
 
@@ -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, useChatMessages, 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, ViewToolsDetails } from './StepsList'
21
21
 
22
22
  export interface CustomRenderResult {
23
23
  /**
@@ -69,12 +69,12 @@ interface Props extends CustomMessage {
69
69
  isLast: boolean,
70
70
  }
71
71
 
72
- interface RenderInputsEntryProps {
73
- isLast: boolean,
74
- entry: TextChatEntry,
75
- value: string[],
72
+ interface RenderInputsEntryProps {
73
+ isLast: boolean,
74
+ entry: TextChatEntry,
75
+ value: string[],
76
76
  setValue: Dispatch<React.SetStateAction<string[]>>,
77
- labels: string[],
77
+ labels: string[],
78
78
  setLabels: Dispatch<React.SetStateAction<string[]>>,
79
79
  }
80
80
 
@@ -113,7 +113,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
113
113
  <Input name={entry.name} onChange={v => setValue([v])} required style={{ height: '30px', width: '33%' }} />}
114
114
  </Row>
115
115
  )}
116
- />
116
+ />
117
117
  }
118
118
 
119
119
  if (entry.type === 'button-list') {
@@ -145,7 +145,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
145
145
  renderLabel={o => (
146
146
  <Row gap={3}>
147
147
  <Text>{o.label}</Text>
148
- {o.hasInput && o.value && labels.findIndex((label) => label === o.value)!== -1 &&
148
+ {o.hasInput && o.value && labels.findIndex((label) => label === o.value) !== -1 &&
149
149
  <div style={{ width: '33%' }}>
150
150
  <Input
151
151
  name={entry.name}
@@ -158,7 +158,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
158
158
  const newValue = [...value]
159
159
  newValue[customIndex] = v
160
160
  setValue(newValue)
161
- }
161
+ }
162
162
  }}
163
163
  required={true}
164
164
  style={{ height: '30px' }}
@@ -218,6 +218,20 @@ 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
+
223
+ // when we have a steps but we are not showing any content of the step
224
+ // (because it is a tool and the user has already answered the question)
225
+ // we do not want to show an avatar with empty content, so we hide the entire message
226
+ const toolsStep = entry.steps?.find(s => s.type === 'tool')
227
+ const messages = useChatMessages(chat.id)
228
+ const userHasAlreadyAnswered = useMemo(() => {
229
+ const messageIndex = messages.findIndex((messageItem) => messageItem.id === message.id)
230
+ if (messages.length - 1 === messageIndex) return false
231
+ const nextMessage = messages[messageIndex + 1].getValue()
232
+ return nextMessage.agentType === 'user'
233
+ }, [messages, messages.length])
234
+ const isMessageHidden = toolsStep && userHasAlreadyAnswered
221
235
 
222
236
  useChatScrollToBottomEffect(ref, [entry])
223
237
  useMidnightUpdateView()
@@ -263,7 +277,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
263
277
  }
264
278
  }
265
279
 
266
- const renderActions = useCallback(()=> <> {entry.actions?.length && (
280
+ const renderActions = useCallback(() => <> {entry.actions?.length && (
267
281
  <div className="actions">
268
282
  {entry.actions.map(
269
283
  (a, index) => (<>
@@ -343,7 +357,8 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
343
357
  widget.set('panel', 'resources')
344
358
  }
345
359
 
346
- return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length) && (
360
+ return (entry.content || entry.error || !!entry.steps?.length ||
361
+ entry.upload?.length) && (!isMessageHidden || !toolsStep || isPlanning) && (
347
362
  <li key={entry.messageId} className={entry.agentType} ref={ref}>
348
363
  <div className="chat-message-container"
349
364
  onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
@@ -356,12 +371,16 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
356
371
  {!!entry.badges?.length && <div className="badges">
357
372
  {entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
358
373
  </div>}
374
+
375
+ {!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
376
+ userHasAlreadyAnswered={userHasAlreadyAnswered} />}
377
+
359
378
  {renderContent()}
360
-
361
- {!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
379
+
362
380
  </div>
363
381
  )}
364
-
382
+ {isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />}
383
+
365
384
  {entry.error && <Alert type="error">{entry.error}</Alert>}
366
385
  </div>
367
386
  {afterMessage && createElement(afterMessage, { message })}
@@ -402,7 +421,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
402
421
  />
403
422
  </ImageBox>
404
423
  ))}
405
- {entry.tools?.map((id) => {
424
+ {entry?.tools?.map((id) => {
406
425
  const tool = toolById(id, toolKits)
407
426
  return (
408
427
  <ImageBox key={id} className="agent-info-avatar-resource">
@@ -414,10 +433,12 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
414
433
  title={tool?.name}
415
434
  />
416
435
  </ImageBox>
417
- )})}
436
+ )
437
+ })}
418
438
  </Button>
439
+ <ViewToolsDetails chatId={chat.id} />
419
440
  </div>}
420
-
441
+
421
442
  {shouldShowFooter && <div className="message-footer">
422
443
  {entry.agentType === 'bot' && !entry.error && <div className="message-actions">
423
444
  {entry.type === 'md' && (