@stack-spot/ai-chat-widget 1.14.0-beta.7 → 1.14.0

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 (118) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/StackspotAIWidget.d.ts.map +1 -1
  3. package/dist/app-metadata.json +19 -19
  4. package/dist/components/Accordion.d.ts.map +1 -1
  5. package/dist/components/Accordion.js +3 -1
  6. package/dist/components/Accordion.js.map +1 -1
  7. package/dist/components/ButtonFavorite.d.ts +47 -0
  8. package/dist/components/ButtonFavorite.d.ts.map +1 -0
  9. package/dist/components/ButtonFavorite.js +25 -0
  10. package/dist/components/ButtonFavorite.js.map +1 -0
  11. package/dist/components/Code.d.ts.map +1 -1
  12. package/dist/components/FadingOverflow.d.ts.map +1 -1
  13. package/dist/components/FallbackBoundary/index.d.ts.map +1 -1
  14. package/dist/components/IconInput.d.ts.map +1 -1
  15. package/dist/components/Markdown.d.ts.map +1 -1
  16. package/dist/components/Modal.d.ts.map +1 -1
  17. package/dist/components/ProgressBar.d.ts.map +1 -1
  18. package/dist/components/QuickStartButton.d.ts.map +1 -1
  19. package/dist/components/RightPanelForm.d.ts.map +1 -1
  20. package/dist/components/RightPanelTabs.d.ts.map +1 -1
  21. package/dist/components/Selector/index.d.ts +9 -1
  22. package/dist/components/Selector/index.d.ts.map +1 -1
  23. package/dist/components/Selector/index.js +19 -13
  24. package/dist/components/Selector/index.js.map +1 -1
  25. package/dist/components/StackedBadge.d.ts.map +1 -1
  26. package/dist/components/ToolBadge.d.ts.map +1 -1
  27. package/dist/components/Tooltip/context.d.ts.map +1 -1
  28. package/dist/components/form/DescribedCheckboxGroup.d.ts +1 -1
  29. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  30. package/dist/components/form/DescribedCheckboxGroup.js +5 -5
  31. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  32. package/dist/components/form/DescribedRadioGroup.d.ts +1 -1
  33. package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
  34. package/dist/components/form/DescribedRadioGroup.js +4 -4
  35. package/dist/components/form/DescribedRadioGroup.js.map +1 -1
  36. package/dist/components/form/dictionary.d.ts +19 -0
  37. package/dist/components/form/dictionary.d.ts.map +1 -0
  38. package/dist/components/form/dictionary.js +19 -0
  39. package/dist/components/form/dictionary.js.map +1 -0
  40. package/dist/components/form/styled.d.ts.map +1 -1
  41. package/dist/components/form/styled.js +1 -2
  42. package/dist/components/form/styled.js.map +1 -1
  43. package/dist/components/form/types.d.ts +8 -0
  44. package/dist/components/form/types.d.ts.map +1 -1
  45. package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
  46. package/dist/right-panel/RightPanelProvider.d.ts.map +1 -1
  47. package/dist/utils/agent.d.ts.map +1 -1
  48. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  49. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  50. package/dist/views/Agents/AgentsPanel.js +2 -0
  51. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  52. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  53. package/dist/views/Agents/AgentsTab.js +7 -2
  54. package/dist/views/Agents/AgentsTab.js.map +1 -1
  55. package/dist/views/Agents/dictionary.d.ts +1 -1
  56. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  57. package/dist/views/Agents/dictionary.js +2 -0
  58. package/dist/views/Agents/dictionary.js.map +1 -1
  59. package/dist/views/Agents/useAgentFavorites.d.ts +8 -0
  60. package/dist/views/Agents/useAgentFavorites.d.ts.map +1 -0
  61. package/dist/views/Agents/useAgentFavorites.js +31 -0
  62. package/dist/views/Agents/useAgentFavorites.js.map +1 -0
  63. package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
  64. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  65. package/dist/views/Chat/ChatMessage.js +2 -2
  66. package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
  67. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  68. package/dist/views/Chat/index.d.ts.map +1 -1
  69. package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
  70. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  71. package/dist/views/ChatHistory/utils.js +3 -2
  72. package/dist/views/ChatHistory/utils.js.map +1 -1
  73. package/dist/views/Home/BuiltInAgent.d.ts.map +1 -1
  74. package/dist/views/Home/index.d.ts.map +1 -1
  75. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  76. package/dist/views/KnowledgeSources.js +35 -4
  77. package/dist/views/KnowledgeSources.js.map +1 -1
  78. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  79. package/dist/views/MessageInput/AgentSelector.js +6 -4
  80. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  81. package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
  82. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  83. package/dist/views/MessageInput/QuickCommandSelector.js +25 -2
  84. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  85. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  86. package/dist/views/MinimizedHeader.d.ts.map +1 -1
  87. package/dist/views/Stacks.d.ts.map +1 -1
  88. package/dist/views/Stacks.js +33 -3
  89. package/dist/views/Stacks.js.map +1 -1
  90. package/dist/views/Steps/FlowChart/HandleGroup.d.ts.map +1 -1
  91. package/dist/views/Steps/FlowChart/NodeStep.d.ts.map +1 -1
  92. package/dist/views/Steps/FlowChart/index.d.ts.map +1 -1
  93. package/dist/views/Steps/StepModal.d.ts.map +1 -1
  94. package/dist/views/Steps/StepsPanel.d.ts.map +1 -1
  95. package/dist/views/Workspaces.d.ts.map +1 -1
  96. package/dist/views/Workspaces.js +42 -5
  97. package/dist/views/Workspaces.js.map +1 -1
  98. package/package.json +3 -3
  99. package/src/app-metadata.json +19 -19
  100. package/src/components/Accordion.tsx +3 -2
  101. package/src/components/ButtonFavorite.tsx +100 -0
  102. package/src/components/Selector/index.tsx +45 -16
  103. package/src/components/form/DescribedCheckboxGroup.tsx +27 -11
  104. package/src/components/form/DescribedRadioGroup.tsx +16 -1
  105. package/src/components/form/dictionary.ts +18 -0
  106. package/src/components/form/styled.ts +1 -2
  107. package/src/components/form/types.ts +11 -1
  108. package/src/views/Agents/AgentsPanel.tsx +2 -0
  109. package/src/views/Agents/AgentsTab.tsx +12 -2
  110. package/src/views/Agents/dictionary.ts +2 -0
  111. package/src/views/Agents/useAgentFavorites.ts +32 -0
  112. package/src/views/Chat/ChatMessage.tsx +2 -2
  113. package/src/views/ChatHistory/utils.ts +3 -2
  114. package/src/views/KnowledgeSources.tsx +45 -8
  115. package/src/views/MessageInput/AgentSelector.tsx +8 -3
  116. package/src/views/MessageInput/QuickCommandSelector.tsx +25 -1
  117. package/src/views/Stacks.tsx +39 -4
  118. package/src/views/Workspaces.tsx +49 -8
