@stack-spot/ai-chat-widget 1.0.0-dev.1769003016623 → 1.0.0-dev.1769537635610

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 (89) hide show
  1. package/CHANGELOG.md +7 -0
  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 +9 -232
  5. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  6. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  7. package/dist/chat-interceptors/send-message.js +5 -1
  8. package/dist/chat-interceptors/send-message.js.map +1 -1
  9. package/dist/components/Selector/SelectVersion.d.ts +12 -0
  10. package/dist/components/Selector/SelectVersion.d.ts.map +1 -0
  11. package/dist/components/Selector/SelectVersion.js +33 -0
  12. package/dist/components/Selector/SelectVersion.js.map +1 -0
  13. package/dist/components/Selector/index.d.ts +2 -0
  14. package/dist/components/Selector/index.d.ts.map +1 -1
  15. package/dist/components/Selector/index.js +5 -2
  16. package/dist/components/Selector/index.js.map +1 -1
  17. package/dist/components/Selector/styled.d.ts +2 -0
  18. package/dist/components/Selector/styled.d.ts.map +1 -1
  19. package/dist/components/Selector/styled.js +45 -0
  20. package/dist/components/Selector/styled.js.map +1 -1
  21. package/dist/hooks/enabled-feature-flags.d.ts +5 -0
  22. package/dist/hooks/enabled-feature-flags.d.ts.map +1 -0
  23. package/dist/hooks/enabled-feature-flags.js +28 -0
  24. package/dist/hooks/enabled-feature-flags.js.map +1 -0
  25. package/dist/state/types.d.ts +1 -0
  26. package/dist/state/types.d.ts.map +1 -1
  27. package/dist/utils/tools.d.ts +5 -5
  28. package/dist/utils/tools.d.ts.map +1 -1
  29. package/dist/utils/tools.js +2 -2
  30. package/dist/utils/tools.js.map +1 -1
  31. package/dist/views/Agents/AgentDescription.d.ts +6 -2
  32. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  33. package/dist/views/Agents/AgentDescription.js +25 -10
  34. package/dist/views/Agents/AgentDescription.js.map +1 -1
  35. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  36. package/dist/views/Agents/AgentsTab.js +21 -4
  37. package/dist/views/Agents/AgentsTab.js.map +1 -1
  38. package/dist/views/Agents/dictionary.d.ts +1 -1
  39. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  40. package/dist/views/Agents/dictionary.js +2 -0
  41. package/dist/views/Agents/dictionary.js.map +1 -1
  42. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  43. package/dist/views/Chat/ChatMessage.js +6 -5
  44. package/dist/views/Chat/ChatMessage.js.map +1 -1
  45. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  46. package/dist/views/ChatHistory/utils.js +14 -7
  47. package/dist/views/ChatHistory/utils.js.map +1 -1
  48. package/dist/views/Editor.d.ts.map +1 -1
  49. package/dist/views/Editor.js +16 -2
  50. package/dist/views/Editor.js.map +1 -1
  51. package/dist/views/Home/CustomAgent.js +3 -3
  52. package/dist/views/Home/CustomAgent.js.map +1 -1
  53. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  54. package/dist/views/MessageInput/AgentSelector.js +11 -1
  55. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  56. package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -1
  57. package/dist/views/MessageInput/ModelSwitcher/index.js +3 -1
  58. package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -1
  59. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts +2 -2
  60. package/dist/views/MessageInput/ModelSwitcher/utils.d.ts.map +1 -1
  61. package/dist/views/MessageInput/ModelSwitcher/utils.js +6 -6
  62. package/dist/views/MessageInput/ModelSwitcher/utils.js.map +1 -1
  63. package/dist/views/Resources.js +8 -5
  64. package/dist/views/Resources.js.map +1 -1
  65. package/dist/views/Steps/dictionary.d.ts +1 -1
  66. package/dist/views/Tools.js +3 -2
  67. package/dist/views/Tools.js.map +1 -1
  68. package/package.json +2 -2
  69. package/src/app-metadata.json +3 -3
  70. package/src/chat-interceptors/quick-commands.ts +14 -275
  71. package/src/chat-interceptors/send-message.ts +6 -2
  72. package/src/components/Selector/SelectVersion.tsx +55 -0
  73. package/src/components/Selector/index.tsx +11 -2
  74. package/src/components/Selector/styled.ts +47 -0
  75. package/src/hooks/enabled-feature-flags.ts +31 -0
  76. package/src/state/types.ts +1 -0
  77. package/src/utils/tools.ts +4 -4
  78. package/src/views/Agents/AgentDescription.tsx +48 -14
  79. package/src/views/Agents/AgentsTab.tsx +31 -13
  80. package/src/views/Agents/dictionary.ts +2 -1
  81. package/src/views/Chat/ChatMessage.tsx +8 -6
  82. package/src/views/ChatHistory/utils.ts +18 -10
  83. package/src/views/Editor.tsx +20 -3
  84. package/src/views/Home/CustomAgent.tsx +4 -4
  85. package/src/views/MessageInput/AgentSelector.tsx +15 -5
  86. package/src/views/MessageInput/ModelSwitcher/index.tsx +3 -2
  87. package/src/views/MessageInput/ModelSwitcher/utils.tsx +9 -8
  88. package/src/views/Resources.tsx +10 -6
  89. package/src/views/Tools.tsx +5 -4
