@stack-spot/ai-chat-widget 3.6.2-beta.5 → 3.7.0-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 (99) hide show
  1. package/CHANGELOG.md +23 -45
  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 +14 -5
  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 +6 -2
  8. package/dist/chat-interceptors/send-message.js.map +1 -1
  9. package/dist/components/Selector/SelectVersion.d.ts +12 -0
  10. package/dist/components/Selector/SelectVersion.d.ts.map +1 -0
  11. package/dist/components/Selector/SelectVersion.js +33 -0
  12. package/dist/components/Selector/SelectVersion.js.map +1 -0
  13. package/dist/components/Selector/index.d.ts +13 -2
  14. package/dist/components/Selector/index.d.ts.map +1 -1
  15. package/dist/components/Selector/index.js +23 -19
  16. package/dist/components/Selector/index.js.map +1 -1
  17. package/dist/components/Selector/styled.d.ts +2 -0
  18. package/dist/components/Selector/styled.d.ts.map +1 -1
  19. package/dist/components/Selector/styled.js +46 -0
  20. package/dist/components/Selector/styled.js.map +1 -1
  21. package/dist/hooks/enabled-feature-flags.d.ts +5 -0
  22. package/dist/hooks/enabled-feature-flags.d.ts.map +1 -0
  23. package/dist/hooks/enabled-feature-flags.js +28 -0
  24. package/dist/hooks/enabled-feature-flags.js.map +1 -0
  25. package/dist/state/types.d.ts +1 -0
  26. package/dist/state/types.d.ts.map +1 -1
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/utils/tools.d.ts +5 -5
  29. package/dist/utils/tools.d.ts.map +1 -1
  30. package/dist/utils/tools.js +2 -2
  31. package/dist/utils/tools.js.map +1 -1
  32. package/dist/views/Agents/AgentDescription.d.ts +2 -1
  33. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  34. package/dist/views/Agents/AgentDescription.js +21 -10
  35. package/dist/views/Agents/AgentDescription.js.map +1 -1
  36. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  37. package/dist/views/Agents/AgentsTab.js +6 -3
  38. package/dist/views/Agents/AgentsTab.js.map +1 -1
  39. package/dist/views/Agents/dictionary.d.ts +1 -1
  40. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  41. package/dist/views/Agents/dictionary.js +2 -0
  42. package/dist/views/Agents/dictionary.js.map +1 -1
  43. package/dist/views/Chat/ButtonExecutionDetail.js +6 -6
  44. package/dist/views/Chat/ButtonExecutionDetail.js.map +1 -1
  45. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  46. package/dist/views/Chat/ChatMessage.js +1 -2
  47. package/dist/views/Chat/ChatMessage.js.map +1 -1
  48. package/dist/views/ChatHistory/utils.js +1 -1
  49. package/dist/views/ChatHistory/utils.js.map +1 -1
  50. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  51. package/dist/views/MessageInput/AgentSelector.js +19 -7
  52. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  53. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts +2 -2
  54. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts.map +1 -1
  55. package/dist/views/MessageInput/ModelSwitcher/utils.js.map +1 -1
  56. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  57. package/dist/views/MessageInput/QuickCommandSelector.js +12 -8
  58. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  59. package/dist/views/Steps/FlowChart/{NodeTool.d.ts → NodeDynamic.d.ts} +2 -2
  60. package/dist/views/Steps/FlowChart/NodeDynamic.d.ts.map +1 -0
  61. package/dist/views/Steps/FlowChart/{NodeTool.js → NodeDynamic.js} +5 -5
  62. package/dist/views/Steps/FlowChart/NodeDynamic.js.map +1 -0
  63. package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
  64. package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
  65. package/dist/views/Steps/FlowChart/hooks.d.ts +1 -1
  66. package/dist/views/Steps/FlowChart/hooks.d.ts.map +1 -1
  67. package/dist/views/Steps/FlowChart/hooks.js +4 -2
  68. package/dist/views/Steps/FlowChart/hooks.js.map +1 -1
  69. package/dist/views/Steps/FlowChart/index.js +3 -3
  70. package/dist/views/Steps/FlowChart/index.js.map +1 -1
  71. package/dist/views/Steps/utils.d.ts.map +1 -1
  72. package/dist/views/Steps/utils.js.map +1 -1
  73. package/package.json +2 -2
  74. package/src/app-metadata.json +3 -3
  75. package/src/chat-interceptors/quick-commands.ts +14 -5
  76. package/src/chat-interceptors/send-message.ts +6 -2
  77. package/src/components/Selector/SelectVersion.tsx +55 -0
  78. package/src/components/Selector/index.tsx +57 -35
  79. package/src/components/Selector/styled.ts +48 -0
  80. package/src/hooks/enabled-feature-flags.ts +31 -0
  81. package/src/state/types.ts +1 -0
  82. package/src/types.ts +0 -1
  83. package/src/utils/tools.ts +4 -4
  84. package/src/views/Agents/AgentDescription.tsx +36 -14
  85. package/src/views/Agents/AgentsTab.tsx +13 -11
  86. package/src/views/Agents/dictionary.ts +2 -1
  87. package/src/views/Chat/ButtonExecutionDetail.tsx +6 -6
  88. package/src/views/Chat/ChatMessage.tsx +1 -2
  89. package/src/views/ChatHistory/utils.ts +4 -4
  90. package/src/views/MessageInput/AgentSelector.tsx +24 -12
  91. package/src/views/MessageInput/ModelSwitcher/utils.tsx +2 -2
  92. package/src/views/MessageInput/QuickCommandSelector.tsx +33 -23
  93. package/src/views/Steps/FlowChart/{NodeTool.tsx → NodeDynamic.tsx} +6 -6
  94. package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
  95. package/src/views/Steps/FlowChart/hooks.ts +3 -2
  96. package/src/views/Steps/FlowChart/index.tsx +3 -3
  97. package/src/views/Steps/utils.tsx +1 -1
  98. package/dist/views/Steps/FlowChart/NodeTool.d.ts.map +0 -1
  99. package/dist/views/Steps/FlowChart/NodeTool.js.map +0 -1