@@ -0,0 +1,100 @@
1
+ import { Button, IconBox } from '@citric/core'
2
+ import { Star, StarFill } from '@citric/icons'
3
+ import { IconButton, LoadingCircular } from '@citric/ui'
4
+ import { useTranslate } from '@stack-spot/portal-translate'
5
+ import { useEffect, useState } from 'react'
6
+ import { dictionaryFavorites } from './form/dictionary'
7
+
8
+ interface ItemFavorite {
9
+ id?: string,
10
+ slug?: string,
11
+ }
12
+
13
+ interface Favorite<T extends ItemFavorite> {
14
+ /**
15
+ * id or slug to be used on functions
16
+ */
17
+ idOrSlug: string,
18
+ /**
19
+ * Array containing the list of favorite items.
20
+ */
21
+ listFavorites?: T[],
22
+ /**
23
+ * Function to add an item to the list of favorites.
24
+ * @param idOrSlug - The ID or slug of the content to be added to the favorites list.
25
+ * @returns void
26
+ */
27
+ onAddFavorite?: (idOrSlug: string) => void,
28
+ /**
29
+ * Function to remove an item from the list of favorites.
30
+ * @param idOrSlug - The ID or slug of the content to be removed from the favorites list.
31
+ * @returns void
32
+ */
33
+ onRemoveFavorite?: (idOrSlug: string) => void,
34
+ /**
35
+ * Indicates whether a request to add a favorite item is pending.
36
+ */
37
+ pendingAddFav?: boolean,
38
+ /**
39
+ * Indicates whether a request to remove a favorite item is pending.
40
+ */
41
+ pendingRemoveFav?: boolean,
42
+ }
43
+
44
+ interface Props<T extends ItemFavorite> {
45
+ /**
46
+ * Determine whether the component should be a Button or an IconButton.
47
+ */
48
+ isIconButton?: boolean,
49
+ /**
50
+ * An object containing favorite items and related operations.
51
+ */
52
+ favorite: Favorite<T>,
53
+ }
54
+
55
+ export const ButtonFavorite = <T extends ItemFavorite>({ favorite, isIconButton = false }: Props<T>) => {
56
+ const [isFavorite, setIsFavorite] = useState(false)
57
+ const { idOrSlug, listFavorites, pendingAddFav, pendingRemoveFav } = favorite || {}
58
+
59
+ useEffect(() => {
60
+ setIsFavorite(!!listFavorites?.some((fav) => fav.slug === idOrSlug || fav.id === idOrSlug))
61
+ }, [listFavorites])
62
+
63
+ return (
64
+ <ButtonWrapper isFavorite={isFavorite} isIconButton={isIconButton} favorite={favorite}>
65
+ <IconBox colorIcon={isFavorite ? 'yellow.500' : 'light'}>
66
+ {pendingAddFav || pendingRemoveFav ?
67
+ <LoadingCircular colorScheme="warning"/> : isFavorite ? <StarFill/> : <Star/> }
68
+ </IconBox>
69
+ </ButtonWrapper>
70
+ )
71
+ }
72
+
73
+ const ButtonWrapper = <T extends ItemFavorite>({ children, isIconButton, isFavorite, favorite }:
74
+ {children: React.ReactNode, isIconButton?: boolean, isFavorite: boolean, favorite: Favorite<T>}) => {
75
+ const t = useTranslate(dictionaryFavorites)
76
+ const { idOrSlug, onAddFavorite, onRemoveFavorite, pendingAddFav, pendingRemoveFav } = favorite
77
+ if (isIconButton){
78
+ return (
79
+ <IconButton
80
+ as="button"
81
+ disabled={pendingAddFav || pendingRemoveFav}
82
+ aria-label={`${t[isFavorite ? 'remove' : 'become']} ${t.favorite}`}
83
+ title={`${t[isFavorite ? 'remove' : 'become']} ${t.favorite}`}
84
+ onClick={() => isFavorite ? onRemoveFavorite?.(idOrSlug || '') : onAddFavorite?.(idOrSlug || '')}>
85
+ {children}
86
+ </IconButton>
87
+ )
88
+ }
89
+ return (
90
+ <Button
91
+ as="button"
92
+ colorScheme="light"
93
+ disabled={pendingAddFav || pendingRemoveFav}
94
+ aria-label={`${t[isFavorite ? 'remove' : 'become']} ${t.favorite}`}
95
+ title={`${t[isFavorite ? 'remove' : 'become']} ${t.favorite}`}
96
+ onClick={() => isFavorite ? onRemoveFavorite?.(idOrSlug || '') : onAddFavorite?.(idOrSlug || '')} >
97
+ {children}
98
+ </Button>
99
+ )
100
+ }
@@ -7,6 +7,7 @@ import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-transl
7
7
  import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