@@ -1,7 +1,6 @@
1
- import { aiClient, CancelledError, FixedChatRequest, StackspotAPIError } from '@stack-spot/portal-network'
2
- import { QuickCommandStepResult, QuickCommandPromptResponse2, QuickCommandResponse, QuickCommandScriptExecutionResponse, QuickCommandStepFetchResponse, QuickCommandStepLlmResponse } from '@stack-spot/portal-network/api/ai'
1
+ import { aiClient, CancelledError, FixedChatRequest, QCContext as QCContextLib, StackspotAPIError } from '@stack-spot/portal-network'
2
+ import { QuickCommandResponse, QuickCommandStepResult } from '@stack-spot/portal-network/api/ai'
3
3
  import { Dictionary, interpolate, translate } from '@stack-spot/portal-translate'
4
- import { isNil } from 'lodash'
5
4
  import type { editor } from 'monaco-editor'
6
5
  import { ulid } from 'ulid'
7
6
  import { AbortedError } from '../AbortedError'
@@ -11,7 +10,6 @@ import { ChatState } from '../state/ChatState'
11
10
  import { LabeledWithImage } from '../state/types'
12
11
  import { WidgetState } from '../state/WidgetState'
13
12
  import { buildConversationContext } from '../utils/chat'
14
- import { getSizeOfString } from '../utils/string'
15
13
  import { CustomInputs } from './CustomInputs'
16
14
 
17
15
  type SlugExecution = Record<string, QuickCommandStepResult>
@@ -73,132 +71,6 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
73
71
  )
74
72
  }
75
73
 
