@stack-spot/ai-chat-widget 1.17.1 → 1.18.0-beta.2

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 (93) hide show
  1. package/dist/StackspotAIWidget.js +1 -1
  2. package/dist/app-metadata.json +2 -2
  3. package/dist/components/ComponentNavigator.d.ts +21 -0
  4. package/dist/components/ComponentNavigator.d.ts.map +1 -0
  5. package/dist/components/ComponentNavigator.js +33 -0
  6. package/dist/components/ComponentNavigator.js.map +1 -0
  7. package/dist/components/ListGroup.d.ts +46 -0
  8. package/dist/components/ListGroup.d.ts.map +1 -0
  9. package/dist/components/ListGroup.js +16 -0
  10. package/dist/components/ListGroup.js.map +1 -0
  11. package/dist/components/RightPanelForm.d.ts.map +1 -1
  12. package/dist/components/RightPanelForm.js +29 -1
  13. package/dist/components/RightPanelForm.js.map +1 -1
  14. package/dist/components/Selector/index.js +5 -5
  15. package/dist/components/Selector/index.js.map +1 -1
  16. package/dist/components/Selector/styled.d.ts +3 -1
  17. package/dist/components/Selector/styled.d.ts.map +1 -1
  18. package/dist/components/Selector/styled.js +2 -1
  19. package/dist/components/Selector/styled.js.map +1 -1
  20. package/dist/components/WorkspaceTabNavigator.d.ts +15 -0
  21. package/dist/components/WorkspaceTabNavigator.d.ts.map +1 -0
  22. package/dist/components/WorkspaceTabNavigator.js +95 -0
  23. package/dist/components/WorkspaceTabNavigator.js.map +1 -0
  24. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  25. package/dist/components/form/DescribedCheckboxGroup.js +23 -2
  26. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  27. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  28. package/dist/views/Agents/AgentsPanel.js +19 -11
  29. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  30. package/dist/views/Agents/AgentsTab.d.ts +9 -3
  31. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  32. package/dist/views/Agents/AgentsTab.js +25 -7
  33. package/dist/views/Agents/AgentsTab.js.map +1 -1
  34. package/dist/views/Agents/dictionary.d.ts +1 -1
  35. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  36. package/dist/views/Agents/dictionary.js +2 -0
  37. package/dist/views/Agents/dictionary.js.map +1 -1
  38. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  39. package/dist/views/ChatHistory/utils.js +8 -3
  40. package/dist/views/ChatHistory/utils.js.map +1 -1
  41. package/dist/views/KnowledgeSources.d.ts +12 -0
  42. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  43. package/dist/views/KnowledgeSources.js +20 -6
  44. package/dist/views/KnowledgeSources.js.map +1 -1
  45. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  46. package/dist/views/MessageInput/AgentSelector.js +4 -3
  47. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  48. package/dist/views/MessageInput/ButtonGroup.js +2 -2
  49. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  50. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  51. package/dist/views/MessageInput/QuickCommandSelector.js +9 -3
  52. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  53. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  54. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  55. package/dist/views/MessageInput/dictionary.js +2 -0
  56. package/dist/views/MessageInput/dictionary.js.map +1 -1
  57. package/dist/views/Stacks.d.ts +9 -0
  58. package/dist/views/Stacks.d.ts.map +1 -1
  59. package/dist/views/Stacks.js +37 -14
  60. package/dist/views/Stacks.js.map +1 -1
  61. package/dist/views/Workspaces/WorkspacesTab.d.ts +20 -0
  62. package/dist/views/Workspaces/WorkspacesTab.d.ts.map +1 -0
  63. package/dist/views/Workspaces/WorkspacesTab.js +64 -0
  64. package/dist/views/Workspaces/WorkspacesTab.js.map +1 -0
  65. package/dist/views/{Workspaces.d.ts → Workspaces/index.d.ts} +1 -1
  66. package/dist/views/Workspaces/index.d.ts.map +1 -0
  67. package/dist/views/Workspaces/index.js +76 -0
  68. package/dist/views/Workspaces/index.js.map +1 -0
  69. package/package.json +2 -2
  70. package/src/app-metadata.json +2 -2
  71. package/src/components/ComponentNavigator.tsx +78 -0
  72. package/src/components/ListGroup.tsx +76 -0
  73. package/src/components/RightPanelForm.tsx +29 -1
  74. package/src/components/Selector/index.tsx +5 -5
  75. package/src/components/Selector/styled.ts +3 -2
  76. package/src/components/WorkspaceTabNavigator.tsx +172 -0
  77. package/src/components/form/DescribedCheckboxGroup.tsx +45 -14
  78. package/src/views/Agents/AgentsPanel.tsx +21 -11
  79. package/src/views/Agents/AgentsTab.tsx +42 -9
  80. package/src/views/Agents/dictionary.ts +3 -0
  81. package/src/views/ChatHistory/utils.ts +9 -3
  82. package/src/views/KnowledgeSources.tsx +37 -14
  83. package/src/views/MessageInput/AgentSelector.tsx +4 -3
  84. package/src/views/MessageInput/ButtonGroup.tsx +3 -3
  85. package/src/views/MessageInput/QuickCommandSelector.tsx +10 -3
  86. package/src/views/MessageInput/dictionary.ts +2 -0
  87. package/src/views/Stacks.tsx +57 -17
  88. package/src/views/Workspaces/WorkspacesTab.tsx +118 -0
  89. package/src/views/Workspaces/index.tsx +85 -0
  90. package/dist/views/Workspaces.d.ts.map +0 -1
  91. package/dist/views/Workspaces.js +0 -103
  92. package/dist/views/Workspaces.js.map +0 -1
  93. package/src/views/Workspaces.tsx +0 -137