8
  import { useCurrentChatState } from '../../context/hooks'
9
9
  import { getUrlToStackSpotAI } from '../../utils/url'
10
+ import { ButtonFavorite } from '../ButtonFavorite'
10
11
  import { Fading } from '../Fading'
11
12
  import { FallbackBoundary } from '../FallbackBoundary'
12
13
  import { SelectorBox } from './styled'
@@ -22,11 +23,20 @@ interface Item {
22
23
  visibility_level: SectionVisibility,
23
24
  }
24
25
 
26
+ interface Favorite<T> {
27
+ listFavorites?: Array<T>,
28
+ onAddFavorite?: (idOrSlug?: string) => void,
29
+ onRemoveFavorite?: (idOrSlug?: string) => void,
30
+ pendingAddFav: boolean,
31
+ pendingRemoveFav: boolean,
32
+ }
33
+
25
34
  interface ListProps<T> {
26
35
  filter?: string,
27
36
  visibility?: SectionVisibility,
28
37
  selectorConfig: SelectorConfig<T>,
29
38
  onSelect: (item: T) => void,
39
+ favorite?: Favorite<T>,
30
40
  }
31
41
 
32
42
  interface ListItemProps<T> extends ListProps<T> {
@@ -38,6 +48,7 @@ interface ContentProps<T> {
38
48
  onClose: () => void,
39
49
  inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
40
50
  selectorConfig: SelectorConfig<T>,
51
+ favorite?: Favorite<T>,
41
52
  }
42
53
 
43
54
  interface SelectorConfig<T> {
@@ -57,9 +68,10 @@ interface SelectorConfig<T> {
57
68
  interface SelectorProps<T> {
58
69
  selectorConfig: SelectorConfig<T>,
59
70
  inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
71
+ favorite?: Favorite<T>,
60
72
  }
61
73
 
62
- const ListItem = <T extends Item>({ item, selectorConfig, onSelect }: ListItemProps<T>) => {
74
+ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }: ListItemProps<T>) => {
63
75
  const t = useTranslate(dictionary)
64
76
  const { urlBuilder, renderComponentItem } = selectorConfig
65
77
  const linkTitle = interpolate(t.open, item.slug)
@@ -78,40 +90,54 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect }: ListItemPr
78
90
  <IconButton as="a" title={linkTitle} aria-label={linkTitle} href={`${getUrlToStackSpotAI()}${urlBuilder(item)}`} target="_blank">
79
91
  <ExternalLink />
80
92
  </IconButton>
93
+ <ButtonFavorite favorite={{ idOrSlug: item?.id || '', ...favorite }} isIconButton />
81
94
  </li>
82
95
  )
83
96
  }
