@stack-spot/ai-chat-widget 3.3.2-beta.5 → 3.3.4-beta.5

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 (40) hide show
  1. package/CHANGELOG.md +53 -16
  2. package/dist/app-metadata.json +3 -3
  3. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  4. package/dist/chat-interceptors/quick-commands.js +40 -1
  5. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  6. package/dist/chat-interceptors/send-message.d.ts +1 -1
  7. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  8. package/dist/chat-interceptors/send-message.js +25 -29
  9. package/dist/chat-interceptors/send-message.js.map +1 -1
  10. package/dist/components/form/DescribedRadioGroup.d.ts +6 -1
  11. package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
  12. package/dist/components/form/DescribedRadioGroup.js +43 -19
  13. package/dist/components/form/DescribedRadioGroup.js.map +1 -1
  14. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  15. package/dist/views/Agents/AgentsTab.js +30 -10
  16. package/dist/views/Agents/AgentsTab.js.map +1 -1
  17. package/dist/views/Agents/useAgentFavorites.d.ts +1 -1
  18. package/dist/views/Agents/useAgentFavorites.js +3 -3
  19. package/dist/views/Agents/useAgentFavorites.js.map +1 -1
  20. package/dist/views/Chat/ChatMessage.js +1 -1
  21. package/dist/views/Chat/ChatMessage.js.map +1 -1
  22. package/dist/views/Chat/StepsList.d.ts +2 -1
  23. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  24. package/dist/views/Chat/StepsList.js +8 -6
  25. package/dist/views/Chat/StepsList.js.map +1 -1
  26. package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -1
  27. package/dist/views/MessageInput/ModelSwitcher/index.js +1 -1
  28. package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -1
  29. package/dist/views/Steps/dictionary.d.ts +1 -1
  30. package/package.json +2 -2
  31. package/src/app-metadata.json +3 -3
  32. package/src/chat-interceptors/quick-commands.ts +60 -5
  33. package/src/chat-interceptors/send-message.ts +25 -30
  34. package/src/components/form/DescribedRadioGroup.tsx +94 -41
  35. package/src/index.ts +0 -1
  36. package/src/views/Agents/AgentsTab.tsx +69 -37
  37. package/src/views/Agents/useAgentFavorites.ts +3 -3
  38. package/src/views/Chat/ChatMessage.tsx +1 -1
  39. package/src/views/Chat/StepsList.tsx +9 -7
  40. package/src/views/MessageInput/ModelSwitcher/index.tsx +2 -1
@@ -47,22 +47,17 @@ function buildPrompt(content: string, data?: any) {
47
47
  }
48
48
 
49
49
  //Verify if the last planning in the messages has status awaiting_approval
50
- const getLastPlanningAwaiting = (chat: ChatState) => {
51
- const messages = chat.getMessages()
52
-
53
- return findLast(messages, item => {
54
- const steps = item.getValue().steps
55
- if (steps) {
56
- const hasPlanning = steps.find((step) => step.type === 'planning')
57
- return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
58
- }
59
- return false
60
- })
61
- }
50
+ const getLastPlanningAwaiting = (messages: ChatEntry[]) => findLast(messages, item => {
51
+ const steps = item.getValue().steps
52
+ if (steps) {
53
+ const hasPlanning = steps.find((step) => step.type === 'planning')
54
+ return hasPlanning ? hasPlanning.status === 'awaiting_approval' : false
55
+ }
56
+ return false
57
+ })
62
58
 
