@stack-spot/ai-chat-widget 1.18.0 → 1.19.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 (97) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/StackspotAIWidget.js +1 -1
  3. package/dist/app-metadata.json +6 -6
  4. package/dist/components/ComponentNavigator.d.ts +38 -0
  5. package/dist/components/ComponentNavigator.d.ts.map +1 -0
  6. package/dist/components/ComponentNavigator.js +33 -0
  7. package/dist/components/ComponentNavigator.js.map +1 -0
  8. package/dist/components/ListResource.d.ts +29 -0
  9. package/dist/components/ListResource.d.ts.map +1 -0
  10. package/dist/components/ListResource.js +17 -0
  11. package/dist/components/ListResource.js.map +1 -0
  12. package/dist/components/RightPanelForm.d.ts.map +1 -1
  13. package/dist/components/RightPanelForm.js +29 -1
  14. package/dist/components/RightPanelForm.js.map +1 -1
  15. package/dist/components/Selector/index.js +5 -5
  16. package/dist/components/Selector/index.js.map +1 -1
  17. package/dist/components/Selector/styled.d.ts +3 -1
  18. package/dist/components/Selector/styled.d.ts.map +1 -1
  19. package/dist/components/Selector/styled.js +2 -1
  20. package/dist/components/Selector/styled.js.map +1 -1
  21. package/dist/components/WorkspaceTabNavigator.d.ts +17 -0
  22. package/dist/components/WorkspaceTabNavigator.d.ts.map +1 -0
  23. package/dist/components/WorkspaceTabNavigator.js +95 -0
  24. package/dist/components/WorkspaceTabNavigator.js.map +1 -0
  25. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  26. package/dist/components/form/DescribedCheckboxGroup.js +23 -2
  27. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  28. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  29. package/dist/views/Agents/AgentsPanel.js +19 -11
  30. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  31. package/dist/views/Agents/AgentsTab.d.ts +9 -3
  32. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  33. package/dist/views/Agents/AgentsTab.js +25 -7
  34. package/dist/views/Agents/AgentsTab.js.map +1 -1
  35. package/dist/views/Agents/dictionary.d.ts +1 -1
  36. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  37. package/dist/views/Agents/dictionary.js +2 -0
  38. package/dist/views/Agents/dictionary.js.map +1 -1
  39. package/dist/views/Chat/StepsList.js +2 -2
  40. package/dist/views/Chat/StepsList.js.map +1 -1
  41. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  42. package/dist/views/ChatHistory/utils.js +12 -3
  43. package/dist/views/ChatHistory/utils.js.map +1 -1
  44. package/dist/views/KnowledgeSources.d.ts +12 -0
  45. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  46. package/dist/views/KnowledgeSources.js +20 -6
  47. package/dist/views/KnowledgeSources.js.map +1 -1
  48. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  49. package/dist/views/MessageInput/AgentSelector.js +11 -7
  50. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  51. package/dist/views/MessageInput/ButtonGroup.js +2 -2
  52. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  53. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  54. package/dist/views/MessageInput/QuickCommandSelector.js +12 -4
  55. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  56. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  57. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  58. package/dist/views/MessageInput/dictionary.js +4 -4
  59. package/dist/views/MessageInput/dictionary.js.map +1 -1
  60. package/dist/views/Stacks.d.ts +9 -0
  61. package/dist/views/Stacks.d.ts.map +1 -1
  62. package/dist/views/Stacks.js +37 -14
  63. package/dist/views/Stacks.js.map +1 -1
  64. package/dist/views/Workspaces/WorkspacesTab.d.ts +20 -0
  65. package/dist/views/Workspaces/WorkspacesTab.d.ts.map +1 -0
  66. package/dist/views/Workspaces/WorkspacesTab.js +64 -0
  67. package/dist/views/Workspaces/WorkspacesTab.js.map +1 -0
  68. package/dist/views/{Workspaces.d.ts → Workspaces/index.d.ts} +1 -1
  69. package/dist/views/Workspaces/index.d.ts.map +1 -0
  70. package/dist/views/Workspaces/index.js +67 -0
  71. package/dist/views/Workspaces/index.js.map +1 -0
  72. package/package.json +3 -3
  73. package/src/app-metadata.json +6 -6
  74. package/src/components/ComponentNavigator.tsx +103 -0
  75. package/src/components/ListResource.tsx +60 -0
  76. package/src/components/RightPanelForm.tsx +29 -1
  77. package/src/components/Selector/index.tsx +5 -5
  78. package/src/components/Selector/styled.ts +3 -2
  79. package/src/components/WorkspaceTabNavigator.tsx +175 -0
  80. package/src/components/form/DescribedCheckboxGroup.tsx +38 -7
  81. package/src/views/Agents/AgentsPanel.tsx +21 -11
  82. package/src/views/Agents/AgentsTab.tsx +42 -9
  83. package/src/views/Agents/dictionary.ts +3 -0
  84. package/src/views/Chat/StepsList.tsx +2 -2
  85. package/src/views/ChatHistory/utils.ts +14 -3
  86. package/src/views/KnowledgeSources.tsx +37 -14
  87. package/src/views/MessageInput/AgentSelector.tsx +20 -8
  88. package/src/views/MessageInput/ButtonGroup.tsx +3 -3
  89. package/src/views/MessageInput/QuickCommandSelector.tsx +19 -6
  90. package/src/views/MessageInput/dictionary.ts +4 -4
  91. package/src/views/Stacks.tsx +57 -17
  92. package/src/views/Workspaces/WorkspacesTab.tsx +120 -0
  93. package/src/views/Workspaces/index.tsx +76 -0
  94. package/dist/views/Workspaces.d.ts.map +0 -1
  95. package/dist/views/Workspaces.js +0 -103
  96. package/dist/views/Workspaces.js.map +0 -1
  97. package/src/views/Workspaces.tsx +0 -137
