@stack-spot/ai-chat-widget 1.31.2-beta.1 → 1.32.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 (65) hide show
  1. package/CHANGELOG.md +25 -7
  2. package/dist/app-metadata.json +3 -3
  3. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  4. package/dist/chat-interceptors/quick-commands.js +16 -71
  5. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  6. package/dist/components/RightPanelContentList.d.ts +10 -0
  7. package/dist/components/RightPanelContentList.d.ts.map +1 -0
  8. package/dist/components/RightPanelContentList.js +7 -0
  9. package/dist/components/RightPanelContentList.js.map +1 -0
  10. package/dist/components/Selector/index.d.ts.map +1 -1
  11. package/dist/components/Selector/index.js +3 -2
  12. package/dist/components/Selector/index.js.map +1 -1
  13. package/dist/features.d.ts +15 -0
  14. package/dist/features.d.ts.map +1 -1
  15. package/dist/features.js +1 -0
  16. package/dist/features.js.map +1 -1
  17. package/dist/layout.css +5 -1
  18. package/dist/state/constants.js +1 -1
  19. package/dist/state/constants.js.map +1 -1
  20. package/dist/views/Agents/AgentDescription.js +1 -1
  21. package/dist/views/Agents/AgentDescription.js.map +1 -1
  22. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  23. package/dist/views/Agents/AgentsPanel.js +24 -15
  24. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  25. package/dist/views/Agents/AgentsTab.d.ts +1 -1
  26. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  27. package/dist/views/Agents/dictionary.d.ts +1 -1
  28. package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
  29. package/dist/views/ChatHistory/HistoryItem.js +3 -1
  30. package/dist/views/ChatHistory/HistoryItem.js.map +1 -1
  31. package/dist/views/KSDocument.d.ts.map +1 -1
  32. package/dist/views/KSDocument.js +2 -1
  33. package/dist/views/KSDocument.js.map +1 -1
  34. package/dist/views/KnowledgeSources.d.ts +1 -1
  35. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  36. package/dist/views/KnowledgeSources.js +24 -11
  37. package/dist/views/KnowledgeSources.js.map +1 -1
  38. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  39. package/dist/views/MessageInput/AgentSelector.js +6 -2
  40. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  41. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  42. package/dist/views/MessageInput/QuickCommandSelector.js +9 -4
  43. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  44. package/dist/views/Stacks.d.ts +1 -1
  45. package/dist/views/Stacks.d.ts.map +1 -1
  46. package/dist/views/Stacks.js +21 -11
  47. package/dist/views/Stacks.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/app-metadata.json +3 -3
  50. package/src/chat-interceptors/quick-commands.ts +18 -84
  51. package/src/components/RightPanelContentList.tsx +15 -0
  52. package/src/components/Selector/index.tsx +8 -6
  53. package/src/features.ts +17 -0
  54. package/src/index.ts +1 -0
  55. package/src/layout.css +5 -1
  56. package/src/state/constants.ts +1 -1
  57. package/src/views/Agents/AgentDescription.tsx +1 -1
  58. package/src/views/Agents/AgentsPanel.tsx +36 -17
  59. package/src/views/Agents/AgentsTab.tsx +1 -1
  60. package/src/views/ChatHistory/HistoryItem.tsx +2 -1
  61. package/src/views/KSDocument.tsx +2 -1
  62. package/src/views/KnowledgeSources.tsx +38 -14
  63. package/src/views/MessageInput/AgentSelector.tsx +9 -3
  64. package/src/views/MessageInput/QuickCommandSelector.tsx +18 -5
  65. package/src/views/Stacks.tsx +34 -14
@@ -1,5 +1,5 @@
1
1
  import { aiClient, CancelledError, FixedChatRequest, StackspotAPIError } from '@stack-spot/portal-network'
2
- import { QuickCommandFetchResponseResult, QuickCommandPromptResponse2, QuickCommandResponse, QuickCommandStepFetchResponse, QuickCommandStepLlmResponse } from '@stack-spot/portal-network/api/ai'
2
+ import { QuickCommandFetchResponseResult, QuickCommandPromptResponse2, QuickCommandResponse, QuickCommandStepFetchResponse } from '@stack-spot/portal-network/api/ai'
3
3
  import { Dictionary, interpolate, translate } from '@stack-spot/portal-translate'
4
4
  import type { editor } from 'monaco-editor'
5
5
  import { ulid } from 'ulid'
@@ -13,13 +13,12 @@ import { buildConversationContext } from '../utils/chat'
13
13
  import { getSizeOfString } from '../utils/string'
14
14
  import { CustomInputs } from './CustomInputs'