76
- /**
77
- * Runs an Router step of a quick command.
78
- */
79
- async function runRouterStep(
80
- ctx: QCContext,
81
- stepIndex: number, iteration: Record<string, number>,
82
- progress: { update: (index: number) => void, remove: () => void },
83
- ) {
84
- const { qc: { slug, steps }, code, resultMap, customInputs } = ctx
85
- const step = steps![stepIndex]
86
- const inputData = Object.keys(customInputs).length > 0 && code ? { ...customInputs, [code]: code } : code ?? customInputs
87
- try {
88
- if (step.slug in iteration) {
89
- iteration[step.slug] = iteration[step.slug] + 1
90
- } else {
91
- iteration[step.slug] = 1
92
- }
93
-
94
- const { next_step_slug } = await aiClient.calculateNextStep.mutate({
95
- stepSlug: step.slug,
96
- slug: slug,
97
- quickCommandEvaluateStepRouterRequest: {
98
- executions_count: iteration[step.slug],
99
- input_data: inputData,
100
- slugs_executions: resultMap,
101
- },
102
- })
103
-
104
- if (next_step_slug === step.slug) {
105
- return runStepsRecursively(stepIndex, progress, ctx, iteration)
106
- }
107
- const nextStepIndex = steps?.findIndex((step) => step.slug === next_step_slug)
108
-
109
- if (isNil(nextStepIndex) || nextStepIndex === -1) return
110
-
111
- return runStepsRecursively(nextStepIndex, progress, ctx, iteration)
112
- }
113
- catch (error: any) {
114
- // eslint-disable-next-line no-console
115
- console.error('Error executing QC step', error)
116
- }
117
- }
118
-
119
- /**
120
- * Runs a fetch step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
121
- */
122
- async function runFetchStep(ctx: QCContext, stepIndex: number) {
123
- const { qc: { slug, steps }, code, context, resultMap, customInputs, executionId, signal } = ctx
124
- const step = steps![stepIndex] as QuickCommandStepFetchResponse
125
- if (step.is_remote) {
126
- ctx.isRemote = true
127
-
128
- const { data } = await aiClient.fetchStepOfQuickCommandRemotely.mutate({
129
- slug, stepSlug: step.slug,
130
- quickCommandsExecutionRequest: {
131
- code_selection: code, context, qc_execution_id: executionId,
132
- slugs_executions: { ...resultMap, ...customInputs },
133
- },
134
- })
135
-
136
- //data is the return of the request in the QC so we do not have full control over the response
137
- //We handle the usual format with body, status_code and headers, but we might also handle other formats
138
- const responseData = data as any
139
- resultMap[step.slug] = {
140
- status: responseData.status_code || 200,
141
- data: JSON.stringify(responseData.body) ?? JSON.stringify(responseData),
142
- headers: responseData.headers ?? {},
143
- }
144
- return
145
- }
146
-
147
- const { headers, data, method, url } = await aiClient.fetchStepOfQuickCommand.mutate({
148
- slug,
149
- stepSlug: step.slug,
150
- quickCommandsExecutionRequest: {
151
- input_data: code, context, qc_execution_id: executionId,
152
- slugs_executions: { ...resultMap, ...customInputs },
153
- },
154
- }, signal)
155
- const body = ['get', 'head'].includes(method.toLowerCase()) ? undefined : data
156
- const response = await fetch(url, { headers: headers || undefined, body, method, signal })
157
- if (!response.ok) throw new Error(`Failed to execute step "${step.slug}" of quick command "${slug}". Status ${response.status}.`)
158
- resultMap[step.slug] = {
159
- status: response.status,
160
- data: await response.text(),
161
- headers: Object.fromEntries(response.headers.entries()),
162
- }
163
- }
164
-
165
- /**
166
- * Runs an LLM step of a quick command and puts the result in the `resultMap` of the context passed as parameter.
167
- */
168
- async function runLLMStep(
169
- { qc: { slug, steps }, code, customInputs, context, executionId, resultMap, signal }: QCContext,
170
- stepIndex: number,
171
- ) {
172
- const step = steps![stepIndex] as QuickCommandStepLlmResponse
173
- let stepContext = context
174
- if (!step.use_uploaded_files) {
175
- const { upload_ids: _upload_ids, ...contextDataProps } = context
176
- stepContext = { ...contextDataProps }
177
- }
178
-
179
- const stream = aiClient.streamLlmStepOfQuickCommand(
180
- slug,
181
- step.slug,
182
- {
183
- input_data: code,
184
- context: stepContext,
185
- qc_execution_id: executionId,
186
- slugs_executions: { ...resultMap, ...customInputs },
187
- },
188
- )
189
-
190
- signal.addEventListener('abort', () => stream.cancel())
191
-
192
- let finalValue: QuickCommandPromptResponse2
193
- try {
194
- finalValue = await stream.getValue()
195
- resultMap[step.slug] = finalValue
196
- } catch (error: any) {
197
- // eslint-disable-next-line no-console
198
- console.error('Error executing QC step', error)
199
- }
200
- }
201
-
202
74
  function updateProgressMessageForStep(message: ChatEntry, qc: QuickCommandResponse, stepIndex: number) {
203
75
  const t = translate(dictionary)
204
76
  message.setValue({
@@ -230,115 +102,17 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
230
102
  }, showImmediately ? 0 : progressMessageDelayMS)
231
103
  return controller
232
104
  }