63
- const updateToolStatus = (agentInfo: AgentInfo, chat: ChatState) => {
59
+ const updateToolStatus = (agentInfo: AgentInfo, messages: ChatEntry[]) => {
64
60
  const executionId = agentInfo.id
65
- const messages = chat.getMessages()
66
61
 
67
62
  if (executionId) {
68
63
  //Update message with type step which contains the planning steps
@@ -100,9 +95,8 @@ const updateToolStatus = (agentInfo: AgentInfo, chat: ChatState) => {
100
95
  }
101
96
  }
102
97
 
103
- const updateStepMessage = (step: ChatStep, chat: ChatState) => {
104
- const lastPlanningAwaiting = getLastPlanningAwaiting(chat)
105
- const messages = chat.getMessages()
98
+ const updateStepMessage = (step: ChatStep, messages: ChatEntry[]) => {
99
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
106
100
 
107
101
  if (lastPlanningAwaiting) {
108
102
  const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
@@ -117,9 +111,8 @@ const updateStepMessage = (step: ChatStep, chat: ChatState) => {
117
111
  }
118
112
 
119
113
 
120
- const updatePlanningMessage = (chat: ChatState) => {
121
- const lastPlanningAwaiting = getLastPlanningAwaiting(chat)
122
- const messages = chat.getMessages()
114
+ const updatePlanningMessage = (messages: ChatEntry[]) => {
115
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
123
116
 
124
117
  if (lastPlanningAwaiting) {
125
118
  const originalItem = messages.find((message) => message.id === lastPlanningAwaiting.id)
@@ -133,9 +126,8 @@ const updatePlanningMessage = (chat: ChatState) => {
133
126
  }
134
127
  }
135
128
 
136
- export function helperSendMessage(value: Partial<ChatResponseWithSteps> & { opportunities?: any },
129
+ export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResponseWithSteps> & { opportunities?: any },
137
130
  chat: ChatState, botEntry: ChatEntry, knowledgeSources: KnowledgeSource[] | undefined) {
138
- const messages = chat.getMessages()
139
131
  if (value.agent_info?.type === 'planning') {
140
132
  if (value.agent_info.action === 'start') {
141
133
  chat.set('isPlaning', true)
@@ -156,19 +148,19 @@ export function helperSendMessage(value: Partial<ChatResponseWithSteps> & { oppo
156
148
  knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
157
149
  }
158
150
 
159
- const lastPlanningAwaiting = getLastPlanningAwaiting(chat)
151
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
160
152
  if (lastPlanningAwaiting && value.steps) {
161
153
  value.steps?.map(step => {
162
154
  if (step.type === 'planning') {
163
- updatePlanningMessage(chat)
155
+ updatePlanningMessage(messages)
164
156
  } else if (step.type === 'step') {
165
- updateStepMessage(step, chat)
157
+ updateStepMessage(step, messages)
166
158
  }
167
159
  })
168
160
  }
169
161
 
170
162
  if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
171
- updateToolStatus(value.agent_info, chat)
163
+ updateToolStatus(value.agent_info, messages)
172
164
  }
173
165
 
174
166
  if (value.steps) {
@@ -219,7 +211,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
219
211
  const context = buildConversationContext(chat, entry)
220
212
  chat.set('isLoading', true)
221
213
  const untitled = chat.untitled
222
- const isFirstMessage = chat.getMessages().length === 1
214
+ const messages = chat.getMessages()
215
+ const isFirstMessage = messages.length === 1
223
216
  if (untitled) {
224
217
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
225
218
  chat.untitled = false
@@ -234,13 +227,15 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
234
227
  }
235
228
  let knowledgeSources: KnowledgeSource[] | undefined
236
229
 
237
- stream.onChange(value => helperSendMessage(value, chat, botEntry, knowledgeSources))
230
+ stream.onChange(value => {
231
+ helperSendMessage(messages, value, chat, botEntry, knowledgeSources)
232
+ })
238
233
 
239
234
  let finalValue: Partial<ChatResponse3> | undefined
240
235
  try {
241
236
  finalValue = await stream.getValue()
242
237
 
243
- const lastPlanningAwaiting = getLastPlanningAwaiting(chat)
238
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
244
239
  if (lastPlanningAwaiting) {
245
240
  const value = lastPlanningAwaiting.getValue()
246
241
  value.content = finalValue.answer || value.content
@@ -264,7 +259,7 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
264
259
  }
265
260
  }
266
261
  if (finalValue?.answer) {
267
- botEntry.setValue(createEntryValueFromChatResponse(finalValue, knowledgeSources, chat.get('agent'), true))
262
+ botEntry.setValue(createEntryValueFromChatResponse(finalValue, botEntry.getValue().knowledgeSources, chat.get('agent'), true))
268
263
  aiClient.chat.invalidate({ conversationId: chat.id })
269
264
  if (isFirstMessage) {
270
265
  // if the chat has a title and this was its first message, we need to rename it according to the title, otherwise, the backend will
@@ -1,6 +1,10 @@
1
+ import { LoadingCircular } from '@citric/ui'
1
2
  import { Icon } from '@stack-spot/citric-icons'
2
- import { Accordion, FieldGroup, ImageBox, Input, RadioGroup, Row, Text, useRadioGroupControls } from '@stack-spot/citric-react'
3
+ import { Accordion, Column, FieldGroup, ImageBox, Input, RadioGroup, Row, Text, useRadioGroupControls } from '@stack-spot/citric-react'
4
+ import { InfiniteScroll } from '@stack-spot/portal-components/InfiniteScroll'
5
+ import { isEqual } from 'lodash'
3
6
  import { useEffect, useState } from 'react'
7
+ import styled from 'styled-components'
4
8
  import { ButtonFavorite, Favorite } from '../ButtonFavorite'
5
9
 
6
10
  interface Props<T> {
@@ -15,15 +19,27 @@ interface Props<T> {
15
19
  emptyResults: React.ReactNode,
16
20
  emptyDataset: React.ReactNode,
17
21
  onChange?: (value: T | undefined) => void,
22
+ fetchNextPage?: () => void,
23
+ hasNextPage?: boolean,
24
+ filter?: string,
25
+ setFilter?: (value?: string) => void,
26
+ isLoading?: boolean,
18
27
  }
19
28
 
29
+ const StyledDiv = styled.div`
30
+ &#agents-content {
31
+ overflow: auto;
32
+ }
33
+ `
34
+
20
35
  /**
21
36
  * Renders a radio group where each option has a label and a description.
22
37
  * The description in placed under the label and checkbox as an accordion.
23
38
  *
24
39
  * Also renders a search input.
25
40
  */
26
- export function DescribedRadioGroup<T>({ initialValue, options: opt, data, emptyDataset, emptyResults, onChange }: Props<T>) {
41
+ export function DescribedRadioGroup<T>({ initialValue, options: opt, data, emptyDataset, emptyResults, onChange,
42
+ fetchNextPage, hasNextPage, filter, setFilter, isLoading }: Props<T>) {
27
43
  const [options, setOptions] = useState(opt)
28
44
  const controls = useRadioGroupControls({
29
45
  initialValue,
@@ -37,48 +53,85 @@ export function DescribedRadioGroup<T>({ initialValue, options: opt, data, empty
37
53
  if (controls.value === undefined && initialValue !== undefined) controls.setValue(initialValue)
38
54
  }, [initialValue])
39
55
 
40
- return options.length ? <>
56
+ useEffect(() => {
57
+ if (!isEqual(opt, options)) {
58
+ setOptions(opt)
59
+ }
60
+ }, [opt])
61
+
62
+ return (options?.length || isLoading || !!filter) ? <>
41
63
  <FieldGroup fullWidth>
42
64
  <Icon icon="Search" />
43
- <Input type="search" value={controls.filter} onChange={controls.setFilter} />
65
+ <Input type="search" value={setFilter ? filter : controls.filter} onChange={setFilter ? setFilter : controls.setFilter} />
44
66
  </FieldGroup>
45
- {controls.options.length ? <RadioGroup
46
- options={controls.options}
47
- value={controls.value}
48
- onChange={controls.setValue}
49
- renderKey={controls.renderKey}
50
- className="option-list"
51
- renderItem={(radio, o) => {
52
- const { description, idOrSlug, name, image, listFavorites, onAddFavorite, onRemoveFavorite } = data(o)
53
- return (
54
- <Row className={controls.isUnfilteredButChecked(o) ? 'filtered-out' : ''}>
55
- <Accordion
56
- header={btn => <>
57
- {radio}
58
- {image && <ImageBox size="xs" style={{ fontSize: '16px' }}>{image}</ImageBox>}
59
- <Text>{name}</Text>{btn}</>
67
+ {isLoading ?
68
+ <Column h="100%" alignItems="center" justifyContent="center">
69
+ <LoadingCircular/>
70
+ </Column> :
71
+ <StyledDiv id="agents-content">
72
+ {controls.options.length ?
73
+ <InfiniteScroll scrollableTarget="agents-content"
74
+ dataLength={controls.options.length}
75
+ next={fetchNextPage as any ?? undefined}
76
+ hasMore={hasNextPage ?? false}>
77
+ <RadioGroup
78
+ options={controls.options}
79
+ value={controls.value}
80
+ onChange={controls.setValue}
81
+ renderKey={controls.renderKey}
82
+ className="option-list"
83
+ renderItem={(radio, o) => {
84
+ const item = data(o)
85
+ const { idOrSlug, listFavorites, onAddFavorite, onRemoveFavorite } = item
86
+ return (
87
+ <Row className={controls.isUnfilteredButChecked(o) ? 'filtered-out' : ''}>
88
+ <ItemContent item={item} radio={radio} />
89
+ {onAddFavorite && <ButtonFavorite favorite={{
90
+ idOrSlug: idOrSlug,
91
+ listFavorites,
92
+ onAddFavorite: async (...args) => {
93
+ const res = await onAddFavorite(...args)
94
+ setOptions([...options]) // forces options re-rendering
95
+ return res
96
+ },
97
+ onRemoveFavorite: async (...args) => {
98
+ const res = await onRemoveFavorite?.(...args)
99
+ setOptions([...options]) // forces options re-rendering
100
+ return res ?? false
101
+ },
102
+ }} />}
103
+ </Row>
104
+ )}
60
105
  }
61
- appearance="card"
62
- >
63
- {typeof description === 'string' ? <Text appearance="microtext1" color="light.700">{description}</Text> : description}
64
- </Accordion>
65
- {onAddFavorite && <ButtonFavorite favorite={{
66
- idOrSlug: idOrSlug,
67
- listFavorites,
68
- onAddFavorite: async (...args) => {
69
- const res = await onAddFavorite(...args)
70
- setOptions([...options]) // forces options re-rendering
71
- return res
72
- },
73
- onRemoveFavorite: async (...args) => {
74
- const res = await onRemoveFavorite?.(...args)
75
- setOptions([...options]) // forces options re-rendering
76
- return res ?? false
77
- },
78
- }} />}
79
- </Row>
80
- )
81
- }}
82
- /> : emptyResults}
106
+ />
107
+ </InfiniteScroll>
108
+ : emptyResults}
109
+ </StyledDiv> }
110
+
83
111
  </> : emptyDataset
84
112
  }
113
+
114
+ interface ItemContentProps {
115
+ radio: React.ReactElement,
116
+ item: { name: string, description: React.ReactNode, image?: React.ReactElement } & Favorite<any>,
117
+ }
118
+
119
+ const ItemContent = ({ item, radio }: ItemContentProps) => {
120
+ const [expanded, setExpanded] = useState(false)
121
+ const { description, name, image } = item
122
+
123
+ return <Accordion
124
+ header={btn => <>
125
+ {radio}
126
+ {image && <ImageBox size="xs" style={{ fontSize: '16px' }}>{image}</ImageBox>}
127
+ <Text>{name}</Text>{btn}</>
128
+ }
129
+ expanded={expanded}
130
+ onChange={setExpanded}
131
+ appearance="card"
132
+ >
133
+ {expanded && <>
134
+ {typeof description === 'string' ? <Text appearance="microtext1" color="light.700">{description}</Text> : description}
135
+ </>}
136
+ </Accordion>
137
+ }
package/src/index.ts CHANGED
@@ -21,4 +21,3 @@ export type { ButtonAction } from './types'
21
21
  export { defaultLanguage, languages } from './utils/programming-languages'
22
22
  export type { CustomRenderResult } from './views/Chat/ChatMessage'
23
23
  export { loadChat } from './views/ChatHistory/utils'
24
-
@@ -3,7 +3,8 @@ import { Button } from '@stack-spot/citric-react'
3
3
  import { Placeholder } from '@stack-spot/portal-components/Placeholder'
4
4
  import { AgentResponseWithBuiltIn, agentToolsClient, AgentVisibilityLevel, workspaceAiClient } from '@stack-spot/portal-network'
5
5
  import { WorkspaceResponse } from '@stack-spot/portal-network/api/workspace-ai'
6
- import { useMemo, useState } from 'react'
6
+ import { debounce } from 'lodash'
7
+ import { useCallback, useMemo, useState, useTransition } from 'react'
7
8
  import { NavigationComponent } from '../../components/ComponentNavigator'
8
9
  import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
9
10
  import { WorkspaceTabNavigator } from '../../components/WorkspaceTabNavigator'
@@ -29,16 +30,41 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
29
30
  const [submitEnabled, setSubmitEnabled] = useState(false)
30
31
  const listFavorites = useFavorites()
31
32
  const agentDefault = agentToolsClient.agentDefault.useQuery()
32
- const agents = workspaceId
33
- ? workspaceAiClient.getAgentFromWorkspaceAi.useQuery({ workspaceId }) as AgentResponseWithBuiltIn[]
34
- : agentToolsClient.agents.useQuery({ visibility })
33
+
34
+ const [filter, setFilter] = useState<string | undefined>()
35
+ const [apiFilter, setApiFilter] = useState<string | undefined>()
36
+ const [isPending, startTransition] = useTransition()
37
+
38
+ const runOnChange = useCallback(
39
+ debounce((value?: string) => {
40
+ startTransition(() => {
41
+ setApiFilter(value)
42
+ })
43
+ }, 500),
44
+ [startTransition],
45
+ )
46
+
47
+ const data = workspaceAiClient.getAgentFromWorkspaceAi.useStatefulQuery({ workspaceId: workspaceId! },
48
+ { enabled: !!workspaceId })
49
+ const workspaceAgents = data?.[0] as AgentResponseWithBuiltIn[]
50
+
51
+ const [agentsData=[], { fetchNextPage, hasNextPage }] = agentToolsClient.agentsMultipleFilters.useInfiniteQuery({
52
+ filters: {
53
+ page: 1,
54
+ size: 20,
55
+ name: apiFilter,
56
+ ...(visibility?.length ? { visibility_list: [visibility] } : {}),
57
+ },
58
+ }, { enabled: !workspaceId })
59
+
60
+ const agents = workspaceId ? workspaceAgents : agentsData
35
61
 
36
62
  const initialValue = useMemo<AgentResponseWithBuiltIn | undefined>(
37
63
  () => {
38
64
  const initial = agent.current
39
- ? agents.find(a => a.id === agent.current?.id)
40
- : chat.get('agent') ? agents.find(a => a.id === chat.get('agent')?.id) : agentDefault as unknown as AgentResponseWithBuiltIn
41
- if (initial) setSubmitEnabled(true)
65
+ ? agents?.find(a => a.id === agent.current?.id)
66
+ : chat.get('agent') ? agents?.find(a => a.id === chat.get('agent')?.id) : agentDefault as unknown as AgentResponseWithBuiltIn
67
+ if (initial && !submitEnabled) setSubmitEnabled(true)
42
68
  return initial
43
69
  },
44
70
  [agents],
@@ -49,36 +75,42 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
49
75
  close()
50
76
  }
51
77
 
52
- return (
53
- <>
54
- <div className="content">
55
- <DescribedRadioGroup
56
- options={agents}
57
- initialValue={initialValue}
58
- onChange={(ag) => {
59
- agent.current = ag
60
- ? { ...ag, label: ag.name, image: ag.avatar!, slug: ag.slug, builtIn: ag.visibility_level === 'built_in' }
61
- : undefined
62
- setSubmitEnabled(true)
63
- }}
64
- data={ag => ({
65
- idOrSlug: ag.id,
66
- image: ag.avatar ? <img src={ag.avatar} /> : <Icon icon="Agent" />,
67
- description: <AgentDescription agentId={ag.id} />,
68
- name: ag.name,
69
- listFavorites,
70
- onAddFavorite,
71
- onRemoveFavorite,
72
- })}
73
- emptyResults={
74
- <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />
75
- }
76
- emptyDataset={<Placeholder title={t.noData} description={t.noDataDescription} />}
77
- />
78
- </div>
79
- {!!agents.length && showSubmitButton && <Button onClick={submit} disabled={!submitEnabled}>{t.apply}</Button>}
80
- </>
81
- )
78
+ return <>
79
+ <div className="content">
80
+ <DescribedRadioGroup
81
+ fetchNextPage={fetchNextPage}
82
+ hasNextPage={hasNextPage && !workspaceId}
83
+ options={agents}
84
+ initialValue={initialValue}
85
+ filter={workspaceId ? undefined : filter}
86
+ setFilter={workspaceId ? undefined : (value?: string) => {
87
+ setFilter(value)
88
+ runOnChange(value)
89
+ }}
90
+ onChange={(ag) => {
91
+ agent.current = ag
92
+ ? { ...ag, label: ag.name, image: ag.avatar!, slug: ag.slug, builtIn: ag.visibility_level === 'built_in' }
93
+ : undefined
94
+ setSubmitEnabled(true)
95
+ }}
96
+ isLoading={isPending}
97
+ data={ag => ({
98
+ idOrSlug: ag.id,
99
+ image: ag.avatar ? <img src={ag.avatar} /> : <Icon icon="Agent" />,
100
+ description: <AgentDescription agentId={ag.id} />,
101
+ name: ag.name,
102
+ listFavorites,
103
+ onAddFavorite,
104
+ onRemoveFavorite,
105
+ })}
106
+ emptyResults={
107
+ <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />
108
+ }
109
+ emptyDataset={<Placeholder title={t.noData} description={t.noDataDescription} />}
110
+ />
111
+ </div>
112
+ {!!agents?.length && showSubmitButton && <Button onClick={submit} disabled={!submitEnabled}>{t.apply}</Button>}
113
+ </>
82
114
  }
83
115
 
84
116
  export function AgentsTabWorkspace({ agent, visibility, showSubmitButton }: AgentTabProps) {
@@ -2,14 +2,14 @@
2
2
  import { agentToolsClient } from '@stack-spot/portal-network'
3
3
 
4
4
  export function useAgentFavorites() {
5
- const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' })
5
+ const useFavorites = () => agentToolsClient.agentsMultipleFilters.useQuery({ filters: { visibility_list: ['favorite'] } })?.items
6
6
  const [addFavorite, pendingAddFav] = agentToolsClient.addFavorite.useMutation()
7
7
  const [removeFavorite, pendingRemoveFav] = agentToolsClient.removeFavorite.useMutation()
8
8
 
9
9
  const removeFavoriteAgent = async (idOrSlug?: string) => {
10
10
  try {
11
11
  await removeFavorite({ agentId: idOrSlug || '' })
12
- await agentToolsClient.agents.invalidate({ visibility: 'favorite' })
12
+ await agentToolsClient.agentsMultipleFilters.invalidate({ filters: { visibility_list: ['favorite'] } })
13
13
  } catch (error) {
14
14
  // eslint-disable-next-line no-console
15
15
  console.error(error)
@@ -19,7 +19,7 @@ export function useAgentFavorites() {
19
19
  const addFavoriteAgent = async (idOrSlug?: string) => {
20
20
  try {
21
21
  await addFavorite({ agentId: idOrSlug || '' })
22
- await agentToolsClient.agents.invalidate({ visibility: 'favorite' })
22
+ await agentToolsClient.agentsMultipleFilters.invalidate({ filters: { visibility_list: ['favorite'] } })
23
23
  } catch (error) {
24
24
  // eslint-disable-next-line no-console
25
25
  console.error(error)
@@ -424,7 +424,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
424
424
  )
425
425
  })}
426
426
  </Button>
427
- <ViewToolsDetails chatId={chat.id} />
427
+ <ViewToolsDetails chatId={chat.id} messageId={message.id} />
428
428
  </div>
429
429
  }
430
430
 
@@ -50,14 +50,14 @@ const StepAccordionHeader = ({ step, index, expand }: Pick<StepProps, 'step' | '
50
50
  return <Row gap="8px">
51
51
  {expand}
52
52
  {step.status === 'target' ? <Text className="step-title" appearance="body2" color="light.700">{t.planGoal}:</Text> :
53
- <Badge colorScheme="inverse" appearance="square">
53
+ <Badge colorScheme="inverse" appearance="square" style={{ whiteSpace: 'nowrap' }}>
54
54
  {t.step} {index}
55
55
  </Badge>}
56
56
  <Text className="step-title" appearance="body2">
57
57
  {step.input}
58
58
  </Text>
59
59
  {step.status === 'awaiting_approval' &&
60
- <Badge appearance="square" style={{ backgroundColor: theme.color.gray[800], color: theme.color.gray[50] }}>
60
+ <Badge appearance="square" style={{ backgroundColor: theme.color.gray[800], color: theme.color.gray[50], whiteSpace: 'nowrap' }}>
61
61
  <Icon icon="Security" />
62
62
  {t.pendingReview}
63
63
  </Badge>}
@@ -199,7 +199,7 @@ export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolC
199
199
  return <>
200
200
  {toolStep && tool ? <AnimatedHeight>
201
201
  <div className="steps">
202
- <Badge colorPalette="yellow" appearance="square">
202
+ <Badge colorPalette="yellow" appearance="square" style={{ whiteSpace: 'nowrap' }}>
203
203
  <Icon icon="StopWatch" />
204
204
  <Text>{tool.name} {t.keepWorking}</Text>
205
205
  </Badge>
@@ -231,7 +231,7 @@ export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolC
231
231
 
232
232
  export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }: Props) => {
233
233
  const t = useTranslate(dictionary)
234
-
234
+
235
235
  const filteredSteps = steps.filter(s => s.type === 'step')
236
236
  const actualSteps = useMemo(() => filteredSteps.filter((item) => {
237
237
  const messageIdForStep = planningToolDictionaryHelper.getMessageIdFromStepId(item.id)
@@ -305,7 +305,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
305
305
  {!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
306
306
  </Column>
307
307
 
308
- {userHasAlreadyAnswered && planning?.[0]?.status === 'success' && <ViewToolsDetails chatId={chatId} />}
308
+ {planning?.[0]?.status === 'success' && <ViewToolsDetails chatId={chatId} messageId={messageId}/>}
309
309
 
310
310
  </div>
311
311
  </AnimatedHeight> : null}
@@ -316,14 +316,16 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
316
316
  )
317
317
  }
318
318
 
319
- export const ViewToolsDetails = ({ chatId }: { chatId: string }) => {
319
+ export const ViewToolsDetails = ({ chatId, messageId }: { chatId: string, messageId: number }) => {
320
320
  const t = useTranslate(dictionary)
321
321
  const messages = useChatMessages(chatId)
322
322
  const widget = useWidget()
323
323
  const getPlanningMessageId = () => {
324
+ const messageEntry = messages.find((message) => message.id === messageId)
325
+ const messageStep = messageEntry?.getValue().steps?.find((step) => step.type === 'step')
324
326
  const messageWithPlanning = findLast(messages, (message) => {
325
327
  const steps = message.getValue().steps
326
- const planningStep = steps?.find((step) => step.type === 'planning' && step.status === 'success')
328
+ const planningStep = steps?.find((step) => step.id === messageStep?.id)
327
329
  return planningStep ? true : false
328
330
  })
329
331
  return messageWithPlanning?.id
@@ -15,7 +15,8 @@ export const ModelSwitcher = () => {
15
15
  const chat = useCurrentChat()
16
16
  const [filter, setFilter] = useState('')
17
17
  const [visibleMenu, setVisibleMenu] = useState(false)
18
- const [agentData, isLoadingAgentData] = agentToolsClient.agent.useStatefulQuery({ agentId: agentCurrentChat?.id || '' })
18
+ const [agentData, isLoadingAgentData] = agentToolsClient.agent.useStatefulQuery({ agentId: agentCurrentChat?.id || '' },
19
+ { enabled: !!agentCurrentChat?.id })
19
20
  const [models, isLoadingModels] =
20
21
  genAiInferenceClient.listModels.useStatefulQuery({ pageSize: 999, active: true })
21
22