@@ -0,0 +1,55 @@
1
+
2
+ import { Select } from '@stack-spot/citric-react'
3
+ import { useTranslate } from '@stack-spot/portal-translate'
4
+ import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react'
5
+ import { isEqual } from 'lodash'
6
+ import { VersionSelector } from './styled'
7
+
8
+ interface Props {
9
+ value?: number,
10
+ onChange: Dispatch<SetStateAction<number | undefined>>,
11
+ options?: number[],
12
+ lazyLoadOptions?: boolean,
13
+ useVersions?: (id: string, enabled: boolean) => number[] | undefined,
14
+ id: string,
15
+ }
16
+
17
+ export const SelectVersion = ({ useVersions, value, onChange, options: initialOptions, id, lazyLoadOptions=false }: Props) => {
18
+ const t = useTranslate(dictionary)
19
+ const [options, setOptions] = useState(initialOptions)
20
+ const [enabled, setEnabled] = useState(!lazyLoadOptions)
21
+ const versions = useVersions?.(id, enabled)
22
+
23
+ const onClick = (e?: MouseEvent<HTMLDivElement>) => {
24
+ e?.stopPropagation()
25
+ if (lazyLoadOptions) {
26
+ setEnabled(true)
27
+ }
28
+ }
29
+
30
+ useEffect(() => {
31
+ if (lazyLoadOptions && !isEqual(versions, options)){
32
+ setOptions(versions)
33
+ }
34
+ }, [versions])
35
+
36
+ return (
37
+ <VersionSelector>
38
+ <Select className="version-selector"
39
+ options={options || []}
40
+ value={value}
41
+ onChange={onChange}
42
+ onClick={onClick}
43
+ renderLabel={(item) =>`${t.version} ${item}`} />
44
+ </VersionSelector>
45
+ )
46
+ }
47
+
48
+ const dictionary = {
49
+ en: {
50
+ version: 'Version',
51
+ },
52
+ pt: {
53
+ version: 'Versão',
54
+ },
55
+ }
@@ -1,15 +1,19 @@
1
1
  import { FallbackBoundary, IconLink, Text } from '@stack-spot/citric-react'
2
2
  import { useKeyboardControls } from '@stack-spot/portal-components'
3
+ import { InfiniteScroll } from '@stack-spot/portal-components/InfiniteScroll'
3
4
  import { AgentVisibilityLevel } from '@stack-spot/portal-network'
5
+ import { VisibilityLevelEnum } from '@stack-spot/portal-network/api/ai'
4
6
  import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
5
7
  import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
+ import { debounce } from 'lodash'
6
9
  import { useCurrentChatState, useWidgetState } from '../../context/hooks'
7
10
  import { getUrlToStackSpotAI } from '../../utils/url'
8
11
  import { ButtonFavorite } from '../ButtonFavorite'
9
12
  import { Fading } from '../Fading'
13
+ import { SelectVersion } from './SelectVersion'
10
14
  import { SelectorBox } from './styled'
11
15
 
12
- type SectionVisibility = AgentVisibilityLevel
16
+ type SectionVisibility = AgentVisibilityLevel | VisibilityLevelEnum
13
17
  type SelectorShortcut = '/' | '@'