@@ -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 { NavigationComponent } 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): NavigationComponent<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
+ spots: 'Spots',
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
+ spots: 'Spots',
39
+
37
40
  },
38
41
  } satisfies Dictionary
39
42
 
@@ -1,5 +1,5 @@
1
1
  import { Button, IconBox, Text } from '@citric/core'
2
- import { CheckCircleFill, Circle, PlayFill, TimesCircleFill } from '@citric/icons'
2
+ import { CheckCircleFill, PlayFill, Spaces, TimesCircleFill } from '@citric/icons'
3
3
  import { LoadingCircular } from '@citric/ui'
4
4
  import { AnimatedHeight } from '@stack-spot/portal-components/AnimatedHeight'
5
5
  import { ChatStep, StepChatStep } from '@stack-spot/portal-network'
@@ -27,7 +27,7 @@ function getStatusIcon(status: ChatStep['status']) {
27
27
  switch (status) {
28
28
  case 'error': return <IconBox {...iconBoxProps}><TimesCircleFill /></IconBox>
29
29
  case 'success': return <IconBox {...iconBoxProps}><CheckCircleFill /></IconBox>
30
- case 'pending': return <IconBox {...iconBoxProps}><Circle /></IconBox>
30
+ case 'pending': return <IconBox {...iconBoxProps}><Spaces /></IconBox>
31
31
  case 'running': return <LoadingCircular className="loading" colorScheme="inverse" size="xs" />
32
32
  }
33
33
  }
@@ -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,20 @@ 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 [agentsAccount, agentsPersonal, agentsShared, publicAgents, workspaceAgents] = await Promise.all([
48
+ agentClient.agents.query({ visibility: 'ACCOUNT' }),
49
+ agentClient.agents.query({ visibility: 'PERSONAL' }),
50
+ agentClient.agents.query({ visibility: 'SHARED' }),
51
+ agentClient.publicAgents.query({}),
52
+
53
+ workspaceAiClient.workspacesContentsByType.query({ contentType: 'agent' }),
54
+ ])
55
+
56
+ const workspaceAgentsAsArray = workspaceAgents.flatMap(({ agents }) => agents)
48
57
  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)
58
+ return [...agentsAccount, ...agentsPersonal, ...agentsShared, ...workspaceAgentsAsArray]
59
+ .map(a => ({ id: a?.id, label: a?.name, image: a?.avatar, slug: a?.slug, builtIn: false }))
60
+ .concat(builtInAgents)
50
61
  } catch (error) {
51
62
  // eslint-disable-next-line no-console
52
63
  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 { NavigationComponent } 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.spots, 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): NavigationComponent<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
+ spots: 'Spots',
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
+ spots: 'Spots',
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'
@@ -9,17 +9,23 @@ import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
9
9
  import { agentRegex } from '../../regex'