84
97
 
85
- const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect }: ListProps<T>) => {
98
+ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, favorite }: ListProps<T>) => {
86
99
  const t = useTranslate(dictionary)
87
100
  const items = selectorConfig.data()
101
+
88
102
  const filtered = useMemo(() => {
89
- if (!filter && !visibility) return items
103
+ if (!filter && !visibility) return [...items]
90
104
  const lowerFilter = filter?.toLowerCase() ?? ''
91
105
 
106
+ if (visibility?.toLowerCase() === 'favorite'){
107
+ return favorite?.listFavorites?.filter(item =>
108
+ (item[selectorConfig.searchProp] as string).toLocaleLowerCase().includes(lowerFilter))
109
+ }
110
+
92
111
  return items.filter(item =>
93
112
  (item[selectorConfig.searchProp] as string).toLocaleLowerCase().includes(lowerFilter) &&
94
113
  (!visibility || item?.visibility_level?.toLowerCase() === visibility.toLowerCase()),
95
114
  )
96
- }, [filter, items, visibility])
115
+ }, [filter, items, visibility, favorite?.listFavorites])
116
+
97
117
 
98
118
  if (!items.length) return <Text className="empty" colorScheme="light.700">{interpolate(t.noData, selectorConfig.resourceName)}</Text>
99
- if (!filtered.length)
119
+ if (!filtered?.length)
100
120
  <Text className="empty" colorScheme="light.700">{interpolate(t.noResults, selectorConfig.resourceName)}</Text>
101
121
 