15
15
 
16
- type SlugExecution = Record<string, QuickCommandFetchResponseResult | QuickCommandPromptResponse2>
16
+ type SlugExecution = Record<string, string | QuickCommandFetchResponseResult | QuickCommandPromptResponse2>
17
17
 
18
18
  interface QCContext {
19
19
  qc: QuickCommandResponse,
20
20
  context: Required<FixedChatRequest>['context'],
21
21
  resultMap: SlugExecution,
22
- customInputs: Record<string, string>,
23
22
  chat: ChatState,
24
23
  code?: string,
25
24
  executionId: string,
@@ -65,61 +64,21 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
65
64
  const inputsValues = await customInputs.getValue()
66
65
  chat.set('isLoading', true)
67
66
  CustomInputs.deleteCustomInputsFromChat(chat)
68
- ctx.customInputs = { ...ctx.customInputs, ...inputsValues }
69
- }
70
-
71
- /**
72
- * Runs an Router step of a quick command.
73
- */
74
- async function runRouterStep(
75
- ctx: QCContext,
76
- stepIndex: number, iteration: number,
77
- progress: { update: (index: number) => void, remove: () => void },
78
- ) {
79
- const { qc: { slug, steps }, code, resultMap, customInputs } = ctx
80
- const step = steps![stepIndex]
81
- const inputData = Object.keys(customInputs).length > 0 && code ? { ...customInputs, [code]: code } : code ?? customInputs
82
- try {
83
- const { next_step_slug } = await aiClient.calculateNextStep.mutate({
84
- stepSlug: step.slug,
85
- slug: slug,
86
- quickCommandEvaluateStepRouterRequest: {
87
- executions_count: iteration,
88
- input_data: inputData,
89
- slugs_executions: resultMap,
90
- },
91
- })
92
-
93
- if (next_step_slug === step.slug) {
94
- return runStepsRecursively(stepIndex, progress, ctx, iteration+1)
95
- }
96
- const nextStepIndex = steps?.findIndex((step) => step.slug === next_step_slug)
97
-
98
- if (!nextStepIndex || nextStepIndex === -1) return
99
-
100
- return runStepsRecursively(nextStepIndex, progress, ctx, iteration)
101
- }
102
- catch (error: any) {
103
- // eslint-disable-next-line no-console
104
- console.error('Error executing QC step', error)
105
- }
67
+ ctx.resultMap = { ...ctx.resultMap, ...inputsValues }
106
68
  }
107
69
 
108
70
  /**
109
71
  * Runs a fetch step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
110
72
  */