10
10
  import { useAgentFavorites } from '../Agents/useAgentFavorites'
11
11
 
12
- const AgentItem = ({ avatar, name }: AgentResponse) => {
12
+ type AgentWithSpaceName = AgentResponse & { spaceName?: string }
13
+
14
+ const AgentItem = ({ avatar, name, spaceName }: AgentWithSpaceName) => {
13
15
  const avatarComponent = avatar ? <Image width="32" height="32" radius="full" src={avatar} /> : <IconBox size="md"><Agent /></IconBox>
14
16
 
15
17
  return <Flex flexWrap="nowrap" alignItems="center" sx={{ gap: '8px' }}>
16
18
  {avatarComponent}
17
- <p className="selector-title">{name}</p>
19
+ <div>
20
+ <p className="selector-description">{spaceName}</p>
21
+ <p className="selector-title">{name}</p>
22
+ </div>
18
23
  </Flex>
19
24
  }
20
25
 
21
26
  export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
22
- inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>, }) => {
27
+ inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
28
+ }) => {
23
29
  const chat = useCurrentChat()
24
30
  const isAgentEnabled = useCurrentChatState('features').agent
25
31
 
@@ -33,8 +39,10 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
33
39
  const isBuiltIn = agent.visibility_level === 'builtIn' || builtInsAgents.some((builtInAgent) => builtInAgent.id === agent.id)
34
40
  chat.set(
35
41
  'agent',
36
- { id: agent.id, label: agent.name, image: agent.avatar, slug: agent.slug,
37
- builtIn: isBuiltIn, visibility_level: agent.visibility_level },
42
+ {
43
+ id: agent.id, label: agent.name, image: agent.avatar, slug: agent.slug,
44
+ builtIn: isBuiltIn, visibility_level: agent.visibility_level,
45
+ },
38
46
  )
39
47
 
40
48
  if (!inputRef.current) return
@@ -46,6 +54,10 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
46
54
  const personalAgents = agentClient.agents.useQuery({ visibility: 'PERSONAL' })
47
55
  const publicAgents = agentClient.publicAgents.useQuery({})
48
56
  const builtInsAgents = [...publicAgents.map((agent) => ({ ...agent, visibility_level: 'builtIn' }))]
57
+ const workspaceAgents = workspaceAiClient.workspacesContentsByType.useQuery({ contentType: 'agent' })
58
+
59
+ const workspaceAgentsWithWorkspaceName = workspaceAgents.flatMap(({ agents, space_name }) =>
60
+ agents?.map((agent) => ({ ...agent, spaceName: space_name }))) as AgentWithSpaceName[]
49
61
  let accountAgents: AgentResponse[] = []
50
62
  let sharedAgents: AgentResponse[] = []
51
63
  if (!isTrial) {
@@ -53,7 +65,7 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
53
65
  sharedAgents = agentClient.agents.useQuery({ visibility: 'SHARED' }) || []
54
66
  }
55
67
 
56
- return uniqBy([...personalAgents, ...accountAgents, ...sharedAgents, ...builtInsAgents], 'id')
68
+ return uniqBy([...personalAgents, ...workspaceAgentsWithWorkspaceName, ...accountAgents, ...sharedAgents, ...builtInsAgents], 'id')
57
69
  }
58
70
 
59
71
  return <Selector
@@ -66,7 +78,7 @@ export const AgentSelector = ({ inputRef, isTrial }: { isTrial: boolean,
66
78
  regex: agentRegex,
67
79
  urlBuilder: (agent) => `/agents/${agent?.id}`,
68
80
  searchProp: 'name',
69
- sections: isTrial ? ['favorite', 'personal', 'builtIn'] : ['favorite', 'personal', 'account', 'shared', 'builtIn'],
81
+ sections: isTrial ? ['favorite', 'personal', 'builtIn'] : ['favorite', 'personal', 'workspace', 'account', 'shared', 'builtIn'],
70
82
  renderComponentItem: AgentItem,
71
83
  isEnabled: isAgentEnabled,
72
84
  onSelect: onSelectItem,
@@ -1,4 +1,4 @@
1
- import { ChevronRight, Code, KnowledgeSource, Send, Stack, Times, Workspace } from '@citric/icons'
1
+ import { ChevronRight, Code, KnowledgeSource, Send, Spaces, 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.spot} title={t.spot} onClick={() => widget.set('panel', 'workspace')}>
66
+ <Spaces />
67
67
  </IconButton>
68
68
  )}