102
122
  return (
103
123
  <ul className="selector-list">
104
- {filtered.map((item) => (
105
- <ListItem key={item.id} item={item} selectorConfig={selectorConfig} onSelect={onSelect} />
124
+ {filtered?.map((item) => (
125
+ <ListItem
126
+ key={item.id}
127
+ item={item}
128
+ selectorConfig={selectorConfig}
129
+ onSelect={onSelect}
130
+ favorite={favorite}
131
+ />
106
132
  ))}
107
133
  </ul>
108
134
  )
109
135
  }
110
136
 
111
- const SelectorContent = ({ filter, onClose, selectorConfig }: ContentProps<any>) => {
137
+ const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentProps<any>) => {
112
138
  const t = useTranslate(dictionary)
113
139
  const ref = useRef<HTMLDivElement>(null)
114
- const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
140
+ const [visibility, setVisibility] = useState<SectionVisibility | undefined>('favorite')
115
141
  const { resourceName, icon, onSelect, sections } = selectorConfig
116
142
  const onSelectItem = useCallback((slug: string) => {
117
143
  onSelect(slug)
@@ -127,11 +153,11 @@ const SelectorContent = ({ filter, onClose, selectorConfig }: ContentProps<any>)
127
153
  ref,
128
154
  }, [])
129
155
 
130
- function createSectionItem(action?: SectionVisibility) {
156
+ function createSectionItem(action: SectionVisibility) {
131
157
  return (
132
- <li key={action ?? 'all'}>
158
+ <li key={action}>
133
159
  <button className={visibility === action ? 'active' : ''} onFocus={() => setVisibility(action)}>
134
- {t[action || 'all']}
160
+ {t[action]}
135
161
  </button>
136
162
  </li>
137
163
  )
@@ -145,17 +171,17 @@ const SelectorContent = ({ filter, onClose, selectorConfig }: ContentProps<any>)
145
171
  </header>
146
172
  <div className="body">
147
173
  <ul className="tabs">
148
- {createSectionItem()}
149
- {sections.map(createSectionItem)}</ul>
174
+ {sections.map(createSectionItem)}
175
+ </ul>
150
176
  <FallbackBoundary message={interpolate(t.error, selectorConfig.resourceName)} mini>
151
- <List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} />
177
+ <List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} favorite={favorite} />
152
178
  </FallbackBoundary>
153
179
  </div>
154
180
  </div>
155
181
  )
156
182
  }
157
183
 
158
- export const Selector = <T, >({ inputRef, selectorConfig }: SelectorProps<T>) => {
184
+ export const Selector = <T, >({ inputRef, selectorConfig, favorite }: SelectorProps<T>) => {
159
185
  const { shortcut, regex, isEnabled } = selectorConfig
160
186
  const [isClosed, setClosed] = useState(false)
161
187
  const selectorRef = useRef<HTMLDivElement>(null)
@@ -210,6 +236,7 @@ export const Selector = <T, >({ inputRef, selectorConfig }: SelectorProps<T>) =>
210
236
  <SelectorBox>
211
237
  <Fading visible={shouldRender} ref={selectorRef} className="box-selector">
212
238
  <SelectorContent
239
+ favorite={favorite}
213
240
  selectorConfig={selectorConfig}
214
241
  filter={filter}
215
242
  onClose={() => setClosed(true)}
@@ -232,6 +259,7 @@ const dictionary = {
232
259
  noData: 'You don\'t have any $0 yet.',
233
260
  noResults: 'There are no $0s to show here.',
234
261
  open: 'Open this $0 settings in a new tab.',
262
+ favorite: 'Favorites',
235
263
  },
236
264
  pt: {
237
265
  all: 'Todos',
@@ -244,5 +272,6 @@ const dictionary = {
244
272
  noData: 'Você ainda não possui $0s.',
245
273
  noResults: 'Não $0s para mostrar aqui.',
246
274
  open: 'Abra as configurações deste $0 em uma nova aba.',
275
+ favorite: 'Favoritos',
247
276
  },
248
277
  } satisfies Dictionary
@@ -9,12 +9,23 @@ import { CheckProps } from './types'
9
9
  * Renders a checkbox group where each option has a label and a description.
10
10
  * The description in placed under the label and checkbox as an accordion.
11
11
  */
12
- export function DescribedCheckboxGroup<T>(
13
- { keygen, onChange, options, renderDescription, renderLabel, optionClassName, optionStyle, value, className, style }: CheckProps<T>,
14
- ) {
12
+ export function DescribedCheckboxGroup<T>({
13
+ keygen,
14
+ onChange,
15
+ options,
16
+ renderDescription,
17
+ renderLabel,
18
+ renderBeforeElement,
19
+ renderAfterElement,
20
+ optionClassName,
21
+ optionStyle,
22
+ value,
23
+ className,
24
+ style }: CheckProps<T>) {
15
25
  const items = useMemo(() => options.map((option) => {
16
26
  const label = renderLabel(option)
17
27
  const description = renderDescription(option)
28
+
18
29
  const header = (
19
30
  <label>
20
31
  <Checkbox
@@ -27,15 +38,20 @@ export function DescribedCheckboxGroup<T>(
27
38
  {typeof label === 'string' ? <Text>{label}</Text> : label}
28
39
  </label>
29
40
  )
41
+
30
42
  return (
31
- <li key={keygen(option)} className={listToClass(['radio-group-item', optionClassName?.(option)])} style={optionStyle?.(option)}>
32
- <Accordion header={header}>
33
- {typeof description === 'string'
34
- ? <Text appearance="microtext1" colorScheme="light.700">{description}</Text>
35
- : description as any
36
- }
37
- </Accordion>
38
- </li>
43
+ <>
44
+ <li key={keygen(option)} className={listToClass(['radio-group-item', optionClassName?.(option)])} style={optionStyle?.(option)}>
45
+ {renderBeforeElement?.(option)}
46
+ <Accordion header={header}>
47
+ {typeof description === 'string'
48
+ ? <Text appearance="microtext1" colorScheme="light.700">{description}</Text>
49
+ : description as any
50
+ }
51
+ </Accordion>
52
+ {renderAfterElement?.(option)}
53
+ </li>
54
+ </>
39
55
  )
40
56
  }), [options, value])
41
57
 
@@ -10,11 +10,24 @@ import { RadioProps } from './types'
10
10
  * The description in placed under the label and radio button as an accordion.
11
11
  */
12
12
  export function DescribedRadioGroup<T>(
13
- { keygen, onChange, options, renderDescription, renderLabel, optionClassName, optionStyle, value, className, style }: RadioProps<T>,
13
+ {
14
+ keygen,
15
+ onChange,
16
+ options,
17
+ renderDescription,
18
+ renderLabel,
19
+ renderBeforeElement,
20
+ renderAfterElement,
21
+ optionClassName,
22
+ optionStyle,
23
+ value,
24
+ className,
25
+ style }: RadioProps<T>,
14
26
  ) {
15
27
  const items = useMemo(() => options.map((option) => {
16
28
  const label = renderLabel(option)
17
29
  const description = renderDescription(option)
30
+
18
31
  const header = (
19
32
  <label>
20
33
  <Radio checked={option === value} onChange={() => onChange(option)} />
@@ -23,12 +36,14 @@ export function DescribedRadioGroup<T>(
23
36
  )
24
37
  return (
25
38
  <li key={keygen(option)} className={listToClass(['radio-group-item', optionClassName?.(option)])} style={optionStyle?.(option)}>
39
+ {renderBeforeElement?.(option)}
26
40
  <Accordion header={header}>
27
41
  {typeof description === 'string'
28
42
  ? <Text appearance="microtext1" colorScheme="light.700">{description}</Text>
29
43
  : description as any
30
44
  }
31
45
  </Accordion>
46
+ {renderAfterElement?.(option)}
32
47
  </li>
33
48
  )
34
49
  }), [options, value])
@@ -0,0 +1,18 @@
1
+ export const dictionaryFavorites = {
2
+ en: {
3
+ add: 'Add',
4
+ remove: 'Remove',
5
+ become: 'Become',
6
+ favorite: 'Favorite',
7
+ couldNotActFavorite: 'Could not $0 favorite',
8
+ actionFavoriteSuccessfully: '$0 from favorites successfully',
9
+ },
10
+ pt: {
11
+ add: 'Adicionar',
12
+ remove: 'Remover',
13
+ become: 'Tornar',
14
+ favorite: 'Favorito',
15
+ couldNotActFavorite: 'Não foi possível $0 o favorito',
16
+ actionFavoriteSuccessfully: '$0 dos favoritos com sucesso',
17
+ },
18
+ }
@@ -11,10 +11,9 @@ export const RadioCheckBox = styled.ul`
11
11
 
12
12
  > li {
13
13
  display: flex;
14
- flex-direction: column;
14
+ flex-direction: row;
15
15
  gap: 8px;
16
16
  border-radius: 6px;
17
- background-color: ${theme.color.light[400]};
18
17
 
19
18
  label {
20
19
  display: flex;
@@ -13,6 +13,14 @@ interface RadioCheckProps<T> extends WithStyle {
13
13
  * A function that renders an option as a description. This can either return a string or a React Element.
14
14
  */
15
15
  renderDescription: (option: T) => React.ReactNode,
16
+ /**
17
+ * A function that renders a element before the content
18
+ */
19
+ renderBeforeElement?: (option: T) => React.ReactNode,
20
+ /**
21
+ * A function that renders a element after the content
22
+ */
23
+ renderAfterElement?: (option: T) => React.ReactNode,
16
24
  /**
17
25
  * A function that gives a custom className to the rendered option.
18
26
  */
@@ -25,7 +33,9 @@ interface RadioCheckProps<T> extends WithStyle {
25
33
  * A function that generates a unique id for the option.
26
34
  */
27
35
  keygen: (option: T) => React.Key,
28
-
36
+ /**
37
+ * An object containing favorite items and related operations.
38
+ */
29
39
  }
30
40
 
31
41
  export interface RadioProps<T> extends RadioCheckProps<T> {
@@ -14,9 +14,11 @@ export const AgentsPanel = () => {
14
14
  const isTrial = checkIsTrial()
15
15
 
16
16
  const tabs= useMemo(() => isTrial ? [
17
+ { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
17
18
  { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
18
19
  { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
19
20
  ]: [
21
+ { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
20
22
  { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
21
23
  { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
22
24
  { title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" /> },
@@ -5,6 +5,7 @@ import { MiniLogo } from '@stack-spot/portal-components/svg'
5
5
  import { agentClient } from '@stack-spot/portal-network'
6
6
  import { AgentResponse, VisibilityLevel } from '@stack-spot/portal-network/api/agent'
7
7
  import { useMemo, useState } from 'react'
8
+ import { ButtonFavorite } from '../../components/ButtonFavorite'
8
9
  import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
9
10
  import { IconInput } from '../../components/IconInput'
10
11
  import { useCurrentChat } from '../../context/hooks'
@@ -13,21 +14,27 @@ import { isAgentDefault } from '../../utils/agent'
13
14
  import { AgentDescription } from './AgentDescription'
14
15
  import { useAgentsDictionary } from './dictionary'
15
16
  import { AgentLabel } from './styled'
17
+ import { useAgentFavorites } from './useAgentFavorites'
16
18
 
17
19
  export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT-IN' }) => {
18
20
  const t = useAgentsDictionary()
19
21
  const { close } = useRightPanel()
20
22
  const chat = useCurrentChat()
21
23
  const [filter, setFilter] = useState('')
24
+ const { listFavorites, onAddFavorite, onRemoveFavorite, pendingAddFav, pendingRemoveFav } = useAgentFavorites()
25
+
22
26
  const agentsBuiltIn = agentClient.publicAgents.useQuery({})
23
27
  const agentDefault = agentsBuiltIn.find((agent) => isAgentDefault(agent.slug))
24
28
  const agents = visibility === 'BUILT-IN' ? agentsBuiltIn : agentClient.agents.useQuery({ visibility })
29
+
25
30
  const [value, setValue] = useState<AgentResponse | undefined>(
26
31
  chat.get('agent') ? agents.find(a => a.id === chat.get('agent')?.id) : agentDefault,
27
32
  )
33
+
28
34
  const filtered = useMemo(
29
- () => filter ? agents.filter(a => a === value || a.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : agents,
30
- [agents, filter, value],
35
+ // Recreate the list so that the favorites list is taken into account
36
+ () => filter ? agents.filter(a => a === value || a.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...agents],
37
+ [agents, filter, value, listFavorites],
31
38
  )
32
39
 
33
40
  function submit() {
@@ -43,6 +50,9 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
43
50
  <IconInput icon={<Search />} value={filter} onChange={setFilter} className="search" />
44
51
  {!!filtered.length && <DescribedRadioGroup
45
52
  options={filtered}
53
+ renderAfterElement={({ id }) => (
54
+ <ButtonFavorite favorite={{ idOrSlug: id, listFavorites, onAddFavorite, onRemoveFavorite, pendingAddFav, pendingRemoveFav }} />
55
+ )}
46
56
  keygen={a => a.id}
47
57
  value={value}
48
58
  onChange={setValue}
@@ -15,6 +15,7 @@ const dictionary = {
15
15
  noDataDescription: 'Use the tabs above to try other categories or use the AI portal to create new agents.',
16
16
  defaultAgentDescription: 'The StackSpot CodeGen is an advanced artificial intelligence agent designed to optimize and accelerate software development. Integrated directly into your integrated development environment, StackSpot CodeGen offers real-time code suggestions, helping developers write high-quality code more efficiently. With robust features such as creating Stacks AI, customized knowledge sources, and quick commands, StackSpot CodeGen contextualizes your development needs to provide the best answers and code suggestions.',
17
17
  description: 'Description',
18
+ favorites: 'Favorites',
18
19
  tools: 'Tools',
19
20
  },
20
21
  pt: {
@@ -31,6 +32,7 @@ const dictionary = {
31
32
  noDataDescription: 'Use as abas acima para tentar outras categorias ou use o Portal AI para criar novos agentes.',
32
33
  defaultAgentDescription: 'O StackSpot CodeGen é um agente de inteligência artificial avançado projetado para otimizar e acelerar o desenvolvimento de software. Integrado diretamente ao seu ambiente de desenvolvimento, o StackSpot CodeGen oferece sugestões de código em tempo real, ajudando os desenvolvedores a escreverem código de alta qualidade de forma mais eficiente. Com recursos robustos, como a criação de Stacks AI, Knowledge Sources personalizadas e comandos rápidos, o StackSpot CodeGen contextualiza suas necessidades de desenvolvimento para fornecer as melhores respostas e sugestões de código.',
33
34
  description: 'Descrição',
35
+ favorites: 'Favoritos',
34
36
  tools: 'Ferramentas',
35
37
  },
36
38
  } satisfies Dictionary
@@ -0,0 +1,32 @@
1
+ /* eslint-disable filenames/match-regex */
2
+ import { agentClient } from '@stack-spot/portal-network'
3
+
4
+ export function useAgentFavorites() {
5
+ const listFavorites = agentClient.agents.useQuery({ visibility: 'FAVORITE' })
6
+ const [addFavorite, pendingAddFav] = agentClient.addFavoriteAgent.useMutation()
7
+ const [removeFavorite, pendingRemoveFav] = agentClient.removeFavoriteAgent.useMutation()
8
+
9
+ const onRemoveFavorite = async (idOrSlug?: string) => {
10
+ try {
11
+ await removeFavorite({ agentId: idOrSlug || '' })
12
+ await agentClient.agents.invalidate()
13
+ } catch (error) {
14
+ // eslint-disable-next-line no-console
15
+ console.error(error)
16
+ }
17
+ }
18
+
19
+ const onAddFavorite = async (idOrSlug?: string) => {
20
+ try {
21
+ await addFavorite({ agentId: idOrSlug || '' })
22
+ await agentClient.agents.invalidate()
23
+ } catch (error) {
24
+ // eslint-disable-next-line no-console
25
+ console.error(error)
26
+ }
27
+ }
28
+
29
+ return {
30
+ listFavorites, onAddFavorite, onRemoveFavorite, pendingAddFav, pendingRemoveFav,
31
+ }
32
+ }
@@ -168,8 +168,8 @@ export const ChatMessage = ({ message, username, isLast }: Props) => {
168
168
  const chat = useCurrentChat()
169
169
  const agentId = entry.agent?.id ?? ''
170
170
  const [agentById] = agentClient.agent.useStatefulQuery({ agentId }, { enabled: !!agentId && !entry?.agent?.builtIn })
171
- const [AgentBuiltInById] = agentClient.publicAgent.useStatefulQuery({ agentId }, { enabled: !!agentId && entry?.agent?.builtIn })
172
- const agent = agentById ?? AgentBuiltInById
171
+ const [agentBuiltInById] = agentClient.publicAgent.useStatefulQuery({ agentId }, { enabled: !!agentId && entry?.agent?.builtIn })
172
+ const agent = agentById ?? agentBuiltInById
173
173
 
174
174
  useChatScrollToBottomEffect(ref, [entry])
175
175
 
@@ -1,4 +1,4 @@
1
- import { agentClient, aiClient, workspaceAiClient } from '@stack-spot/portal-network'
1
+ import { agentClient, aiClient, workspaceClient } from '@stack-spot/portal-network'
2
2
  import { ChatProperties } from '../../state/ChatState'
3
3
  import { LabeledWithImage } from '../../state/types'
4
4
 
@@ -27,7 +27,8 @@ export async function findStack(id: string | null): Promise<ChatProperties['stac
27
27
  export async function findWorkspace(id: string | null): Promise<ChatProperties['workspace'] | undefined> {
28
28
  if (!id) return
29
29
  try {
30
- const ws = await workspaceAiClient.workspaceAi.query({ id })
30
+ // const ws = await workspaceAiClient.workspaceAi.query({ id })
31
+ const ws = await workspaceClient.workspace.query({ workspaceId: id })
31
32
  return { id, label: ws.name }
32
33
  } catch (error) {
33
34
  // eslint-disable-next-line no-console