14
18
 
15
19
  interface Item {
@@ -17,6 +21,7 @@ interface Item {
17
21
  slug: string,
18
22
  description: string,
19
23
  visibility_level: SectionVisibility,
24
+ version_number?: number,
20
25
  }
21
26
 
22
27
  interface Favorite<T> {
@@ -53,6 +58,12 @@ interface ContentProps<T> {
53
58
  favorite?: Favorite<T>,
54
59
  }
55
60
 
61
+ interface PropsUseData<T> {
62
+ data: T[],
63
+ fetchNextPage?: () => void,
64
+ hasNextPage?: boolean,
65
+ }
66
+
56
67
  interface SelectorConfig<T> {
57
68
  resourceName: string,
58
69
  shortcut: SelectorShortcut,
@@ -60,6 +71,8 @@ interface SelectorConfig<T> {
60
71
  regex: RegExp,
61
72
  urlBuilder: (item: T) => string,
62
73
  searchProp: keyof T,
74
+ isEnabledVersionContent?: boolean,
75
+ useVersions?: (id: string, enabled: boolean) => number[] | undefined,
63
76
  isEnabled: boolean,
64
77
  sections: SectionVisibility[],
65
78
  renderComponentItem: FC<T>,
@@ -71,7 +84,7 @@ interface SelectorConfig<T> {
71
84
  *
72
85
  * @returns the data to render in the selector.
73
86
  */
74
- useData: () => T[],
87
+ useData: (props: {filter?: string, visibility?: SectionVisibility[]}) => PropsUseData<T>,
75
88
  }
76
89
 
77
90
  interface SelectorProps<T> {
@@ -82,10 +95,11 @@ interface SelectorProps<T> {
82
95
 
83
96
  const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }: ListItemProps<T>) => {
84
97
  const t = useTranslate(dictionary)
85
- const { urlBuilder, renderComponentItem } = selectorConfig
98
+ const { urlBuilder, renderComponentItem, useVersions, isEnabledVersionContent } = selectorConfig
86
99
  const linkTitle = interpolate(t.open, item.slug)
87
100
  const listFavorites = favorite?.useFavorites?.()
88
-
101
+ const [selectedVersion, setSelectedVersion] = useState(item.version_number)
102
+
89
103
  return (
90
104
  <li>
91
105
  <button
@@ -97,6 +111,10 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }:
97
111
  >
98
112
  {renderComponentItem(item)}
99
113
  </button>
114
+ {isEnabledVersionContent &&
115
+ <SelectVersion options={item.version_number ? [item.version_number] : []} id={item.id}
116
+ value={selectedVersion} onChange={setSelectedVersion} lazyLoadOptions={true} useVersions={useVersions}
117
+ /> }
100
118
  <IconLink
101
119
  icon="ExternalLink"
102
120
  title={linkTitle}
@@ -114,39 +132,27 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }:
114
132
 
115
133
  const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, favorite }: ListProps<T>) => {
116
134
  const t = useTranslate(dictionary)
117
- const items = selectorConfig.useData()
118
- const favorites = favorite?.useFavorites?.()
119
- const filtered = useMemo(() => {
120
- if (!filter && !visibility) return [...items]
121
- const lowerFilter = filter?.toLowerCase() ?? ''
122
-
123
- if (visibility?.toLowerCase() === 'favorite'){
124
- return favorites?.filter(item =>
125
- (item[selectorConfig.searchProp] as string).toLocaleLowerCase().includes(lowerFilter))
126
- }
127
-
128
- return items.filter(item =>
129
- (item[selectorConfig.searchProp] as string).toLocaleLowerCase().includes(lowerFilter) &&
130
- (!visibility || item?.visibility_level?.toLowerCase() === visibility.toLowerCase()),
131
- )
132
- }, [filter, items, visibility, favorites])
135
+ const { data: items, fetchNextPage, hasNextPage } = selectorConfig.useData({ filter, visibility: visibility ? [visibility] : undefined })
133
136
 
134
-
135
- if (!items.length) return <Text className="empty" color="light.700">{interpolate(t.noData, selectorConfig.resourceName)}</Text>
136
- if (!filtered?.length)
137
- <Text className="empty" color="light.700">{interpolate(t.noResults, selectorConfig.resourceName)}</Text>
137
+ if (!items.length && !filter) return <Text className="empty" color="light.700">{interpolate(t.noData, selectorConfig.resourceName)}</Text>
138
+ if (!items?.length) return <Text className="empty" color="light.700">{interpolate(t.noResults, selectorConfig.resourceName)}</Text>
138
139
 
139
140
  return (
140
- <ul className="selector-list">
141
- {filtered?.map((item, i) => (
142
- <ListItem
143
- key={`${item.id}${item.visibility_level}${i}`}
144
- item={item}
145
- selectorConfig={selectorConfig}
146
- onSelect={onSelect}
147
- favorite={favorite}
148
- />
149
- ))}
141
+ <ul className="selector-list" id="selector-list">
142
+ <InfiniteScroll scrollableTarget="selector-list"
143
+ dataLength={items?.length ?? 0}
144
+ next={fetchNextPage as any ?? undefined}
145
+ hasMore={hasNextPage ?? false}>
146
+ {items?.map((item, i) => (
147
+ <ListItem
148
+ key={`${item.id}${item.visibility_level}${i}`}
149
+ item={item}
150
+ selectorConfig={selectorConfig}
151
+ onSelect={onSelect}
152
+ favorite={favorite}
153
+ />
154
+ ))}
155
+ </InfiniteScroll>
150
156
  </ul>
151
157
  )
152
158
  }
@@ -206,9 +212,25 @@ export const Selector = <T, >({ inputRef, selectorConfig, favorite }: SelectorPr
206
212
  const { shortcut, regex, isEnabled } = selectorConfig
207
213
  const [isClosed, setClosed] = useState(false)
208
214
  const selectorRef = useRef<HTMLDivElement>(null)
215
+ const [debouncedFilter, setDebouncedFilter] = useState<string | undefined>(undefined)
209
216
  const value = useCurrentChatState('nextMessage') ?? ''
210
217
  const filter = useMemo(() => value === shortcut || regex.test(value) ? value.substring(1) : undefined, [value])
211
218
  const shouldRender = isEnabled && filter !== undefined && !isClosed
219
+
220
+ const debouncedSetFilter = useCallback(
221
+ debounce((filterValue: string | undefined) => {
222
+ setDebouncedFilter(filterValue)
223
+ }, 300),
224
+ [],
225
+ )
226
+
227
+ useEffect(() => {
228
+ debouncedSetFilter(filter)
229
+ return () => {
230
+ debouncedSetFilter.cancel()
231
+ }
232
+ }, [filter, debouncedSetFilter])
233
+
212
234
 
213
235
  // Resets the closed state whenever the message input is cleared
214
236
  useEffect(() => {
@@ -258,7 +280,7 @@ export const Selector = <T, >({ inputRef, selectorConfig, favorite }: SelectorPr
258
280
  <SelectorContent
259
281
  favorite={favorite}
260
282
  selectorConfig={selectorConfig}
261
- filter={filter}
283
+ filter={debouncedFilter}
262
284
  onClose={() => setClosed(true)}
263
285
  inputRef={inputRef}
264
286
  />
@@ -156,3 +156,51 @@ export const SelectorBox = styled.div<{ $tabsCount: number }>`
156
156
  }
157
157
  }
158
158
  `
159
+
160
+ export const VersionSelector = styled.div`
161
+ position: relative;
162
+ min-width: 100px;
163
+
164
+ .version-selector {
165
+ height: 32px;
166
+ position: absolute;
167
+ top: -18px;
168
+ left: -12px;
169
+
170
+ header {
171
+ height: 20px;
172
+ background-color: ${theme.color.light[300]};
173
+ border: none;
174
+ margin-bottom: 0;
175
+ gap: 0;
176
+ padding: 0;
177
+
178
+ }
179
+ .selection-panel .options{
180
+ overflow: hidden;
181
+ }
182
+ li {
183
+ gap: 5px;
184
+ padding: 0 8px 0 !important;
185
+ padding-left: 5px;
186
+ }
187
+ }
188
+
189
+ `
190
+
191
+ export const VersionSelectorBox = styled.div`
192
+ border: 1px solid ${theme.color.light[500]};
193
+ border-radius: 4px;
194
+ .version-selector {
195
+ /* top: -16px; */
196
+ position: relative;
197
+ top: 0;
198
+ left: 0;
199
+ }
200
+ > div {
201
+ position: relative;
202
+ .options {
203
+ overflow: hidden;
204
+ }
205
+ }
206
+ `
@@ -0,0 +1,31 @@
1
+ import { accountClient } from '@stack-spot/portal-network'
2
+
3
+ const useEnabledFeatureFlags = (resourceType?: string, resourceSlug?: string) => {
4
+ const queryParams = resourceType && resourceSlug ? { [resourceType]: resourceSlug } : {}
5
+ const [featureFlags, , error, { isLoading }] = accountClient.getEnabledFeatureFlagsForAccount.useStatefulQuery({
6
+ queryParams,
7
+ })
8
+ return { featureFlags, isLoading, error }
9
+ }
10
+
11
+ export const useIsFeatureFlagEnabled = (
12
+ flagSlug: string,
13
+ resourceType?: string,
14
+ resourceSlug?: string,
15
+ ) => {
16
+ const { featureFlags, isLoading } = useEnabledFeatureFlags(resourceType, resourceSlug)
17
+ if (resourceType && resourceSlug) {
18
+ const featureFlag = featureFlags?.find((flag) => flag.slug === flagSlug)
19
+ const resourcesByType = featureFlag?.resources?.[resourceType]
20
+ if (!!featureFlag && !!resourcesByType) {
21
+ if (resourcesByType.mode === 'ALL') {
22
+ return { flagEnabled: true, isLoading }
23
+ } else {
24
+ return { flagEnabled: !!featureFlag.resources?.[resourceType].slugs.some((slug) => slug === resourceSlug), isLoading }
25
+ }
26
+ }
27
+ return { flagEnabled: false, isLoading }
28
+ } else {
29
+ return { flagEnabled: !!featureFlags?.some((flag) => flag.slug === flagSlug), isLoading }
30
+ }
31
+ }
@@ -11,6 +11,7 @@ export interface LabeledAgent extends LabeledWithImage {
11
11
  builtIn?: boolean,
12
12
  slug?: string,
13
13
  visibility_level?: string,
14
+ agent_version_number?: number,
14
15
  }
15
16
 
16
17
  export interface FileSize {
package/src/types.ts CHANGED
@@ -7,7 +7,6 @@ export interface WithChildren<T = React.ReactNode> {
7
7
  }
8
8
 
9
9
  export type PropsOf<T extends React.FunctionComponent> = T extends React.FunctionComponent<infer P> ? P : never
10
-
11
10
  export type ButtonAction = {
12
11
  color?: string,
13
12
  label?: string,
@@ -1,9 +1,9 @@
1
1
 
2
- export type ToolWithImage = { id?: string, image?: string, name: string, description?: string }
2
+ export type ToolWithImage = { id?: string | null, image?: string, name?: string, description?: string }
3
3
 
4
4
  interface Toolkit {
5
5
  id?: string | null,
6
- tools?: { id?: string, name?: string, description?: string, function?: { name: string, description?: string } }[] | null,
6
+ tools?: { id?: string | null, name?: string, description?: string, function?: { name: string, description: string } | null }[] | null,
7
7
  image_url?: string | null,
8
8
  avatar?: string | null,
9
9
  }
@@ -22,8 +22,8 @@ export function toolById(id: string, toolkits: Toolkit[] | undefined): ToolWithI
22
22
  if (mcp) {
23
23
  const [, toolkitId, toolName] = mcp
24
24
  const toolkit = toolkits?.find(tk => tk.id === toolkitId)
25
- const tool = toolkit?.tools?.find(t => t.name === toolName)
26
- return { id, image: toolkit?.avatar ?? undefined, name: toolName, description: tool?.function?.description }
25
+ const tool = toolkit?.tools?.find(t => t.name === toolName || t.function?.name === toolName)
26
+ return { id, image: toolkit?.avatar ?? undefined, name: tool?.function?.name, description: tool?.function?.description }
27
27
  }
28
28
  const { tool, toolkit } = findToolById(id, toolkits ?? []) ?? {}
29
29
  return (tool && toolkit)
@@ -1,17 +1,25 @@
1
1
  import { Icon } from '@stack-spot/citric-icons'
2
- import { AsyncContent, Badge, Card, IconBox, ImageBox, ImageWithFallback, Text } from '@stack-spot/citric-react'
2
+ import { AsyncContent, Badge, Card, IconBox, ImageBox, ImageWithFallback, Row, Text } from '@stack-spot/citric-react'
3
3
  import { agentToolsClient } from '@stack-spot/portal-network'
4
- import { useMemo } from 'react'
4
+ import { useEffect, useMemo, useState } from 'react'
5
+ import { SelectVersion } from '../../components/Selector/SelectVersion'
6
+ import { VersionSelectorBox } from '../../components/Selector/styled'
5
7
  import { useAgentsDictionary } from './dictionary'
6
8
  import { AgentDescriptionBox } from './styled'
7
9
 
8
- export const AgentDescription = ({ agentId }: { agentId?: string }) => {
10
+ export const AgentDescription = ({ agentId, enableVersionSelect }: { agentId?: string, enableVersionSelect?: boolean }) => {
9
11
  const t = useAgentsDictionary()
10
- const [agent,, error, { isLoading }] = agentToolsClient.agent.useStatefulQuery({ agentId: agentId! }, { enabled: !!agentId })
11
- const numberOfKnowledgeSources = agent?.knowledge_sources_config?.knowledge_sources.length ?? 0
12
+ const [selectedVersion, setSelectedVersion] = useState<number | undefined>()
13
+ const [agent,, error, { isLoading }] = agentToolsClient.agentV2.useStatefulQuery({ agentCoreId: agentId!,
14
+ versionNumber: selectedVersion }, { enabled: !!agentId })
15
+ const [listVersions, , errorAgentWithVersion, { isLoading: isLoadingAgentWithVersions }] =
16
+ agentToolsClient.listAgentVersions.useStatefulQuery({ agentCoreId: agentId! },
17
+ { enabled: enableVersionSelect && !!agentId })
18
+ const optionsVersions = listVersions?.map((version) => version.version_number)
19
+ const numberOfKnowledgeSources = agent?.version.knowledge_sources_config?.knowledge_sources.length ?? 0
12
20
 
13
21
  const knowledgeSources = useMemo(
14
- () => agent?.knowledge_sources_config?.knowledge_sources_details?.map((ks, index) => (
22
+ () => agent?.version.knowledge_sources_config?.knowledge_sources_details?.map((ks, index) => (
15
23
  <li key={index}>
16
24
  <Card gap="10px" direction="row" flex={1} size="xxs" bgLevel={500} justifyContent="space-between">
17
25
  <Text color="light.contrastText">{ks.name}</Text>
@@ -21,12 +29,12 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
21
29
  )),
22
30
  [agent],
23
31
  )
24
-
32
+
25
33
  const { tools, multiAgents } = useMemo(() => {
26
34
  const tools: React.ReactElement[] = []
27
35
  const multiAgents: React.ReactElement[] = []
28
- const builtInTools = agent?.toolkits?.builtin_toolkits ?? []
29
- const customToolkits = agent?.toolkits?.custom_toolkits ?? []
36
+ const builtInTools = agent?.version.toolkits?.builtin_toolkits ?? []
37
+ const customToolkits = agent?.version.toolkits?.custom_toolkits ?? []
30
38
  for (const toolkit of builtInTools) {
31
39
  for (const tool of toolkit.tools ?? []) {
32
40
  if (toolkit.id == 'UTILITIES'){
@@ -66,22 +74,36 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
66
74
  return { tools, multiAgents }
67
75
  }, [agent])
68
76
 
69
- const mcpToolkits = useMemo(() => agent?.toolkits?.mcp_toolkits?.map(t => (
77
+ const mcpToolkits = useMemo(() => agent?.version.toolkits?.mcp_toolkits?.map(t => (
70
78
  <li key={`mcp-${t.id}`}>
71
79
  <Card gap="10px" direction="row" flex={1} size="xxs" bgLevel={500}>
72
80
  <ImageBox><ImageWithFallback src={t.avatar ?? undefined} fallback={<Icon icon="Cog" />} /></ImageBox>
73
81
  <Text color="light.contrastText">{t.name}</Text>
74
82
  </Card>
75
83
  </li>
76
- )), [agent?.toolkits?.mcp_toolkits])
84
+ )), [agent?.version.toolkits?.mcp_toolkits])
77
85
 
86
+ useEffect(() => {
87
+ if (!selectedVersion && agent?.version.version_number !== selectedVersion){
88
+ setSelectedVersion(agent?.version.version_number)
89
+ }
90
+ }, [agent?.version.version_number])
91
+
78
92
  return (
79
- <AsyncContent loading={isLoading} error={error}>
93
+ <AsyncContent loading={isLoading || isLoadingAgentWithVersions} error={error || errorAgentWithVersion}>
80
94
  <AgentDescriptionBox>
81
95
  {agent?.description && <section>
82
96
  <Text appearance="microtext1" className="title">{t.description}</Text>
83
97
  <Text>{agent?.description}</Text>
84
98
  </section>}
99
+ {enableVersionSelect && optionsVersions && optionsVersions?.length > 0 && <section>
100
+ <VersionSelectorBox>
101
+ <Row justifyContent="space-between" p={4}>
102
+ <Text>{t.version}</Text>
103
+ <SelectVersion options={optionsVersions} value={selectedVersion} onChange={setSelectedVersion} id={agent?.id ?? ''} />
104
+ </Row>
105
+ </VersionSelectorBox>
106
+ </section>}
85
107
  {(!!numberOfKnowledgeSources || !!knowledgeSources?.length) && <section>
86
108
  <Text appearance="microtext1" className="title">Knowledge sources</Text>
87
109
  <ul>{knowledgeSources}</ul>
@@ -97,9 +119,9 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
97
119
  <Text appearance="microtext1" className="title">{t.multiAgent}</Text>
98
120
  <ul>{multiAgents}</ul>
99
121
  </section>}
100
- {agent?.model_name && <section>
122
+ {agent?.version.model_name && <section>
101
123
  <Text appearance="microtext1" className="title">LLM</Text>
102
- <Badge colorPalette="orange" appearance="square">{agent?.model_name}</Badge>
124
+ <Badge colorPalette="orange" appearance="square">{agent?.version.model_name}</Badge>
103
125
  </section>}
104
126
  </AgentDescriptionBox>
105
127
  </AsyncContent>
@@ -9,6 +9,7 @@ import { NavigationComponent } from '../../components/ComponentNavigator'
9
9
  import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
10
10
  import { WorkspaceTabNavigator } from '../../components/WorkspaceTabNavigator'
11
11
  import { useCurrentChat } from '../../context/hooks'
12
+ import { useIsFeatureFlagEnabled } from '../../hooks/enabled-feature-flags'
12
13
  import { useRightPanel } from '../../right-panel/hooks'
13
14
  import { ChatProperties } from '../../state/ChatState'
14
15
  import { AgentDescription } from './AgentDescription'
@@ -28,6 +29,7 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
28
29
  const chat = useCurrentChat()
29
30
  const { useFavorites, onAddFavorite, onRemoveFavorite } = useAgentFavorites()
30
31
  const [submitEnabled, setSubmitEnabled] = useState(false)
32
+ const featureFlag = useIsFeatureFlagEnabled('ENABLE_VERSION_CONTENT_AI')
31
33
  const listFavorites = useFavorites()
32
34
  const agentDefault = agentToolsClient.agentDefault.useQuery()
33
35
 
@@ -59,15 +61,15 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
59
61
 
60
62
  const agents = workspaceId ? workspaceAgents : agentsData
61
63
 
62
- const initialValue = useMemo<AgentResponseWithBuiltIn | undefined>(
63
- () => {
64
- const initial = agent.current
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)
68
- return initial
69
- },
70
- [agents],
64
+ const initialValue = useMemo<AgentResponseWithBuiltIn | undefined>(() => {
65
+ const initial = agent.current
66
+ ? agents?.find(a => a.id === agent.current?.id)
67
+ : chat.get('agent') ?
68
+ agents?.find(a => a.id === chat.get('agent')?.id) : { ...agentDefault, suggested_prompts: null } as AgentResponseWithBuiltIn
69
+ if (initial && !submitEnabled) setSubmitEnabled(true)
70
+ return initial
71
+ },
72
+ [agents],
71
73
  )
72
74
 
73
75
  function submit() {
@@ -80,7 +82,7 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
80
82
  <DescribedRadioGroup
81
83
  fetchNextPage={fetchNextPage}
82
84
  hasNextPage={hasNextPage && !workspaceId}
83
- options={agents}
85
+ options={agents || []}
84
86
  initialValue={initialValue}
85
87
  filter={workspaceId ? undefined : filter}
86
88
  setFilter={workspaceId ? undefined : (value?: string) => {
@@ -98,7 +100,7 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
98
100
  idOrSlug: ag.id,
99
101
  // below, "key" is important. I don't know why exactly, but sometimes the avatar doesn't update if we don't do this.
100
102
  image: <ImageWithFallback key={ag.id} src={ag.avatar ?? undefined} fallback={<Icon icon="Agent" />} />,
101
- description: <AgentDescription agentId={ag.id} />,
103
+ description: <AgentDescription agentId={ag.id} enableVersionSelect={featureFlag.flagEnabled} />,
102
104
  name: ag.name,
103
105
  listFavorites,
104
106
  onAddFavorite,
@@ -19,6 +19,7 @@ const dictionary = {
19
19
  tools: 'Tools',
20
20
  spots: 'Spots',
21
21
  multiAgent: 'Multi-agents',
22
+ version: 'Version',
22
23
  },
23
24
  pt: {
24
25
  title: 'Agentes',
@@ -38,7 +39,7 @@ const dictionary = {
38
39
  tools: 'Ferramentas',
39
40
  spots: 'Spots',
40
41
  multiAgent: 'Multi-agents',
41
-
42
+ version: 'Versão',
42
43
  },
43
44
  } satisfies Dictionary
44
45
 
@@ -1,7 +1,7 @@
1
1
  import { LoadingCircular } from '@citric/ui'
2
2
  import { Button, Icon, Row } from '@stack-spot/citric-react'
3
3
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
4
- import { useEffect } from 'react'
4
+ import { useEffect, useMemo } from 'react'
5
5
  import { useChatMessages, useWidget } from '../../context/hooks'
6
6
 
7
7
  export const ButtonExecutionDetail = ({ chatId, messageId }: { chatId: string, messageId: number }) => {
@@ -9,10 +9,10 @@ export const ButtonExecutionDetail = ({ chatId, messageId }: { chatId: string, m
9
9
  const messages = useChatMessages(chatId)
10
10
  const widget = useWidget()
11
11
 
12
- const isRunning = () => {
13
- const messageEntry = messages.find((message) => message.id === messageId)
12
+ const isRunning = useMemo(() => {
13
+ const messageEntry = messages?.find((message) => message.id === messageId)
14
14
  return !messageEntry?.getValue().steps?.find((step) => step.type === 'answer' && step.status !== 'running')
15
- }
15
+ }, [messageId, messages])
16
16
 
17
17
  function openToolsPanel() {
18
18
  if (messageId) {
@@ -22,12 +22,12 @@ export const ButtonExecutionDetail = ({ chatId, messageId }: { chatId: string, m
22
22
  }
23
23
 
24
24
  useEffect(() => {
25
- isRunning() && openToolsPanel()
25
+ isRunning && openToolsPanel()
26
26
  }, [isRunning])
27
27
 
28
28
  return <>
29
29
  <Row className="step-actions">
30
- {isRunning() && <LoadingCircular colorScheme="inverse" size="xs" />}
30
+ {isRunning && <LoadingCircular colorScheme="inverse" size="xs" />}
31
31
  <Button colorScheme="light" size="sm" appearance="none" className="icon-button details" onClick={openToolsPanel}>
32
32
  <Icon group="outline" icon="Expand" size="xs" />
33
33
  {t.detailed}
@@ -227,8 +227,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
227
227
  const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
228
228
  const isPlanning = useCurrentChatState('isPlaning') ?? false
229
229
 
230
- // Dynamic tool steps are identified by the "dynamic" id.
231
- // We're temporarily hiding the toolbox for these dynamic tools while we finalize their UI.
230
+ // Dynamic steps are identified by the "dynamic" id.
232
231
  const isDynamicSteps = !!entry?.steps?.some((s) => s.id?.toLowerCase() === 'dynamic')
233
232
  const hasTools = ((agentsTools?.length ?? 0) > 0) || ((entry.tools?.length ?? 0) > 0)
234
233
  const showToolBox = hasTools && !isDynamicSteps
@@ -1,5 +1,5 @@
1
1
  import { agentToolsClient, aiClient, AnswerChatStep, ChatAgentTool, ChatStep, PlanningChatStep, workspaceClient } from '@stack-spot/portal-network'
2
- import { AgentModel } from '@stack-spot/portal-network/api/agent-tools'
2
+ import { FindByIdAgentResponse } from '@stack-spot/portal-network/api/agent-tools'
3
3
  import { last } from 'lodash'
4
4
  import { ChatEntry } from '../../state/ChatEntry'
5
5
  import { ChatProperties, ChatState } from '../../state/ChatState'
@@ -56,7 +56,7 @@ function toJSONString(data: any) {
56
56
  return `${data}`
57
57
  }
58
58
 
59
- function findTool(agent: AgentModel | undefined, id: string) {
59
+ function findTool(agent: FindByIdAgentResponse | undefined, id: string) {
60
60
  const allToolkits = [
61
61
  ...agent?.toolkits?.builtin_toolkits ?? [],
62
62
  ...agent?.toolkits?.custom_toolkits ?? [],
@@ -75,7 +75,7 @@ async function stepsFromAgentInfo(
75
75
  ): Promise<ChatStep[] | undefined> {
76
76
  const planningInfo = agentInfo?.find(i => i.type === 'planning' && !!i.data)
77
77
  if (planningInfo) {
78
- let agent: AgentModel | undefined
78
+ let agent: FindByIdAgentResponse | undefined
79
79
  try {
80
80
  agent = agentId ? await agentToolsClient.agent.query({ agentId }) : undefined
81
81
  } catch { /* empty */ }
@@ -105,7 +105,7 @@ async function stepsFromAgentInfo(
105
105
  return {
106
106
  id: t.tool_id,
107
107
  executionId: t.tool_execution_id,
108
- name: tool?.name,
108
+ name: ('name' in tool ? tool.name : ('function' in tool ? tool?.function?.name : undefined) ?? ''),
109
109
  description: 'description' in tool ? tool.description : undefined,
110
110
  image: toolkit ? (('image_url' in toolkit ? toolkit?.image_url : toolkit?.avatar) ?? undefined) : undefined,
111
111
  goal: t.goal,