233
- async function getScriptStepStatus(
234
- scriptExecutionId: string,
235
- interval = 5000,
236
- maxAttempts = 500,
237
- currentAttempt = 0,
238
- ): Promise<QuickCommandScriptExecutionResponse> {
239
- if (currentAttempt >= maxAttempts) {
240
- throw new Error('Max attempts reached in verify script status')
241
- }
242
- await aiClient.getStatusScriptStep.invalidate({ scriptExecutionId })
243
- const response = await aiClient.getStatusScriptStep.query({ scriptExecutionId })
244
-
245
- if (response.status === 'success') {
246
- return response
247
- }
248
105
 
249
- if (response.status === 'failure') {
250
- throw response
251
- }
252
-
253
- await new Promise(resolve => {setTimeout(resolve, interval)})
254
-
255
- return getScriptStepStatus(scriptExecutionId, interval, maxAttempts, currentAttempt + 1)
256
- }
257
-
258
- async function runScriptStep(
259
- { qc: { slug, steps }, code, context, resultMap, customInputs, signal }: QCContext,
260
- stepIndex: number,
261
- ) {
262
- const step = steps![stepIndex] as QuickCommandStepLlmResponse
263
- let stepContext = context
264
- if (!step.use_uploaded_files) {
265
- const { upload_ids: _upload_ids, ...contextDataProps } = context
266
- stepContext = { ...contextDataProps }
267
- }
268
-
269
- try {
270
- const { script_execution_id } = await aiClient.startScriptStep.mutate({
271
- stepSlug: step.slug,
272
- slug: slug,
273
- quickCommandStartScriptRequest: {
274
- input_data: code,
275
- custom_inputs: customInputs,
276
- context: stepContext,
277
- slugs_executions: resultMap,
278
- },
279
- }, signal)
280
- const scriptResult = await getScriptStepStatus(script_execution_id)
281
- resultMap[step.slug] = scriptResult
282
- }
283
- catch (error: any) {
284
- throw new Error(`Failed to execute step "${step.slug}" of quick command "${slug}". Error ${error}.`)
285
- }
286
- }
287
-
288
- async function runStepsRecursively(currentIndex: number, progress: { update: (index: number) => void, remove: () => void },
289
- ctx: QCContext, iteration: Record<string, number>) {
290
- const { qc } = ctx
291
-
292
- if (!qc.steps || currentIndex >= qc.steps?.length) return
293
- progress.update(currentIndex)
294
-
295
- if (qc.steps[currentIndex].type === 'ROUTER') {
296
- await runRouterStep(ctx, currentIndex, iteration, progress)
297
- return
298
- }
299
-
300
- if (qc.steps[currentIndex].type === 'SCRIPT') {
301
- await runScriptStep(ctx, currentIndex)
302
- } else {
303
- await (qc.steps[currentIndex].type === 'FETCH' ? runFetchStep(ctx, currentIndex) : runLLMStep(ctx, currentIndex))
304
- }
305
-
306
- const currentStep = qc.steps?.[currentIndex] as QuickCommandStepFetchResponse | QuickCommandStepLlmResponse
307
- let nextIndex = currentIndex + 1
308
-
309
- let nextStepSlug = currentStep.next_step_slug
310
- const stepResult = ctx.resultMap[currentStep.slug]
311
- if (stepResult && typeof stepResult !== 'string' && 'answer_status' in stepResult && !!stepResult.answer_status?.next_step_slug) {
312
- nextStepSlug = stepResult.answer_status.next_step_slug
313
- }
314
-
315
- if (nextStepSlug) {
316
- nextIndex = nextStepSlug === 'end' ?
317
- qc.steps.length : qc.steps?.findIndex((step) => step.slug === nextStepSlug)
318
- }
319
- await runStepsRecursively(nextIndex, progress, ctx, iteration)
320
- }
321
-
322
- async function runSteps(ctx: QCContext) {
106
+ async function runQC(ctx: QCContext) {
323
107
  const progress = showProgressMessage(ctx)
324
- try {
325
- await runStepsRecursively(0, progress, ctx, {})
326
- } finally {
327
- progress.remove()
328
- }
329
- }
330
108
 
331
- async function formatResult({ qc, code, executionId, context, resultMap, customInputs, signal }: QCContext) {
332
- const formatted = await aiClient.formatResultOfQuickCommand.mutate({
333
- slug: qc.slug,
334
- quickCommandsExecutionRequest: {
335
- input_data: code,
336
- context,
337
- qc_execution_id: executionId,
338
- slugs_executions: { ...resultMap, ...customInputs },
339
- },
340
- }, signal)
341
- return formatted.result
109
+ // await runStepsRecursively(0, progress, ctx, {}
110
+ const { qc, ...props } = ctx
111
+ const context: QCContextLib = { ...props, slug: qc.slug }
112
+ const finalResult = await aiClient.runQuickCommand(context, progress)
113
+
114
+ progress.remove()
115
+ return finalResult
342
116
  }
343
117
 
344
118
  // opens a new chat tab if the quick command doesn't preserve the conversation and the current conversation isn't clean.
@@ -368,7 +142,7 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
368
142
  } catch (error) {
369
143
  throw error instanceof StackspotAPIError && error.status === 404 ? new Error(t.notFound) : error
370
144
  }