69
69
  {features.knowledgeSource && (
@@ -1,11 +1,13 @@
1
1
  import { QuickCommand } from '@citric/icons'
2
- import { aiClient } from '@stack-spot/portal-network'
3
- import { QuickCommandListResponse } from '@stack-spot/portal-network/api/ai'
2
+ import { aiClient, workspaceAiClient } from '@stack-spot/portal-network'
3
+ import { QuickCommandResponse } from '@stack-spot/portal-network/api/ai'
4
4
  import { useCallback } from 'react'
5
5
  import { Selector } from '../../components/Selector'
6
6
  import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
7
7
  import { quickCommandRegex } from '../../regex'
8
8
 
9
+ type QuickCommandResponseWithSpaceName = QuickCommandResponse & { spaceName?: string }
10
+
9
11
  export const QuickCommandSelector = ({ inputRef, isTrial }:
10
12
  { isTrial: boolean, inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement> }) => {
11
13
  const chat = useCurrentChat()
@@ -65,7 +67,7 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
65
67
  }
66
68
  })
67
69
 
68
- const onSelectItem = useCallback((qc: QuickCommandListResponse) => {
70
+ const onSelectItem = useCallback((qc: QuickCommandResponseWithSpaceName) => {
69
71
  const newValue = `/${qc.slug}`
70
72
  chat.set('nextMessage', newValue)
71
73
 
@@ -74,8 +76,19 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
74
76
  inputRef.current.focus()
75
77
  }, [chat, inputRef])
76
78
 
77
- const QuickCommandItem = ({ slug, description }: QuickCommandListResponse) => <>
78
- <p className="selector-title">/{slug.toUpperCase()}</p>
79
+ const getQuickCommands = () => {
80
+ const quickCommands = aiClient.quickCommands.useQuery({ order: 'a-to-z' })
81
+ const quickCommandsFiltered = quickCommands.filter((qc) => qc.visibility_level.toLowerCase() !== 'workspace')
82
+ const workspaceQuickCommands = workspaceAiClient.workspacesContentsByType.useQuery({ contentType: 'quick_command' })
83
+ const workspaceQuickCommandsWithWorkspaceName: QuickCommandResponseWithSpaceName[] = workspaceQuickCommands
84
+ .flatMap(({ qcs, space_name }) => qcs?.map((qc) => ({ ...qc, spaceName: space_name }))) as QuickCommandResponseWithSpaceName[]
85
+
86
+ return [...quickCommandsFiltered, ...workspaceQuickCommandsWithWorkspaceName]
87
+ }
88
+
89
+ const QuickCommandItem = ({ slug, description, spaceName }: QuickCommandResponseWithSpaceName) => <>
90
+ <p className="selector-description">{spaceName}</p>
91
+ <p className="selector-title">/{slug?.toUpperCase()}</p>
79
92
  <p className="selector-description">{description}</p>
80
93
  </>
81
94
 
@@ -95,7 +108,7 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
95
108
  isEnabled: isQuickCommandEnabled,
96
109
  onSelect: onSelectItem,
97
110
  renderComponentItem: QuickCommandItem,
98
- useData: () => aiClient.quickCommands.useQuery({ order: 'a-to-z' }),
111
+ useData: getQuickCommands,
99
112
  }}
100
113
  />
101
114
  }