111
73
  async function runFetchStep(ctx: QCContext, stepIndex: number) {
112
- const { qc: { slug, steps }, code, context, resultMap, customInputs, executionId, signal } = ctx
74
+ const { qc: { slug, steps }, code, context, resultMap, executionId, signal } = ctx
113
75
  const step = steps![stepIndex] as QuickCommandStepFetchResponse
114
76
  if (step.is_remote) {
115
77
  ctx.isRemote = true
116
78
 
117
79
  const { data } = await aiClient.fetchStepOfQuickCommandRemotely.mutate({
118
80
  slug, stepSlug: step.slug,
119
- quickCommandsExecutionRequest: {
120
- code_selection: code, context, qc_execution_id: executionId,
121
- slugs_executions: { ...resultMap, ...customInputs },
122
- },
81
+ quickCommandsExecutionRequest: { code_selection: code, context, slugs_executions: resultMap, qc_execution_id: executionId },
123
82
  })
124
83
 
125
84
  //data is the return of the request in the QC so we do not have full control over the response
@@ -136,9 +95,7 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
136
95
  const { headers, data, method, url } = await aiClient.fetchStepOfQuickCommand.mutate({
137
96
  slug,
138
97
  stepSlug: step.slug,
139
- quickCommandsExecutionRequest: { input_data: code, context, qc_execution_id: executionId,
140
- slugs_executions: { ...resultMap, ...customInputs },
141
- },
98
+ quickCommandsExecutionRequest: { input_data: code, context, slugs_executions: resultMap, qc_execution_id: executionId },
142
99
  }, signal)
143
100
  const body = ['get', 'head'].includes(method.toLowerCase()) ? undefined : data
144
101
  const response = await fetch(url, { headers: headers || undefined, body, method, signal })
@@ -154,17 +111,12 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
154
111
  * Runs an LLM step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
155
112
  */
156
113
  async function runLLMStep(
157
- { qc: { slug, steps }, code, customInputs, context, executionId, resultMap, signal }: QCContext,
114
+ { qc: { slug, steps }, code, context, executionId, resultMap, signal }: QCContext,
158
115
  stepIndex: number,
159
116
  ) {
160
117
  const step = steps![stepIndex]
161
118
  const stream = aiClient.streamLlmStepOfQuickCommand(
162
- slug, step.slug, {
163
- input_data: code,
164
- context,
165
- qc_execution_id: executionId,
166
- slugs_executions: { ...resultMap, ...customInputs },
167
- },
119
+ slug, step.slug, { input_data: code, context, qc_execution_id: executionId, slugs_executions: resultMap },
168
120
  )
169
121
 
170
122
  signal.addEventListener('abort', () => stream.cancel())
@@ -183,7 +135,7 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
183
135
  const t = translate(dictionary)
184
136
  message.setValue({
185
137
  ...message.getValue(),
186
- content: interpolate(t.progress, qc.steps?.[stepIndex]?.slug, qc.name || qc.slug),
138
+ content: interpolate(t.progress, qc.name || qc.slug, stepIndex + 1, qc.steps?.[stepIndex]?.slug, qc.steps?.length),
187
139
  })
188
140
  }
189
141
 
@@ -211,44 +163,27 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
211
163
  return controller
212
164
  }
213
165
 
214
- async function runStepsRecursively(currentIndex: number, progress: { update: (index: number) => void, remove: () => void },
215
- ctx: QCContext, iteration: number) {
216
- const { qc } = ctx
217
- if (!qc.steps || currentIndex >= qc.steps?.length) return
218
- progress.update(currentIndex)
219
-
220
- if (qc.steps[currentIndex].type === 'ROUTER') {
221
- await runRouterStep(ctx, currentIndex, iteration, progress)
222
- return
223
- }
224
- const currentStep = qc.steps?.[currentIndex] as QuickCommandStepFetchResponse | QuickCommandStepLlmResponse
225
- await (currentStep.type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
226
-
227
- let nextIndex = currentIndex + 1
228
- if (currentStep.next_step_slug) {
229
- nextIndex = currentStep.next_step_slug === 'end' ?
230
- qc.steps.length : qc.steps?.findIndex((step) => step.slug === currentStep.next_step_slug)
231
- }
232
- await runStepsRecursively(nextIndex, progress, ctx, iteration)
233
- }
234
-
235
166
  async function runSteps(ctx: QCContext) {
167
+ const { qc } = ctx
236
168
  const progress = showProgressMessage(ctx)
237
169
  try {
238
- await runStepsRecursively(0, progress, ctx, 0)
170
+ for (let i = 0; i < (qc.steps?.length ?? 0); i++) {
171
+ progress.update(i)
172
+ await (qc.steps?.[i].type === 'FETCH' ? runFetchStep(ctx, i) : runLLMStep(ctx, i))
173
+ }
239
174
  } finally {
240
175
  progress.remove()
241
176
  }
242
177
  }
243
178
 
244
- async function formatResult({ qc, code, executionId, context, resultMap, customInputs, signal }: QCContext) {
179
+ async function formatResult({ qc, code, executionId, context, resultMap, signal }: QCContext) {
245
180
  const formatted = await aiClient.formatResultOfQuickCommand.mutate({
246
181
  slug: qc.slug,
247
182
  quickCommandsExecutionRequest: {
248
183
  input_data: code,
249
184
  context,
250
185
  qc_execution_id: executionId,
251
- slugs_executions: { ...resultMap, ...customInputs },
186
+ slugs_executions: resultMap,
252
187
  },
253
188
  }, signal)
254
189
  return formatted.result
@@ -363,7 +298,6 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
363
298
  context: buildConversationContext(chat) ?? {},
364
299
  executionId: ulid(),
365
300
  resultMap: {},
366
- customInputs: {},
367
301
  signal,
368
302
  }
369
303
  chat.set('isLoading', true)
@@ -397,14 +331,14 @@ const dictionary = {
397
331
  en: {
398
332
  requiresSelection: 'This quick command requires some code to be selected in the editor. To open the editor click the icon "{/}" in the field below.',
399
333
  startQuestioning: 'To execute the Quick Command "$0", I\'ll need you to provide some information. Some may be mandatory, and others optional. Let\'s get started.',
400
- progress: 'Running step "$0" from Quick Command "$1".',
334
+ progress: 'Running quick command "$0". Step $1 ($2) of $3.',
401
335
  aborted: 'The quick command execution aborted by the user.',
402
336
  notFound: 'There\'s no quick command with the provided name. If you don\'t wish to run a command, prefix the first "/" with a "\\".',
403
337
  },
404
338
  pt: {
405
339
  requiresSelection: 'Este quick command precisa que algum código esteja selecionado no editor. Para abrir o editor clique no ícone "{/}" no campo abaixo.',
406
340
  startQuestioning: 'Para executar o Quick Command "$0", vou precisar que você providencie algumas explicações. Algumas são obrigatórias e outras opcionais. Vamos começar.',
407
- progress: 'Executando step "$0" do Quick Command "$1".',
341
+ progress: 'Executando quick command "$0". Passo $1 ($2) de $3.',
408
342
  aborted: 'A execução do quick command foi abortada pelo usuário.',
409
343
  notFound: 'Não existe quick command com o nome providenciado. Se você não quiser rodar um comando, prefixe o primeiro "/" com um "\\".',
410
344
  },
@@ -0,0 +1,15 @@
1
+ import { ReactElement } from 'react'
2
+ import { RightPanelForm } from './RightPanelForm'
3
+
4
+ interface Props {
5
+ children: ReactElement,
6
+ }
7
+
8
+ /**
9
+ * List content for the right panel.
10
+ */
11
+ export const RightPanelContentList = ({ children }: Props) => (
12
+ <RightPanelForm>
13
+ {children}
14
+ </RightPanelForm>
15
+ )
@@ -5,7 +5,7 @@ import { useKeyboardControls } from '@stack-spot/portal-components'
5
5
  import { AgentVisibilityLevel } from '@stack-spot/portal-network'
6
6
  import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
7
7
  import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
- import { useCurrentChatState } from '../../context/hooks'
8
+ import { useCurrentChatState, useWidgetState } from '../../context/hooks'
9
9
  import { getUrlToStackSpotAI } from '../../utils/url'
10
10
  import { ButtonFavorite } from '../ButtonFavorite'
11
11
  import { Fading } from '../Fading'
@@ -112,7 +112,6 @@ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, fa
112
112
  const t = useTranslate(dictionary)
113
113
  const items = selectorConfig.useData()
114
114
  const favorites = favorite?.useFavorites?.()
115
-
116
115
  const filtered = useMemo(() => {
117
116
  if (!filter && !visibility) return [...items]
118
117
  const lowerFilter = filter?.toLowerCase() ?? ''
@@ -152,6 +151,7 @@ const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentP
152
151
  const t = useTranslate(dictionary)
153
152
  const ref = useRef<HTMLDivElement>(null)
154
153
  const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
154
+ const isGroupResourcesByScope = useWidgetState('features')?.groupResourcesByScope
155
155
  const { resourceName, icon, onSelect, sections } = selectorConfig
156
156
  const onSelectItem = useCallback((slug: string) => {
157
157
  onSelect(slug)
@@ -184,10 +184,12 @@ const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentP
184
184
  <Text as="h3" className="uppercase"> {resourceName} </Text>
185
185
  </header>
186
186
  <div className="body">
187
- <ul className="tabs">
188
- {createSectionItem()}
189
- {sections.map(createSectionItem)}
190
- </ul>
187
+ {isGroupResourcesByScope && (
188
+ <ul className="tabs">
189
+ {createSectionItem()}
190
+ {sections.map(createSectionItem)}
191
+ </ul>
192
+ )}
191
193
  <FallbackBoundary message={interpolate(t.error, selectorConfig.resourceName)} mini>
192
194
  <List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} favorite={favorite} />
193
195
  </FallbackBoundary>
package/src/features.ts CHANGED
@@ -41,11 +41,27 @@ export interface ChatFeatures {
41
41
  streaming: boolean,
42
42
  }
43
43
 
44
+ export type Scope = 'favorite' | 'builtin' | 'personal' | 'shared' | 'workspace' | 'account'
45
+
44
46
  export interface GlobalFeatures {
45
47
  /**
46
48
  * Enables the chat history.
47
49
  */
48
50
  chatHistory: boolean,
51
+ /**
52
+ * When selecting a resource, they're separated by scope (account, personal, workspace, etc.).
53
+ *
54
+ * If this is set, only the scopes in this list will be enabled.
55
+ */
56
+ scopes?: Scope[],
57
+ /**
58
+ * When set, only items(knowledge sources, agents, stacks ai, quick commands) from that workspace will be shown to the user.
59
+ */
60
+ workspaceId?: string,
61
+ /**
62
+ * when set to false it does not show tab by scope, it shows a single list.
63
+ */
64
+ groupResourcesByScope?: boolean,
49
65
  }
50
66
 
51
67
  export type AIWidgetFeatures = ChatFeatures & GlobalFeatures
@@ -68,6 +84,7 @@ export function getFeaturesWithDefaults(features?: Partial<AIWidgetFeatures>): A
68
84
  upload: true,
69
85
  streaming: true,
70
86
  showSourcesInResponse: true,
87
+ groupResourcesByScope: true,
71
88
  ...features,
72
89
  }
73
90
  }
package/src/index.ts CHANGED
@@ -16,3 +16,4 @@ export { ObservableState } from './state/ObservableState'
16
16
  export type { Labeled, LabeledAgent, LabeledWithImage } from './state/types'
17
17
  export { WidgetState } from './state/WidgetState'
18
18
  export { defaultLanguage, languages } from './utils/programming-languages'
19
+
package/src/layout.css CHANGED
@@ -143,6 +143,10 @@
143
143
  display: flex;
144
144
  flex-direction: column;
145
145
 
146
+ code {
147
+ overflow: auto;
148
+ }
149
+
146
150
  &.visible {
147
151
  left: 50%;
148
152
  }
@@ -205,4 +209,4 @@
205
209
  z-index: 9999;
206
210
  pointer-events: auto;
207
211
  color: var(--light-contrastText)
208
- }
212
+ }
@@ -1,7 +1,7 @@
1
1
  import { FileSize } from './types'
2
2
 
3
3
  export const acceptedFileTypes = [
4
- 'json', 'yaml', 'txt', 'md', 'json', 'yaml', 'pdf', /*'xls', 'xlsx', 'csv',*/ 'cbl', 'cpp', 'cxx', 'cc', 'c', 'hpp', 'hxx', 'hh', 'h',
4
+ 'json', 'yaml', 'txt', 'md', 'json', 'yaml', 'pdf', /*'xls', 'xlsx',*/ 'csv', 'cbl', 'cpp', 'cxx', 'cc', 'c', 'hpp', 'hxx', 'hh', 'h',
5
5
  'cs', 'go', 'html', 'htm', 'kt', 'kts', 'md', 'php', 'proto', 'py', 'java', 'js', 'jsx', 'ts', 'tsx', 'rst', 'rb', 'rs', 'scala', 'swift',
6
6
  'sql', 'yaml', 'yml', 'tf', 'sh', 'ps1', 'psd1', 'psm1', 'bat', 'cmd', 'rego', 'f', 'for', 'r', 'pl', 'vb', 'dart', 'hs', 'lua',
7
7
  'asm', 'groovy', 'gvy', 'gy', 'mat', 'clj', 'lisp', 'm', 'cls', 'css', 'scss', 'json', 'jpg', 'jpeg', 'png',
@@ -28,7 +28,7 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
28
28
  }, [numberOfKnowledgeSources])
29
29
  const tools = useMemo(() => {
30
30
  const result: React.ReactElement[] = []
31
- const builtInTools = agent?.toolkits?.builtin_toolkits?.[0].tools
31
+ const builtInTools = agent?.toolkits?.builtin_toolkits?.[0]?.tools
32
32
  const customToolkits = agent?.toolkits?.custom_toolkits ?? []
33
33
  for (const tool of builtInTools ?? []) {
34
34
  const toolWithImage = toolById(tool.id, toolKits)
@@ -1,11 +1,15 @@
1
1
  import { agentToolsClient } from '@stack-spot/portal-network'
2
- import { useEffect, useMemo, useRef } from 'react'
2
+ import { ReactElement, useEffect, useRef } from 'react'
3
+ import { RightPanelContentList } from '../../components/RightPanelContentList'
3
4
  import { RightPanelTabs } from '../../components/RightPanelTabs'
4
- import { useCurrentChat } from '../../context/hooks'
5
+ import { useCurrentChat, useWidgetState } from '../../context/hooks'
6
+ import { Scope } from '../../features'
5
7
  import { checkIsTrial } from '../../utils/check-is-trial'
6
8
  import { AgentsTab, AgentsTabWorkspace } from './AgentsTab'
7
9
  import { useAgentsDictionary } from './dictionary'
8
10
 
11
+ type TabElement = { title: string, content: ReactElement }
12
+
9
13
  /**
10
14
  * Renders the Agent selection form in the Right Panel if this is the panel that is currently opened.
11
15
  */
@@ -14,6 +18,10 @@ export const AgentsPanel = () => {
14
18
  const chat = useCurrentChat()
15
19
  const isTrial = checkIsTrial()
16
20
  const agent = useRef(chat.get('agent'))
21
+ const features = useWidgetState('features')
22
+ const isGroupResourcesByScope = features?.groupResourcesByScope
23
+ const scopes = features?.scopes ?? []
24
+ const spotId = features?.workspaceId
17
25
 
18
26
  useEffect(() => {
19
27
  if (agentToolsClient.agentDefaultSlug !== chat.get('agent')?.slug) {
@@ -21,19 +29,30 @@ export const AgentsPanel = () => {
21
29
  }
22
30
  }, [chat])
23
31
 
24
- const tabs= useMemo(() => isTrial ? [
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} /> },
28
- ]: [
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.spots, 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])
37
-
38
- return <RightPanelTabs key={chat.id} tabs={tabs} />
32
+ const allTabsMap: Record<Scope, TabElement> = {
33
+ favorite: { title: t.favorites, content: <AgentsTab key="favorite" visibility="favorite" agent={agent} /> },
34
+ builtin: { title: t.builtin, content: <AgentsTab key="builtin" visibility="built_in" agent={agent} /> },
35
+ personal: { title: t.personal, content: <AgentsTab key="personal" visibility="personal" agent={agent} /> },
36
+ shared: { title: t.shared, content: <AgentsTab key="shared" visibility="shared" agent={agent} /> },
37
+ workspace: { title: t.spots, content: <AgentsTabWorkspace key="workspace" visibility="workspace" agent={agent} /> },
38
+ account: { title: t.account, content: <AgentsTab key="account" visibility="account" agent={agent} /> },
39
+ }
40
+
41
+ const defaultScopes: Scope[] = ['favorite', 'builtin', 'personal', 'shared', 'workspace', 'account']
42
+
43
+ const rawScopes: Scope[] = scopes?.length ? scopes : defaultScopes
44
+
45
+ const scopesToRender: Scope[] = isTrial
46
+ ? ['favorite', 'builtin', 'personal']
47
+ : rawScopes
48
+
49
+ const tabs = scopesToRender
50
+ .map(scope => allTabsMap[scope])
51
+ .filter(Boolean) as TabElement[]
52
+
53
+ return (isGroupResourcesByScope ? (
54
+ <RightPanelTabs key={chat.id} tabs={tabs} />
55
+ ) : (
56
+ <RightPanelContentList><AgentsTab workspaceId={spotId} agent={agent} /></RightPanelContentList>
57
+ ))
39
58
  }
@@ -19,7 +19,7 @@ import { AgentLabel } from './styled'
19
19
  import { useAgentFavorites } from './useAgentFavorites'
20
20
 
21
21
  export interface AgentTabProps {
22
- visibility: AgentVisibilityLevel,
22
+ visibility?: AgentVisibilityLevel,
23
23
  workspaceId?: string,
24
24
  agent: React.MutableRefObject<ChatProperties['agent']>,
25
25
  showSubmitButton?: boolean,
@@ -102,7 +102,8 @@ export const HistoryItem = ({ item }: { item: ConversationResponse }) => {
102
102
  const builtIn = !!last(chat.history ?? [])?.custom_agent?.built_in
103
103
  widget.chatTabs.add(new ChatState({
104
104
  id: chat.id,
105
- initial: { label: chat.title, stack, workspace, agent: agent ? { ...agent, builtIn } : undefined },
105
+ initial: { features: widget.chatFeatures, label: chat.title, stack, workspace, agent: agent ? {
106
+ ...agent, builtIn } : undefined },
106
107
  interceptors: widget.interceptors,
107
108
  entries: chat.history?.map(item => new ChatEntry({
108
109
  agentType: item.agent === 'USER' ? 'user' : 'bot',
@@ -4,6 +4,7 @@ import { aiClient } from '@stack-spot/portal-network'
4
4
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
5
5
  import { useEffect } from 'react'
6
6
  import { Code } from '../components/Code'
7
+ import { FallbackBoundary } from '../components/FallbackBoundary'
7
8
  import { useWidget, useWidgetState } from '../context/hooks'
8
9
  import { useRightPanel } from '../right-panel/hooks'
9
10
  import { extractCodeFromKSDocument } from '../utils/knowledge-source'
@@ -20,7 +21,7 @@ export const KSDocument = () => {
20
21
 
21
22
  useEffect(() => {
22
23
  if (panel === 'ks-details' && ks) open(
23
- <KSDocumentPanel documentId={ks.documentId} slug={ks.slug} />,
24
+ <FallbackBoundary><KSDocumentPanel documentId={ks.documentId} slug={ks.slug} /></FallbackBoundary>,
24
25
  {
25
26
  title: (
26
27
  <Flex flexDirection="row" alignItems="center" flex="1">
@@ -6,26 +6,30 @@ import { KnowledgeSourceItemResponse, VisibilityLevelEnum } from '@stack-spot/po
6
6
  import { WorkspaceResponse } from '@stack-spot/portal-network/api/workspace-ai'
7
7
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
8
8
  import { difference, uniqBy } from 'lodash'
9
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
9
+ import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'
10
10
  import { ButtonFavorite } from '../components/ButtonFavorite'
11
11
  import { NavigationComponent } from '../components/ComponentNavigator'
12
12
  import { DescribedCheckboxGroup } from '../components/form/DescribedCheckboxGroup'
13
13
  import { IconInput } from '../components/IconInput'
14
+ import { RightPanelContentList } from '../components/RightPanelContentList'
14
15
  import { RightPanelTabs } from '../components/RightPanelTabs'
15
16
  import { WorkspaceTabNavigator } from '../components/WorkspaceTabNavigator'
16
17
  import { useCurrentChat, useWidget, useWidgetState } from '../context/hooks'
18
+ import { Scope } from '../features'
17
19
  import { useRightPanel } from '../right-panel/hooks'
18
20
  import { ChatProperties } from '../state/ChatState'
19
21
  import { checkIsTrial } from '../utils/check-is-trial'
20
22
 
21
23
  export interface TabProps {
22
- visibility: VisibilityLevelEnum,
24
+ visibility?: VisibilityLevelEnum,
23
25
  allKS: React.MutableRefObject<ChatProperties['knowledgeSources']>,
24
26
  workspaceId?: string,
25
27
  showSubmitButton?: boolean,
26
28
  onSubmit?: () => void,
27
29
  }
28
30
 
31
+ type TabElement = { title: string, content: ReactElement }
32
+
29
33
  export const KnowledgeSources = () => {
30
34
  const t = useTranslate(dictionary)
31
35
  const panel = useWidgetState('panel')
@@ -51,6 +55,10 @@ const KnowledgeSourcesPanel = () => {
51
55
  const allKS = useRef(chat.get('knowledgeSources') ?? [])
52
56
  const { close } = useRightPanel()
53
57
  const isTrial = checkIsTrial()
58
+ const features = useWidgetState('features')
59
+ const isGroupResourcesByScope = features?.groupResourcesByScope
60
+ const scopes = features?.scopes ?? []
61
+ const spotId = features?.workspaceId
54
62
 
55
63
  const onSubmit = useCallback(() => {
56
64
  chat.set('knowledgeSources', allKS.current)
@@ -61,18 +69,34 @@ const KnowledgeSourcesPanel = () => {
61
69
  allKS.current = chat.get('knowledgeSources') ?? []
62
70
  }, [chat])
63
71
 
64
- const tabs = isTrial ? [
65
- { title: t.favorites, content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
66
- { title: t.personal, content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
67
- ]: [
68
- { title: t.favorites, content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
69
- { title: t.personal, content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
70
- { title: t.shared, content: <KnowledgeSourcesTab key="shared" visibility="shared" 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} /> },
73
- ]
74
-
75
- return <RightPanelTabs key={chat.id} tabs={tabs} />
72
+ const allTabsMap: Partial<Record<Scope, TabElement>> = {
73
+ favorite: { title: t.favorites,
74
+ content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
75
+ personal: { title: t.personal,
76
+ content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
77
+ shared: { title: t.shared, content: <KnowledgeSourcesTab key="shared" visibility="shared" allKS={allKS} onSubmit={onSubmit} /> },
78
+ workspace: { title: t.spots,
79
+ content: <KnowledgeSourcesTabWorkspace key="workspace" visibility="workspace" allKS={allKS} onSubmit={onSubmit} /> },
80
+ account: { title: t.account, content: <KnowledgeSourcesTab key="account" visibility="account" allKS={allKS} onSubmit={onSubmit} /> },
81
+ }
82
+
83
+ const defaultScopes: Scope[] = ['favorite', 'personal', 'shared', 'workspace', 'account']
84
+
85
+ const rawScopes: Scope[] = scopes?.length ? scopes : defaultScopes
86
+
87
+ const scopesToRender: Scope[] = isTrial
88
+ ? ['favorite', 'personal']
89
+ : rawScopes
90
+
91
+ const tabs = scopesToRender
92
+ .map(scope => allTabsMap[scope])
93
+ .filter(Boolean) as TabElement[]
94
+
95
+ return (isGroupResourcesByScope ? (
96
+ <RightPanelTabs key={chat.id} tabs={tabs} />
97
+ ) : (
98
+ <RightPanelContentList><KnowledgeSourcesTab allKS={allKS} onSubmit={onSubmit} workspaceId={spotId} /></RightPanelContentList>
99
+ ))
76
100
  }
77
101
 
78
102
  export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId, showSubmitButton = true }: TabProps) => {
@@ -1,9 +1,9 @@
1
1
  import { Flex, IconBox, Image } from '@citric/core'
2
2
  import { Agent } from '@citric/icons'
3
- import { AgentResponseWithBuiltIn, agentToolsClient } from '@stack-spot/portal-network'
3
+ import { AgentResponseWithBuiltIn, agentToolsClient, workspaceAiClient } from '@stack-spot/portal-network'
4
4
  import { useCallback } from 'react'
5
5
  import { Selector } from '../../components/Selector'
6
- import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
6
+ import { useCurrentChat, useCurrentChatState, useWidgetState } from '../../context/hooks'
7
7
  import { agentRegex } from '../../regex'
8
8
  import { useAgentFavorites } from '../Agents/useAgentFavorites'
9
9
 
@@ -27,7 +27,8 @@ export const AgentSelector = ({ inputRef, isTrial }: {
27
27
  }) => {
28
28
  const chat = useCurrentChat()
29
29
  const isAgentEnabled = useCurrentChatState('features').agent
30
-
30
+ const spotId = useWidgetState('features')?.workspaceId
31
+
31
32
  const { useFavorites, onAddFavorite, onRemoveFavorite } = useAgentFavorites()
32
33
 
33
34
  const onSelectItem = useCallback(async (agent: AgentResponseWithBuiltIn) => {
@@ -52,9 +53,14 @@ export const AgentSelector = ({ inputRef, isTrial }: {
52
53
 
53
54
 
54
55
  const getAgents = () => {
56
+ if (spotId) {
57
+ return workspaceAiClient.getAgentFromWorkspaceAi.useQuery({ workspaceId: spotId }) as AgentResponseWithBuiltIn[]
58
+ }
59
+
55
60
  if (isTrial) {
56
61
  return agentToolsClient.allAgents.useQuery({ visibilities: ['personal', 'built_in'] })
57
62
  }
63
+
58
64
  return agentToolsClient.allAgents.useQuery({ visibilities: ['account', 'shared', 'personal', 'built_in', 'workspace'] })
59
65
  }
60
66
 
@@ -3,7 +3,7 @@ import { aiClient, workspaceAiClient } from '@stack-spot/portal-network'
3
3
  import { QuickCommandResponse } from '@stack-spot/portal-network/api/ai'
4
4
  import { useCallback } from 'react'
5
5
  import { Selector } from '../../components/Selector'
6
- import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
6
+ import { useCurrentChat, useCurrentChatState, useWidgetState } from '../../context/hooks'
7
7
  import { quickCommandRegex } from '../../regex'
8
8
 
9
9
  type QuickCommandResponseWithSpaceName = QuickCommandResponse & { spaceName?: string }
@@ -12,6 +12,7 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
12
12
  { isTrial: boolean, inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement> }) => {
13
13
  const chat = useCurrentChat()
14
14
  const isQuickCommandEnabled = useCurrentChatState('features').quickCommands
15
+ const spotId = useWidgetState('features')?.workspaceId
15
16
 
16
17
  const useFavorites = () => aiClient.allQuickCommands.useQuery({ visibility: 'favorite' })
17
18
  const [addFavorite, pendingAddFav] = aiClient.addFavoriteQuickCommand.useMutation()
@@ -77,11 +78,23 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
77
78
  }, [chat, inputRef])
78
79
 
79
80
  const getQuickCommands = () => {
81
+ if (spotId) {
82
+ return workspaceAiClient.getQCFromWorkspaceAi.useQuery({ workspaceId: spotId })
83
+ }
84
+
80
85
  const quickCommands = aiClient.allQuickCommands.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[]
86
+ const quickCommandsFiltered = quickCommands.filter(
87
+ (qc) => qc.visibility_level.toLowerCase() !== 'workspace',
88
+ )
89
+
90
+ const workspaceQuickCommands = workspaceAiClient.workspacesContentsByType.useQuery({
91
+ contentType: 'quick_command',
92
+ })
93
+
94
+ const workspaceQuickCommandsWithWorkspaceName: QuickCommandResponseWithSpaceName[] =
95
+ workspaceQuickCommands.flatMap(({ qcs, space_name }) =>
96
+ qcs?.map((qc) => ({ ...qc, spaceName: space_name })),
97
+ ) as QuickCommandResponseWithSpaceName[]
85
98
 
86
99
  return [...quickCommandsFiltered, ...workspaceQuickCommandsWithWorkspaceName]
87
100
  }