371
-
145
+
372
146
  if (ctx.qc.use_selected_code && (!code && ctx.context.upload_ids?.length === 0)) {
373
147
  widget.set('panel', 'editor')
374
148
  ctx.chat.pushMessage(new ChatEntry({
@@ -377,44 +151,12 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
377
151
  agent: ctx.chat.get('agent'),
378
152
  content: t.requiresSelection,
379
153
  }))
380
- return
154
+ return
381
155
  }
382
156
  manageConversationContext(ctx)
383
157
  await computeCustomInputs(ctx)
384
- await runSteps(ctx)
385
- return formatResult(ctx)
386
- }
387
-
388
- /**
389
- * This registers a quick command event in the backend (analytics).
390
- */
391
- async function registerAnalyticsEvent({ qc, isRemote, executionId, code = '', context }: QCContext, status: string, start: number) {
392
- const now = new Date().getTime()
393
- try {
394
- await aiClient.createEvent.mutate({
395
- body: [{
396
- type: 'custom_quick_command_execution',
397
- quick_command_event: {
398
- type: qc.type || '',
399
- duration_execution: now - start,
400
- status_execution: status,
401
- slug: qc.slug,
402
- qc_execution_id: executionId,
403
- id: qc.id,
404
- //@ts-ignore
405
- is_remote: isRemote,
406
- },
407
- code,
408
- context,
409
- knowledge_sources: [],
410
- size: getSizeOfString(code),
411
- generated_at: now,
412
- }],
413
- })
414
- } catch (error) {
415
- // eslint-disable-next-line no-console
416
- console.warn('Failed to register event: quick command.')
417
- }
158
+ const result = await runQC(ctx)
159
+ return result
418
160
  }
419
161
 