@@ -2,7 +2,7 @@ import { theme } from '@stack-spot/portal-theme'
2
2
  import { styled } from 'styled-components'
3
3
 
4
4
 
5
- export const SelectorBox = styled.div`
5
+ export const SelectorBox = styled.div<{tabsCount: number}>`
6
6
  position: absolute;
7
7
  bottom: 0;
8
8
 
@@ -53,6 +53,8 @@ export const SelectorBox = styled.div`
53
53
  display: flex;
54
54
  flex-direction: row;
55
55
  align-items: center;
56
+ align-items: flex-start;
57
+ height: ${({ tabsCount }) => 34 + (tabsCount * 34)}px;
56
58
  }
57
59
 
58
60
  ul {
@@ -105,7 +107,6 @@ export const SelectorBox = styled.div`
105
107
  gap: 2px;
106
108
  overflow-y: auto;
107
109
  flex: 1;
108
- max-height: 170px;
109
110
 
110
111
  li {
111
112
  display: flex;
@@ -0,0 +1,172 @@
1
+ import { Flex, IconBox, Image, Text } from '@citric/core'
2
+ import { ArrowRight, Circle, Search, Times } from '@citric/icons'
3
+ import { Avatar, IconButton } from '@citric/ui'
4
+ import { Placeholder } from '@stack-spot/portal-components/Placeholder'
5
+ import { workspaceAiClient } from '@stack-spot/portal-network'
6
+ import { WorkspaceResponse, WorkspaceVisibilityLevelEnum } from '@stack-spot/portal-network/api/workspace-ai'
7
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
8
+ import { memo, useMemo, useState } from 'react'
9
+ import { useRightPanel } from '../right-panel/hooks'
10
+ import { ButtonFavorite } from './ButtonFavorite'
11
+ import { ComponentNavigator, ComponentNavigatorProps, NavigationItem, NavigationMap, useComponentNavigation } from './ComponentNavigator'
12
+ import { IconInput } from './IconInput'
13
+ import { ListGroup } from './ListGroup'
14
+
15
+ interface CardSpaceProps {
16
+ onClick: VoidFunction,
17
+ name: string,
18
+ icon: React.ReactElement,
19
+ logoUrl?: string | null,
20
+ }
21
+
22
+ export const CardSpace = ({ onClick, name, icon, logoUrl }: CardSpaceProps) =>
23
+ <Flex
24
+ onClick={onClick}
25
+ flex={1}
26
+ alignItems="center"
27
+ justifyContent="space-between"
28
+ mr={2}
29
+ bg="light.400"
30
+ r="sm"
31
+ p={3}
32
+ sx={{ cursor: 'pointer' }}
33
+ >
34
+ <Flex alignContent="center" alignItems="center" sx={{ gap: '8px', m: 1 }} >
35
+ <Avatar size="xxs" appearance="square" sx={{ bg: 'light.600', r: 'xxs' }}>
36
+ {logoUrl ? <Image src={logoUrl} /> : <IconBox> {icon} </IconBox>}
37
+ </Avatar>
38
+ <Text appearance="body2">{name}</Text>
39
+ </Flex>
40
+ <IconButton><ArrowRight /></IconButton>
41
+ </Flex>
42
+
43
+ interface WorkspaceSourcesTabProps {
44
+ visibility: WorkspaceVisibilityLevelEnum,
45
+ onClick: (workspace: WorkspaceResponse) => void,
46
+ }
47
+
48
+ const WorkspaceSourcesTab = ({ visibility, onClick }: WorkspaceSourcesTabProps) => {
49
+ const t = useTranslate(dictionary)
50
+ const [filter, setFilter] = useState('')
51
+ const workspaces = workspaceAiClient.workspacesAi.useQuery({ visibility })
52
+ const listFavorites = workspaceAiClient.workspacesAi.useQuery({ visibility: 'favorite' })
53
+ const [addFavorite, pendingAddFav] = workspaceAiClient.addFavoriteWorkspaceAi.useMutation()
54
+ const [removeFavorite, pendingRemoveFav] = workspaceAiClient.removeFavoriteWorkspaceAi.useMutation()
55
+
56
+ // eslint-disable-next-line no-async-promise-executor
57
+ const onAddFavorite = async (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
58
+ try {
59
+ await addFavorite({ workspaceId: idOrSlug })
60
+ await workspaceAiClient.workspacesAi.invalidate()
61
+ if (!pendingAddFav) {
62
+ resolve(true)
63
+ }
64
+ } catch (error) {
65
+ // eslint-disable-next-line no-console
66
+ console.error(error)
67
+ reject(error)
68
+ }
69
+ })
70
+
71
+ // eslint-disable-next-line no-async-promise-executor
72
+ const onRemoveFavorite = (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
73
+ try {
74
+ await removeFavorite({ workspaceId: idOrSlug })
75
+ await workspaceAiClient.workspacesAi.invalidate()
76
+ if (!pendingRemoveFav) {
77
+ resolve(true)
78
+ }
79
+ } catch (error) {
80
+ // eslint-disable-next-line no-console
81
+ console.error(error)
82
+ reject(error)
83
+ }
84
+ })
85
+
86
+ const filtered = useMemo(
87
+ // Recreate the list so that the favorites list is taken into account
88
+ () => filter ? workspaces.filter(w => w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...workspaces],
89
+ [workspaces, filter, listFavorites],
90
+ )
91
+
92
+ return (
93
+ <Flex w={12} sx={{ gap: '16px' }} flexDirection="column">
94
+ <IconInput icon={<Search />} value={filter} onChange={setFilter} className="search" />
95
+ {!!filtered.length &&
96
+ <ListGroup
97
+ list={filtered}
98
+ keygen={w => w.id}
99
+ onClick={onClick}
100
+ style={{ gap: '6px', display: 'flex', flexDirection: 'column' }}
101
+ renderLabel={w => <CardSpace name={w.name} logoUrl={w.logo} icon={<Circle />} onClick={() => onClick(w)} />}
102
+ renderDescription={w => w.description}
103
+ renderAfterElement={(w) =>
104
+ <ButtonFavorite favorite={{ idOrSlug: w?.id, listFavorites, onAddFavorite, onRemoveFavorite }} />}
105
+ optionClassName={w => (filter && !w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
106
+ ? 'filtered-out'
107
+ : ''
108
+ }
109
+ className="option-list"
110
+ />
111
+ }
112
+ {!!workspaces.length && !filtered.length &&
113
+ <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />}
114
+ {!workspaces.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
115
+ </Flex>
116
+ )
117
+ }
118
+
119
+
120
+ const WorkspaceHeader = <T extends NavigationMap, K extends keyof T>({ data }: { data: NavigationItem<T, K> }) => {
121
+ const { close: closeRightPanel } = useRightPanel()
122
+ const workspaceId = (data.props as any)['workspaceId']
123
+ if (!workspaceId) return
124
+
125
+ const workspace = workspaceAiClient.workspaceAi.useQuery({ id: workspaceId })
126
+ return <Flex justifyContent="space-between" alignItems="center" flex={1}>
127
+ {data.component === 'workspaceResource' ? 'Spaces' : workspace.name}
128
+ {data.fullScreen && <IconButton title={'t.close'} aria-label={'t.close'} onClick={closeRightPanel}> <Times /> </IconButton>}
129
+ </Flex>
130
+ }
131
+
132
+ interface WorkspaceTabNavigatorProps {
133
+ getNavigateParam: (workspace: WorkspaceResponse) => NavigationItem<NavigationMap, string>,
134
+ visibility?: WorkspaceVisibilityLevelEnum,
135
+ className?: string,
136
+ }
137
+ export function WorkspaceTabNavigator<T extends NavigationMap, K extends keyof T>({ components, getNavigateParam, visibility, className }:
138
+ Omit<ComponentNavigatorProps<T, K>, 'initialItem'> & WorkspaceTabNavigatorProps) {
139
+
140
+ const workspaceTabComponents = useMemo(() => ({
141
+ workspace: memo(function WorkspacesTab() {
142
+ const { navigate } = useComponentNavigation()
143
+ return (<WorkspaceSourcesTab visibility={visibility ?? 'all'} onClick={(w) => navigate(getNavigateParam(w))} />)
144
+ }),
145
+ ...components,
146
+ }), [components])
147
+
148
+ return <ComponentNavigator
149
+ initialItem={{ component: 'workspace' }}
150
+ components={workspaceTabComponents}
151
+ className={className}
152
+ renderTitle={(data) => <WorkspaceHeader data={data} />}
153
+ />
154
+ }
155
+
156
+ const dictionary = {
157
+ en: {
158
+ noSearchResults: "Your search didn't yield results.",
159
+ noSearchResultsDescription: 'Please, try another search term.',
160
+ noData: 'There are no spaces yet.',
161
+ noDataDescription: 'Use the AI portal to create new spaces.',
162
+ apply: 'Apply',
163
+ },
164
+ pt: {
165
+ noSearchResults: 'Sua busca não produziu resultados',
166
+ noSearchResultsDescription: 'Por favor, tente outra busca.',
167
+ noData: 'Ainda não há spaces.',
168
+ noDataDescription: 'Use o Portal AI para criar novos spaces.',
169
+ apply: 'Apply',
170
+ },
171
+ } satisfies Dictionary
172
+
@@ -1,5 +1,6 @@
1
- import { Checkbox, Text } from '@citric/core'
1
+ import { Checkbox, Flex, Text } from '@citric/core'
2
2
  import { listToClass } from '@stack-spot/portal-theme'
3
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
3
4
  import { useMemo } from 'react'
4
5
  import { Accordion } from '../Accordion'
5
6
  import { RadioCheckBox } from './styled'
@@ -9,30 +10,40 @@ import { CheckProps } from './types'
9
10
  * Renders a checkbox group where each option has a label and a description.
10
11
  * The description in placed under the label and checkbox as an accordion.
11
12
  */
12
- export function DescribedCheckboxGroup<T>({
13
- keygen,
14
- onChange,
15
- options,
16
- renderDescription,
17
- renderLabel,
13
+ export function DescribedCheckboxGroup<T>({
14
+ keygen,
15
+ onChange,
16
+ options,
17
+ renderDescription,
18
+ renderLabel,
18
19
  renderBeforeElement,
19
20
  renderAfterElement,
20
- optionClassName,
21
- optionStyle,
22
- value,
23
- className,
21
+ optionClassName,
22
+ optionStyle,
23
+ value,
24
+ className,
24
25
  style }: CheckProps<T>) {
26
+ const t = useTranslate(dictionary)
27
+ const allSelected = options.length > 0 && options.every(option => value.includes(option))
28
+
29
+ const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
30
+ if (e.target.checked) {
31
+ onChange(options)
32
+ } else {
33
+ onChange([])
34
+ }
35
+ }
36
+
25
37
  const items = useMemo(() => options.map((option) => {
26
38
  const label = renderLabel(option)
27
39
  const description = renderDescription(option)
28
-
29
40
  const header = (
30
41
  <label>
31
42
  <Checkbox
32
43
  checked={value.includes(option)}
33
44
  onChange={(e) => {
34
45
  if (e.target.checked && !value.includes(option)) onChange([...value, option])
35
- else onChange(value.filter(item => item !== option))
46
+ else onChange(value.filter(item => item !== option))
36
47
  }}
37
48
  />
38
49
  {typeof label === 'string' ? <Text>{label}</Text> : label}
@@ -55,5 +66,25 @@ export function DescribedCheckboxGroup<T>({
55
66
  )
56
67
  }), [options, value])
57
68
 
58
- return <RadioCheckBox style={style} className={className}>{items}</RadioCheckBox>
69
+ return <RadioCheckBox style={style} className={className}>
70
+ <Flex as="li" alignItems="center" sx={{ pl: 4 }}>
71
+ <Checkbox
72
+ checked={allSelected}
73
+ onChange={handleSelectAll}
74
+ />
75
+ <Text>{allSelected ? t.removeAll : t.selectAll}</Text>
76
+ </Flex>
77
+ {items}
78
+ </RadioCheckBox>
59
79
  }
80
+
81
+ const dictionary = {
82
+ en: {
83
+ selectAll: 'Select all',
84
+ removeAll: 'Remove all',
85
+ },
86
+ pt: {
87
+ selectAll: 'Selecionar todos',
88
+ removeAll: 'Remover todos',
89
+ },
90
+ } satisfies Dictionary
@@ -1,8 +1,9 @@
1
- import { useMemo } from 'react'
1
+ import { useEffect, useMemo, useRef } from 'react'
2
2
  import { RightPanelTabs } from '../../components/RightPanelTabs'
3
3
  import { useCurrentChat } from '../../context/hooks'
4
+ import { isAgentDefault } from '../../utils/agent'
4
5
  import { checkIsTrial } from '../../utils/check-is-trial'
5
- import { AgentsTab } from './AgentsTab'
6
+ import { AgentsTab, AgentsTabWorkspace } from './AgentsTab'
6
7
  import { useAgentsDictionary } from './dictionary'
7
8
 
8
9
  /**
@@ -12,18 +13,27 @@ export const AgentsPanel = () => {
12
13
  const t = useAgentsDictionary()
13
14
  const chat = useCurrentChat()
14
15
  const isTrial = checkIsTrial()
16
+ const agent = useRef(chat.get('agent'))
17
+
18
+ useEffect(() => {
19
+ if (!isAgentDefault(chat.get('agent')?.slug)) {
20
+ agent.current = chat.get('agent')
21
+ }
22
+ }, [chat])
15
23
 
16
24
  const tabs= useMemo(() => isTrial ? [
17
- { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
18
- { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
19
- { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
25
+ { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" agent={agent} /> },
26
+ { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" agent={agent} /> },
27
+ { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" agent={agent} /> },
20
28
  ]: [
21
- { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
22
- { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
23
- { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
24
- { title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" /> },
25
- { title: t.account, content: <AgentsTab key="account" visibility="ACCOUNT" /> },
26
- ], [t, isTrial])
29
+ { title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" agent={agent} /> },
30
+ { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" agent={agent} /> },
31
+ { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" agent={agent} /> },
32
+ { title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" agent={agent} /> },
33
+ { title: t.spaces, content: <AgentsTabWorkspace key="workspace" visibility="WORKSPACE" agent={agent} /> },
34
+ { title: t.account, content: <AgentsTab key="account" visibility="ACCOUNT" agent={agent} /> },
35
+
36
+ ], [t, isTrial, agent])
27
37
 
28
38
  return <RightPanelTabs key={chat.id} tabs={tabs} />
29
39
  }
@@ -2,21 +2,32 @@ import { Button, IconBox, Text } from '@citric/core'
2
2
  import { Agent, Search } from '@citric/icons'
3
3
  import { Placeholder } from '@stack-spot/portal-components/Placeholder'
4
4
  import { MiniLogo } from '@stack-spot/portal-components/svg'
5
- import { agentClient } from '@stack-spot/portal-network'
5
+ import { agentClient, workspaceAiClient } from '@stack-spot/portal-network'
6
6
  import { AgentResponse, VisibilityLevel } from '@stack-spot/portal-network/api/agent'
7
- import { useMemo, useState } from 'react'
7
+ import { WorkspaceResponse } from '@stack-spot/portal-network/api/workspace-ai'
8
+ import { useCallback, useMemo, useState } from 'react'
8
9
  import { ButtonFavorite } from '../../components/ButtonFavorite'
10
+ import { NavigationItem } from '../../components/ComponentNavigator'
9
11
  import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
10
12
  import { IconInput } from '../../components/IconInput'
13
+ import { WorkspaceTabNavigator } from '../../components/WorkspaceTabNavigator'
11
14
  import { useCurrentChat } from '../../context/hooks'
12
15
  import { useRightPanel } from '../../right-panel/hooks'
16
+ import { ChatProperties } from '../../state/ChatState'
13
17
  import { isAgentDefault } from '../../utils/agent'
14
18
  import { AgentDescription } from './AgentDescription'
15
19
  import { useAgentsDictionary } from './dictionary'
16
20
  import { AgentLabel } from './styled'
17
21
  import { useAgentFavorites } from './useAgentFavorites'
18
22
 
19
- export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT-IN' }) => {
23
+ export interface AgentTabProps {
24
+ visibility: VisibilityLevel | 'BUILT-IN',
25
+ workspaceId?: string,
26
+ agent: React.MutableRefObject<ChatProperties['agent']>,
27
+ showSubmitButton?: boolean,
28
+ }
29
+
30
+ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = true }: AgentTabProps) => {
20
31
  const t = useAgentsDictionary()
21
32
  const { close } = useRightPanel()
22
33
  const chat = useCurrentChat()
@@ -26,10 +37,14 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
26
37
 
27
38
  const agentsBuiltIn = agentClient.publicAgents.useQuery({})
28
39
  const agentDefault = agentsBuiltIn.find((agent) => isAgentDefault(agent.slug))
29
- const agents = visibility === 'BUILT-IN' ? agentsBuiltIn : agentClient.agents.useQuery({ visibility })
30
-
40
+ const agents = workspaceId
41
+ ? workspaceAiClient.getAgentFromWorkspaceAi.useQuery({ workspaceId })
42
+ : visibility === 'BUILT-IN' ? agentsBuiltIn : agentClient.agents.useQuery({ visibility })
43
+
31
44
  const [value, setValue] = useState<AgentResponse | undefined>(
32
- chat.get('agent') ? agents.find(a => a.id === chat.get('agent')?.id) : agentDefault,
45
+ agent.current
46
+ ? agents.find(a => a.id === agent.current?.id)
47
+ : chat.get('agent') ? agents.find(a => a.id === chat.get('agent')?.id) : agentDefault,
33
48
  )
34
49
 
35
50
  const filtered = useMemo(
@@ -53,6 +68,13 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
53
68
  close()
54
69
  }
55
70
 
71
+ const onChange = useCallback((newValue: AgentResponse) => {
72
+ const isBuiltIn = visibility === 'BUILT-IN' || agentsBuiltIn.some((agent) => agent.id === newValue.id)
73
+
74
+ setValue(newValue)
75
+ agent.current = { ...newValue, builtIn: isBuiltIn, label: newValue.name }
76
+ }, [])
77
+
56
78
  return (
57
79
  <>
58
80
  <div className="content">
@@ -65,7 +87,7 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
65
87
  )}
66
88
  keygen={a => a.id}
67
89
  value={value}
68
- onChange={setValue}
90
+ onChange={onChange}
69
91
  renderLabel={({ name, avatar, id }) => (
70
92
  <AgentLabel>
71
93
  {id ? (avatar ? <img src={avatar} /> : <IconBox size="xs"><Agent /></IconBox>) : <MiniLogo />}
@@ -74,7 +96,7 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
74
96
  )}
75
97
  renderDescription={a => <AgentDescription
76
98
  agentId={a.id}
77
- visibility={visibility}
99
+ visibility={visibility}
78
100
  description={a.description}
79
101
  llm={a.llm_config?.model_slug}
80
102
  numberOfKnowledgeSources={a.knowledge_sources_config?.knowledge_sources?.length ?? 0}
@@ -89,7 +111,18 @@ export const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT
89
111
  <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />}
90
112
  {!agents.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
91
113
  </div>
92
- {!!filtered.length && <Button onClick={submit} disabled={!value}>{t.apply}</Button>}
114
+ {!!filtered.length && showSubmitButton && <Button onClick={submit} disabled={!value}>{t.apply}</Button>}
93
115
  </>
94
116
  )
95
117
  }
118
+
119
+ export function AgentsTabWorkspace({ agent, visibility, showSubmitButton }: AgentTabProps) {
120
+ const workspaceTabComponents = useMemo(() => ({ agent: AgentsTab }), [agent])
121
+
122
+ const buildNavigateParams = (workspace: WorkspaceResponse): NavigationItem<typeof workspaceTabComponents> => ({
123
+ component: 'agent',
124
+ props: { visibility, workspaceId: workspace.id, agent, showSubmitButton },
125
+ })
126
+
127
+ return <WorkspaceTabNavigator components={workspaceTabComponents} getNavigateParam={buildNavigateParams} />
128
+ }
@@ -17,6 +17,7 @@ const dictionary = {
17
17
  description: 'Description',
18
18
  favorites: 'Favorites',
19
19
  tools: 'Tools',
20
+ spaces: 'Spaces',
20
21
  },
21
22
  pt: {
22
23
  title: 'Agentes',
@@ -34,6 +35,8 @@ const dictionary = {
34
35
  description: 'Descrição',
35
36
  favorites: 'Favoritos',
36
37
  tools: 'Ferramentas',
38
+ spaces: 'Spaces',
39
+
37
40
  },
38
41
  } satisfies Dictionary
39
42
 
@@ -1,4 +1,4 @@
1
- import { agentClient, aiClient, workspaceClient } from '@stack-spot/portal-network'
1
+ import { agentClient, aiClient, workspaceAiClient, workspaceClient } from '@stack-spot/portal-network'
2
2
  import { ChatProperties } from '../../state/ChatState'
3
3
  import { LabeledWithImage } from '../../state/types'
4
4
 
@@ -44,9 +44,15 @@ export async function findWorkspace(id: string | null): Promise<ChatProperties['
44
44
  */
45
45
  export async function getAllAgents(): Promise<LabeledWithImage[]> {
46
46
  try {
47
- const [agents, publicAgents] = await Promise.all([agentClient.agents.query({}), agentClient.publicAgents.query({})])
47
+ const [agents, publicAgents, workspaceAgents] = await Promise.all([
48
+ agentClient.agents.query({}),
49
+ agentClient.publicAgents.query({}),
50
+ workspaceAiClient.workspacesContentsByType.query({ contentType: 'agent' }),
51
+ ])
52
+
48
53
  const builtInAgents = publicAgents.map((a) => ({ id: a.id, label: a.name, image: a.avatar, slug: a.slug, builtIn: true }))
49
- return agents.map(a => ({ id: a.id, label: a.name, image: a.avatar, slug: a.slug, builtIn: false })).concat(builtInAgents)
54
+ return [...agents, ... workspaceAgents.agents].map(a => ({ id: a.id, label: a.name, image: a.avatar, slug: a.slug, builtIn: false }))
55
+ .concat(builtInAgents)
50
56
  } catch (error) {
51
57
  // eslint-disable-next-line no-console
52
58
  console.error(error)
@@ -1,24 +1,29 @@
1
1
  import { Button } from '@citric/core'
2
2
  import { Search } from '@citric/icons'
3
3
  import { Placeholder } from '@stack-spot/portal-components/Placeholder'
4
- import { aiClient, dataIntegrationClient } from '@stack-spot/portal-network'
4
+ import { aiClient, dataIntegrationClient, workspaceAiClient } from '@stack-spot/portal-network'
5
5
  import { KnowledgeSourceItemResponse, VisibilityLevelEnum } from '@stack-spot/portal-network/api/ai'
6
+ import { WorkspaceResponse } from '@stack-spot/portal-network/api/workspace-ai'
6
7
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
7
8
  import { difference, uniqBy } from 'lodash'
8
9
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
9
10
  import { ButtonFavorite } from '../components/ButtonFavorite'
11
+ import { NavigationItem } from '../components/ComponentNavigator'
10
12
  import { DescribedCheckboxGroup } from '../components/form/DescribedCheckboxGroup'
11
13
  import { IconInput } from '../components/IconInput'
12
14
  import { RightPanelTabs } from '../components/RightPanelTabs'
15
+ import { WorkspaceTabNavigator } from '../components/WorkspaceTabNavigator'
13
16
  import { useCurrentChat, useWidget, useWidgetState } from '../context/hooks'
14
17
  import { useRightPanel } from '../right-panel/hooks'
15
18
  import { ChatProperties } from '../state/ChatState'
16
19
  import { checkIsTrial } from '../utils/check-is-trial'
17
20
 
18
- interface TabProps {
21
+ export interface TabProps {
19
22
  visibility: VisibilityLevelEnum,
20
- onSubmit: () => void,
21
23
  allKS: React.MutableRefObject<ChatProperties['knowledgeSources']>,
24
+ workspaceId?: string,
25
+ showSubmitButton?: boolean,
26
+ onSubmit?: () => void,
22
27
  }
23
28
 
24
29
  export const KnowledgeSources = () => {
@@ -55,7 +60,7 @@ const KnowledgeSourcesPanel = () => {
55
60
  useEffect(() => {
56
61
  allKS.current = chat.get('knowledgeSources') ?? []
57
62
  }, [chat])
58
-
63
+
59
64
  const tabs = isTrial ? [
60
65
  { title: t.favorites, content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
61
66
  { title: t.personal, content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
@@ -63,19 +68,23 @@ const KnowledgeSourcesPanel = () => {
63
68
  { title: t.favorites, content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
64
69
  { title: t.personal, content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
65
70
  { title: t.shared, content: <KnowledgeSourcesTab key="shared" visibility="shared" allKS={allKS} onSubmit={onSubmit} /> },
66
- { title: t.account, content: <KnowledgeSourcesTab key="account" visibility="account" allKS={allKS} onSubmit={onSubmit} /> },
71
+ { title: t.spaces, content: <KnowledgeSourcesTabWorkspace key="workspace" visibility="workspace" allKS={allKS} onSubmit={onSubmit} /> },
72
+ { title: t.account, content: <KnowledgeSourcesTab key="account" visibility="account" allKS={allKS} onSubmit={onSubmit} /> },
67
73
  ]
68
-
74
+
69
75
  return <RightPanelTabs key={chat.id} tabs={tabs} />
70
76
  }
71
77
 
72
- const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit }: TabProps) => {
78
+ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId, showSubmitButton = true }: TabProps) => {
73
79
  const t = useTranslate(dictionary)
74
80
  const [filter, setFilter] = useState('')
75
-
76
- const knowledgeSources = aiClient.knowledgeSources.useQuery({
77
- visibility, order: 'a-to-z', types: ['snippet', 'api', 'event', 'custom'],
78
- })
81
+
82
+ const knowledgeSources = workspaceId
83
+ ? workspaceAiClient.getKSFromWorkspaceAi.useQuery({ workspaceId })
84
+ : aiClient.knowledgeSources.useQuery({
85
+ visibility, order: 'a-to-z', types: ['snippet', 'api', 'event', 'custom'],
86
+ })
87
+
79
88
  const listFavorites = dataIntegrationClient.knowledgeSources.useQuery({ visibility: 'favorite' })
80
89
  const [addFavorite, pendingAddFav] = dataIntegrationClient.addFavoriteKnowledgeSource.useMutation()
81
90
  const [removeFavorite, pendingRemoveFav] = dataIntegrationClient.removeFavoriteKnowledgeSource.useMutation()
@@ -131,7 +140,7 @@ const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit }: TabProps) => {
131
140
  reject(error)
132
141
  }
133
142
  })
134
-
143
+
135
144
  const [value, setValue] = useState<KnowledgeSourceItemResponse[]>((() => {
136
145
  const currentlySelected = allKS.current?.map(ks => ks.id)
137
146
  return knowledgeSources.filter(ks => currentlySelected?.includes(ks.slug))
@@ -139,7 +148,7 @@ const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit }: TabProps) => {
139
148
  const filtered = useMemo(
140
149
  () => filter
141
150
  // Recreate the list so that the favorites list is taken into account
142
- ? knowledgeSources.filter(ks => value.includes(ks) || ks.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
151
+ ? knowledgeSources.filter(ks => value.includes(ks) || ks.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
143
152
  : [...knowledgeSources]
144
153
  , [knowledgeSources, filter, value, listFavorites],
145
154
  )
@@ -181,11 +190,23 @@ const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit }: TabProps) => {
181
190
  )}
182
191
  {!filtered.length && <Placeholder title={t.noData} description={t.noDataDescription} className="no-data-placeholder" />}
183
192
  </div>
184
- {!!filtered.length && <Button onClick={onSubmit}>{t.apply}</Button>}
193
+ {!!filtered.length && showSubmitButton && <Button onClick={onSubmit}>{t.apply}</Button>}
185
194
  </>
186
195
  )
187
196
  }
188
197
 
198
+ export function KnowledgeSourcesTabWorkspace({ allKS, onSubmit }: TabProps) {
199
+ const workspaceTabComponents = useMemo(() => ({ ks: KnowledgeSourcesTab }), [allKS, onSubmit])
200
+
201
+ const buildNavigateParams = (workspace: WorkspaceResponse): NavigationItem<typeof workspaceTabComponents> => ({
202
+ component: 'ks',
203
+ props: { visibility: 'workspace', workspaceId: workspace.id, allKS, onSubmit },
204
+ })
205
+
206
+ return <WorkspaceTabNavigator components={workspaceTabComponents} getNavigateParam={buildNavigateParams} />
207
+ }
208
+
209
+
189
210
  const dictionary = {
190
211
  en: {
191
212
  title: 'Knowledge Sources',
@@ -199,6 +220,7 @@ const dictionary = {
199
220
  noSearchResultsDescription: 'Please, try another search term.',
200
221
  noData: 'There are no knowledge sources in this category yet.',
201
222
  noDataDescription: 'Use the tabs above to try other categories or use the AI portal to create new knowledge sources.',
223
+ spaces: 'Spaces',
202
224
  },
203
225
  pt: {
204
226
  title: 'Knowledge Sources',
@@ -212,5 +234,6 @@ const dictionary = {
212
234
  noSearchResultsDescription: 'Por favor, tente outra busca.',
213
235
  noData: 'Ainda não há knowledge sources nesta categoria.',
214
236
  noDataDescription: 'Use as abas acima para tentar outras categorias ou use o Portal AI para criar novos knowledge sources.',
237
+ spaces: 'Spaces',
215
238
  },
216
239
  } satisfies Dictionary
@@ -1,6 +1,6 @@
1
1
  import { Flex, IconBox, Image } from '@citric/core'
2
2
  import { Agent } from '@citric/icons'
3
- import { agentClient } from '@stack-spot/portal-network'
3
+ import { agentClient, workspaceAiClient } from '@stack-spot/portal-network'
4
4
  import { AgentResponse } from '@stack-spot/portal-network/api/agent'
5
5
  import { uniqBy } from 'lodash'
6
6
  import { useCallback } from 'react'
@@ -46,6 +46,7 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
46
46
  const personalAgents = agentClient.agents.useQuery({ visibility: 'PERSONAL' })
47
47
  const publicAgents = agentClient.publicAgents.useQuery({})
48
48
  const builtInsAgents = [...publicAgents.map((agent) => ({ ...agent, visibility_level: 'builtIn' }))]
49
+ const workspaceAgents = workspaceAiClient.workspacesContentsByType.useQuery({ contentType: 'agent' })
49
50
  let accountAgents: AgentResponse[] = []
50
51
  let sharedAgents: AgentResponse[] = []
51
52
  if (!isTrial) {
@@ -53,7 +54,7 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
53
54
  sharedAgents = agentClient.agents.useQuery({ visibility: 'SHARED' }) || []
54
55
  }
55
56
 
56
- return uniqBy([...personalAgents, ...accountAgents, ...sharedAgents, ...builtInsAgents], 'id')
57
+ return uniqBy([...personalAgents, ...workspaceAgents.agents, ...accountAgents, ...sharedAgents, ...builtInsAgents], 'id')
57
58
  }
58
59
 
59
60
  return <Selector
@@ -66,7 +67,7 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
66
67
  regex: agentRegex,
67
68
  urlBuilder: (agent) => `/agents/${agent?.id}`,
68
69
  searchProp: 'name',
69
- sections: isTrial ? ['favorite', 'personal', 'builtIn'] : ['favorite', 'personal', 'account', 'shared', 'builtIn'],
70
+ sections: isTrial ? ['favorite', 'personal', 'builtIn'] : ['favorite', 'personal', 'workspace', 'account', 'shared', 'builtIn'],
70
71
  renderComponentItem: AgentItem,
71
72
  isEnabled: isAgentEnabled,
72
73
  onSelect: onSelectItem,
@@ -1,4 +1,4 @@
1
- import { ChevronRight, Code, KnowledgeSource, Send, Stack, Times, Workspace } from '@citric/icons'
1
+ import { ChevronRight, Circle, Code, KnowledgeSource, Send, Stack, Times } from '@citric/icons'
2
2
  import { IconButton } from '@citric/ui'
3
3
  import { listToClass } from '@stack-spot/portal-theme'
4
4
  import { useEffect, useRef } from 'react'
@@ -62,8 +62,8 @@ export const ButtonGroup = ({ onSend, onCancel, expanded, setExpanded, isLoading
62
62
  style={{ width: expanded ? featureButtonsWidth.current : 0 }}
63
63
  >
64
64
  {features.workspace && (
65
- <IconButton aria-label={t.workspace} title={t.workspace} onClick={() => widget.set('panel', 'workspace')}>
66
- <Workspace />
65
+ <IconButton aria-label={t.space} title={t.space} onClick={() => widget.set('panel', 'workspace')}>
66
+ <Circle />
67
67
  </IconButton>
68
68
  )}
69
69
  {features.knowledgeSource && (