@@ -4,7 +4,7 @@ const dictionary = {
4
4
  en: {
5
5
  stack: 'Select stack',
6
6
  code: 'Open code editor',
7
- workspace: 'Select workspace',
7
+ spot: 'Select spot',
8
8
  knowledgeSource: 'Select knowledge sources',
9
9
  agent: 'Select agent',
10
10
  collapse: 'Hide buttons',
@@ -14,7 +14,7 @@ const dictionary = {
14
14
  cancel: 'Cancel',
15
15
  removeConfig: 'Remove all the configuration',
16
16
  removeStack: 'Stop using the current stack',
17
- removeWorkspace: 'Stop using the current workspace',
17
+ removeWorkspace: 'Stop using the current spot',
18
18
  removeKS: 'Stop using this knowledge source',
19
19
  selected: 'Selected',
20
20
  removeSelection: 'Remove current code selection',
@@ -23,7 +23,7 @@ const dictionary = {
23
23
  pt: {
24
24
  stack: 'Selecionar stack',
25
25
  code: 'Abrir editor de código',
26
- workspace: 'Selecionar workspace',
26
+ spot: 'Selecionar spot',
27
27
  knowledgeSource: 'Selecionar knowledge sources',
28
28
  agent: 'Selecionar agente',
29
29
  collapse: 'Esconder botões',
@@ -33,7 +33,7 @@ const dictionary = {
33
33
  cancel: 'Cancelar',
34
34
  removeConfig: 'Remover todas as configurações',
35
35
  removeStack: 'Parar de usar a stack atual',
36
- removeWorkspace: 'Parar de usar o workspace atual',
36
+ removeWorkspace: 'Parar de usar o spot atual',
37
37
  removeKS: 'Parar de usar este knowledge source',
38
38
  selected: 'Selecionado',
39
39
  removeSelection: 'Desfazer seleção de código',
@@ -1,16 +1,20 @@
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 } from '@stack-spot/portal-network'
4
+ import { aiClient, workspaceAiClient } from '@stack-spot/portal-network'
5
5
  import { GetAiStackResponse, 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
- import { useEffect, useMemo, useState } from 'react'
8
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
9
  import { ButtonFavorite } from '../components/ButtonFavorite'
10
+ import { NavigationComponent } from '../components/ComponentNavigator'
9
11
  import { DescribedRadioGroup } from '../components/form/DescribedRadioGroup'
10
12
  import { IconInput } from '../components/IconInput'
11
13
  import { RightPanelTabs } from '../components/RightPanelTabs'
14
+ import { WorkspaceTabNavigator } from '../components/WorkspaceTabNavigator'
12
15
  import { useCurrentChat, useWidget, useWidgetState } from '../context/hooks'
13
16
  import { useRightPanel } from '../right-panel/hooks'
17
+ import { ChatProperties } from '../state/ChatState'
14
18
  import { checkIsTrial } from '../utils/check-is-trial'
15
19
 
16
20
  /**
@@ -35,23 +39,35 @@ export const Stacks = () => {
35
39
  const StacksPanel = () => {
36
40
  const t = useTranslate(dictionary)
37
41
  const chat = useCurrentChat()
38
-
39
42
  const isTrial = checkIsTrial()
43
+ const stack = useRef(chat.get('stack'))
44
+
45
+ useEffect(() => {
46
+ stack.current = chat.get('stack')
47
+ }, [chat])
40
48
 
41
49
  const tabs = useMemo(() => isTrial ? [
42
- { title: t.favorites, content: <StacksTab key="favorites" visibility="favorite" /> },
43
- { title: t.personal, content: <StacksTab key="personal" visibility="personal" /> },
50
+ { title: t.favorites, content: <StacksTab key="favorites" visibility="favorite" stack={stack} /> },
51
+ { title: t.personal, content: <StacksTab key="personal" visibility="personal" stack={stack} /> },
44
52
  ]: [
45
- { title: t.favorites, content: <StacksTab key="favorites" visibility="favorite" /> },
46
- { title: t.personal, content: <StacksTab key="personal" visibility="personal" /> },
47
- { title: t.shared, content: <StacksTab key="shared" visibility="shared" /> },
48
- { title: t.account, content: <StacksTab key="account" visibility="account" /> },
53
+ { title: t.favorites, content: <StacksTab key="favorites" visibility="favorite" stack={stack} /> },
54
+ { title: t.personal, content: <StacksTab key="personal" visibility="personal" stack={stack} /> },
55
+ { title: t.shared, content: <StacksTab key="shared" visibility="shared" stack={stack} /> },
56
+ { title: t.spots, content: <StacksTabWorkspace key="workspace" visibility="workspace" stack={stack} /> },
57
+ { title: t.account, content: <StacksTab key="account" visibility="account" stack={stack} /> },
49
58
  ], [t, isTrial])
50
59
 
51
60
  return <RightPanelTabs key={chat.id} tabs={tabs} />
52
61
  }
53
62
 
54
- const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
63
+ export interface StacksTabProps {
64
+ visibility: VisibilityLevelEnum,
65
+ workspaceId?: string,
66
+ stack: React.MutableRefObject<ChatProperties['stack']>,
67
+ showSubmitButton?: boolean,
68
+ }
69
+
70
+ export const StacksTab = ({ visibility, workspaceId, stack, showSubmitButton = true }: StacksTabProps) => {
55
71
  const t = useTranslate(dictionary)
56
72
  const { close } = useRightPanel()
57
73
  const chat = useCurrentChat()
@@ -111,13 +127,19 @@ const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
111
127
  }
112
128
  })
113
129
 
114
- //@ts-ignore
115
- const stacks = aiClient.aiStacks.useQuery({ visibility, order: 'a-to-z' })
116
- const [value, setValue] = useState<GetAiStackResponse | undefined>(stacks.find(s => s.id === chat.get('stack')?.id))
130
+
131
+
132
+ const stacks = workspaceId
133
+ ? workspaceAiClient.getStackFromWorkspaceAi.useQuery({ workspaceId })
134
+ //@ts-ignore
135
+ : aiClient.aiStacks.useQuery({ visibility, order: 'a-to-z' })
136
+
137
+ const currentStackId = stack.current ? stack.current.id : chat.get('stack')?.id
138
+ const [value, setValue] = useState<GetAiStackResponse | undefined>(stacks.find(s => s.id === currentStackId))
117
139
  const filtered = useMemo(() => filter ?
118
140
  // Recreate the list so that the favorites list is taken into account
119
- stacks.filter(s => s === value || s.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...stacks],
120
- [stacks, listFavorites, filter, value],
141
+ stacks.filter(s => s === value || s.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...stacks],
142
+ [stacks, listFavorites, filter, value],
121
143
  )
122
144
 
123
145
  function submit() {
@@ -125,6 +147,11 @@ const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
125
147
  close()
126
148
  }
127
149
 
150
+ const onChange = useCallback((newValue: GetAiStackResponse) => {
151
+ setValue(newValue)
152
+ stack.current = { ...newValue, label: newValue.name }
153
+ }, [])
154
+
128
155
  return (
129
156
  <>
130
157
  <div className="content">
@@ -136,7 +163,7 @@ const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
136
163
  }
137
164
  keygen={s => s.id}
138
165
  value={value}
139
- onChange={setValue}
166
+ onChange={onChange}
140
167
  renderLabel={s => s.name}
141
168
  renderDescription={s => s.use_case}
142
169
  optionClassName={s => (s === value && filter && !s.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
@@ -149,11 +176,22 @@ const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
149
176
  <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder"/>}
150
177
  {!stacks.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
151
178
  </div>
152
- {!!filtered.length && <Button onClick={submit} disabled={!value}>{t.apply}</Button>}
179
+ {!!filtered.length && showSubmitButton && <Button onClick={submit} disabled={!value}>{t.apply}</Button>}
153
180
  </>
154
181
  )
155
182
  }
156
183
 
184
+ function StacksTabWorkspace({ stack, visibility, showSubmitButton }: StacksTabProps) {
185
+ const workspaceTabComponents = useMemo(() => ({ stack: StacksTab }), [stack])
186
+
187
+ const buildNavigateParams = (workspace: WorkspaceResponse): NavigationComponent<typeof workspaceTabComponents> => ({
188
+ component: 'stack',
189
+ props: { visibility, workspaceId: workspace.id, stack, showSubmitButton },
190
+ })
191
+
192
+ return <WorkspaceTabNavigator components={workspaceTabComponents} getNavigateParam={buildNavigateParams} />
193
+ }
194
+
157
195
  const dictionary = {
158
196
  en: {
159
197
  title: 'Stacks AI',
@@ -167,6 +205,7 @@ const dictionary = {
167
205
  noData: 'There are no stacks in this category yet.',
168
206
  noDataDescription: 'Use the tabs above to try other categories or use the AI portal to create new stacks.',
169
207
  favorites: 'Favorites',
208
+ spots: 'Spots',
170
209
  },
171
210
  pt: {
172
211
  title: 'Stacks AI',
@@ -180,5 +219,6 @@ const dictionary = {
180
219
  noData: 'Ainda não há stacks nesta categoria.',
181
220
  noDataDescription: 'Use as abas acima para tentar outras categorias ou use o Portal AI para criar novas stacks.',
182
221
  favorites: 'Favoritos',
222
+ spots: 'Spots',
183
223
  },
184
224
  } satisfies Dictionary