420
162
  /**
@@ -462,12 +204,10 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
462
204
  signal,
463
205
  }
464
206
  chat.set('isLoading', true)
465
- const start = new Date().getTime()
466
207
  try {
467
208
  const result = await runQuickCommand(ctx)
468
209
  if (result) {
469
210
  outputResult(ctx, result)
470
- registerAnalyticsEvent(ctx, '200', start)
471
211
  }
472
212
  } catch (error: any) {
473
213
  let message = error.message || `${error}`
@@ -480,7 +220,6 @@ export function createQuickCommandInterceptor(widget: WidgetState, getEditor: ()
480
220
  agent: chat.get('agent'),
481
221
  type: 'text',
482
222
  }))
483
- registerAnalyticsEvent(ctx, message, start)
484
223
  }
485
224
  ctx.chat.set('isLoading', false)
486
225
  // prevents the next interceptors from running
@@ -202,8 +202,12 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
202
202
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
203
203
  chat.untitled = false
204
204
  }
205
-
206
- const stream = aiClient.sendChatMessage({ context, user_prompt: buildPrompt(content, data) })
205
+
206
+ const stream = aiClient.sendChatMessage({
207
+ context,
208
+ user_prompt: buildPrompt(content, data),
209
+ agent_version_number: chat.get('agent')?.agent_version_number,
210
+ })
207
211
  signal.addEventListener('abort', () => stream.cancel())
208
212
  const botEntry = ChatEntry.createStreamedBotEntry()
209
213
  // we add the chat entry and show the streaming if the streaming feature is enabled
@@ -0,0 +1,55 @@
1
+
2
+ import { Select } from '@stack-spot/citric-react'
3
+ import { useTranslate } from '@stack-spot/portal-translate'
4
+ import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react'
5
+ import { isEqual } from 'lodash'
6
+ import { VersionSelector } from './styled'
7
+
8
+ interface Props {
9
+ value?: number,
10
+ onChange: Dispatch<SetStateAction<number | undefined>>,
11
+ options?: number[],
12
+ lazyLoadOptions?: boolean,
13
+ useVersions?: (id: string, enabled: boolean) => number[] | undefined,
14
+ id: string,
15
+ }
16
+
17
+ export const SelectVersion = ({ useVersions, value, onChange, options: initialOptions, id, lazyLoadOptions=false }: Props) => {
18
+ const t = useTranslate(dictionary)
19
+ const [options, setOptions] = useState(initialOptions)
20
+ const [enabled, setEnabled] = useState(!lazyLoadOptions)
21
+ const versions = useVersions?.(id, enabled)
22
+
23
+ const onClick = (e?: MouseEvent<HTMLDivElement>) => {
24
+ e?.stopPropagation()
25
+ if (lazyLoadOptions) {
26
+ setEnabled(true)
27
+ }
28
+ }
29
+
30
+ useEffect(() => {
31
+ if (lazyLoadOptions && !isEqual(versions, options)){
32
+ setOptions(versions)
33
+ }
34
+ }, [versions])
35
+
36
+ return (
37
+ <VersionSelector>
38
+ <Select className="version-selector"
39
+ options={options || []}
40
+ value={value}
41
+ onChange={onChange}
42
+ onClick={onClick}
43
+ renderLabel={(item) =>`${t.version} ${item}`} />
44
+ </VersionSelector>
45
+ )
46
+ }
47
+
48
+ const dictionary = {
49
+ en: {
50
+ version: 'Version',
51
+ },
52
+ pt: {
53
+ version: 'Versão',
54
+ },
55
+ }
@@ -10,6 +10,7 @@ import { useCurrentChatState, useWidgetState } from '../../context/hooks'
10
10
  import { getUrlToStackSpotAI } from '../../utils/url'
11
11
  import { ButtonFavorite } from '../ButtonFavorite'
12
12
  import { Fading } from '../Fading'
13
+ import { SelectVersion } from './SelectVersion'
13
14
  import { SelectorBox } from './styled'
14
15
 
15
16
  type SectionVisibility = AgentVisibilityLevel | VisibilityLevelEnum
@@ -20,6 +21,7 @@ interface Item {
20
21
  slug: string,
21
22
  description: string,
22
23
  visibility_level: SectionVisibility,
24
+ version_number?: number,
23
25
  }
24
26
 
25
27
  interface Favorite<T> {
@@ -69,6 +71,8 @@ interface SelectorConfig<T> {
69
71
  regex: RegExp,
70
72
  urlBuilder: (item: T) => string,
71
73
  searchProp: keyof T,
74
+ isEnabledVersionContent?: boolean,
75
+ useVersions?: (id: string, enabled: boolean) => number[] | undefined,
72
76
  isEnabled: boolean,
73
77
  sections: SectionVisibility[],
74
78
  renderComponentItem: FC<T>,
@@ -91,10 +95,11 @@ interface SelectorProps<T> {
91
95
 
92
96
  const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }: ListItemProps<T>) => {
93
97
  const t = useTranslate(dictionary)
94
- const { urlBuilder, renderComponentItem } = selectorConfig
98
+ const { urlBuilder, renderComponentItem, useVersions, isEnabledVersionContent } = selectorConfig
95
99
  const linkTitle = interpolate(t.open, item.slug)
96
100
  const listFavorites = favorite?.useFavorites?.()
97
-
101
+ const [selectedVersion, setSelectedVersion] = useState(item.version_number)
102
+
98
103
  return (
99
104
  <li>
100
105
  <button
@@ -106,6 +111,10 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }:
106
111
  >
107
112
  {renderComponentItem(item)}
108
113
  </button>
114
+ {isEnabledVersionContent &&
115
+ <SelectVersion options={item.version_number ? [item.version_number] : []} id={item.id}
116
+ value={selectedVersion} onChange={setSelectedVersion} lazyLoadOptions={true} useVersions={useVersions}
117
+ /> }
109
118
  <IconLink
110
119
  icon="ExternalLink"
111
120
  title={linkTitle}
@@ -156,3 +156,50 @@ export const SelectorBox = styled.div<{ $tabsCount: number }>`
156
156
  }
157
157
  }
158
158
  `
159
+
160
+ export const VersionSelector = styled.div`
161
+ position: relative;
162
+ min-width: 100px;
163
+
164
+ .version-selector {
165
+ height: 28px;
166
+ position: absolute;
167
+ top: -18px;
168
+ left: -12px;
169
+
170
+ header {
171
+ height: 20px;
172
+ background-color: ${theme.color.light[300]};
173
+ border: none;
174
+ margin-bottom: 0;
175
+ gap: 0;
176
+ padding: 0;
177
+
178
+ }
179
+ .selection-panel .options{
180
+ overflow: hidden;
181
+ }
182
+ li {
183
+ gap: 5px;
184
+ padding: 0 8px 0 !important;
185
+ padding-left: 5px;
186
+ }
187
+ }
188
+
189
+ `
190
+
191
+ export const VersionSelectorBox = styled.div`
192
+ border: 1px solid ${theme.color.light[500]};
193
+ border-radius: 4px;
194
+ .version-selector {
195
+ position: relative;
196
+ top: 0;
197
+ left: 0;
198
+ }
199
+ > div {
200
+ position: relative;
201
+ .options {
202
+ overflow: hidden;
203
+ }
204
+ }
205
+ `
@@ -0,0 +1,31 @@
1
+ import { accountClient } from '@stack-spot/portal-network'
2
+
3
+ const useEnabledFeatureFlags = (resourceType?: string, resourceSlug?: string) => {
4
+ const queryParams = resourceType && resourceSlug ? { [resourceType]: resourceSlug } : {}
5
+ const [featureFlags, , error, { isLoading }] = accountClient.getEnabledFeatureFlagsForAccount.useStatefulQuery({
6
+ queryParams,
7
+ })
8
+ return { featureFlags, isLoading, error }
9
+ }
10
+
11
+ export const useIsFeatureFlagEnabled = (
12
+ flagSlug: string,
13
+ resourceType?: string,
14
+ resourceSlug?: string,
15
+ ) => {
16
+ const { featureFlags, isLoading } = useEnabledFeatureFlags(resourceType, resourceSlug)
17
+ if (resourceType && resourceSlug) {
18
+ const featureFlag = featureFlags?.find((flag) => flag.slug === flagSlug)
19
+ const resourcesByType = featureFlag?.resources?.[resourceType]
20
+ if (!!featureFlag && !!resourcesByType) {
21
+ if (resourcesByType.mode === 'ALL') {
22
+ return { flagEnabled: true, isLoading }
23
+ } else {
24
+ return { flagEnabled: !!featureFlag.resources?.[resourceType].slugs.some((slug) => slug === resourceSlug), isLoading }
25
+ }
26
+ }
27
+ return { flagEnabled: false, isLoading }
28
+ } else {
29
+ return { flagEnabled: !!featureFlags?.some((flag) => flag.slug === flagSlug), isLoading }
30
+ }
31
+ }
@@ -11,6 +11,7 @@ export interface LabeledAgent extends LabeledWithImage {
11
11
  builtIn?: boolean,
12
12
  slug?: string,
13
13
  visibility_level?: string,
14
+ agent_version_number?: number,
14
15
  }
15
16
 
16
17
  export interface FileSize {
@@ -1,9 +1,9 @@
1
1
 
2
- export type ToolWithImage = { id?: string, image?: string, name: string, description?: string }
2
+ export type ToolWithImage = { id?: string | null, image?: string, name?: string, description?: string }
3
3
 
4
4
  interface Toolkit {
5
5
  id?: string | null,
6
- tools?: { id?: string, name?: string, description?: string, function?: { name: string, description?: string } }[] | null,
6
+ tools?: { id?: string | null, name?: string, description?: string, function?: { name: string, description: string } | null }[] | null,
7
7
  image_url?: string | null,
8
8
  avatar?: string | null,
9
9
  }
@@ -22,8 +22,8 @@ export function toolById(id: string, toolkits: Toolkit[] | undefined): ToolWithI
22
22
  if (mcp) {
23
23
  const [, toolkitId, toolName] = mcp
24
24
  const toolkit = toolkits?.find(tk => tk.id === toolkitId)
25
- const tool = toolkit?.tools?.find(t => t.name === toolName)
26
- return { id, image: toolkit?.avatar ?? undefined, name: toolName, description: tool?.function?.description }
25
+ const tool = toolkit?.tools?.find(t => t.name === toolName || t.function?.name === toolName)
26
+ return { id, image: toolkit?.avatar ?? undefined, name: tool?.function?.name, description: tool?.function?.description }
27
27
  }
28
28
  const { tool, toolkit } = findToolById(id, toolkits ?? []) ?? {}
29
29
  return (tool && toolkit)