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

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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -1
  3. package/dist/StackspotAIWidget.d.ts +1 -0
  4. package/dist/StackspotAIWidget.d.ts.map +1 -1
  5. package/dist/StackspotAIWidget.js +1 -0
  6. package/dist/StackspotAIWidget.js.map +1 -1
  7. package/dist/app-metadata.json +15 -3
  8. package/dist/chat-interceptors/quick-commands.d.ts +15 -0
  9. package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
  10. package/dist/chat-interceptors/quick-commands.js +20 -2
  11. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  12. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  13. package/dist/chat-interceptors/send-message.js +39 -38
  14. package/dist/chat-interceptors/send-message.js.map +1 -1
  15. package/dist/components/FileDescription.js +1 -1
  16. package/dist/components/FileDescription.js.map +1 -1
  17. package/dist/components/Markdown.d.ts.map +1 -1
  18. package/dist/components/Markdown.js +22 -6
  19. package/dist/components/Markdown.js.map +1 -1
  20. package/dist/components/Selector/index.d.ts +1 -1
  21. package/dist/components/Selector/index.d.ts.map +1 -1
  22. package/dist/components/Selector/index.js +3 -3
  23. package/dist/components/Selector/index.js.map +1 -1
  24. package/dist/components/TabManager.d.ts.map +1 -1
  25. package/dist/components/TabManager.js +20 -4
  26. package/dist/components/TabManager.js.map +1 -1
  27. package/dist/state/ChatState.d.ts +5 -0
  28. package/dist/state/ChatState.d.ts.map +1 -1
  29. package/dist/state/ChatState.js.map +1 -1
  30. package/dist/utils/chat.d.ts +4 -12
  31. package/dist/utils/chat.d.ts.map +1 -1
  32. package/dist/utils/chat.js +20 -19
  33. package/dist/utils/chat.js.map +1 -1
  34. package/dist/utils/knowledge-source.js +1 -1
  35. package/dist/utils/knowledge-source.js.map +1 -1
  36. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  37. package/dist/views/ChatHistory/utils.js +9 -5
  38. package/dist/views/ChatHistory/utils.js.map +1 -1
  39. package/dist/views/KnowledgeSources.js +1 -1
  40. package/dist/views/KnowledgeSources.js.map +1 -1
  41. package/dist/views/MessageInput/ContextBar.js +1 -1
  42. package/dist/views/MessageInput/ContextBar.js.map +1 -1
  43. package/dist/views/MessageInput/ModelSwitcher/index.js +4 -2
  44. package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -1
  45. package/dist/views/Steps/dictionary.d.ts +1 -1
  46. package/package.json +5 -2
  47. package/src/StackspotAIWidget.tsx +1 -0
  48. package/src/app-metadata.json +15 -3
  49. package/src/chat-interceptors/quick-commands.ts +25 -4
  50. package/src/chat-interceptors/send-message.ts +40 -40
  51. package/src/components/FileDescription.tsx +1 -1
  52. package/src/components/Markdown.tsx +42 -23
  53. package/src/components/Selector/index.tsx +6 -6
  54. package/src/components/TabManager.tsx +31 -8
  55. package/src/state/ChatState.ts +6 -0
  56. package/src/utils/chat.ts +23 -22
  57. package/src/utils/knowledge-source.ts +1 -1
  58. package/src/views/ChatHistory/utils.ts +11 -6
  59. package/src/views/KnowledgeSources.tsx +1 -1
  60. package/src/views/MessageInput/ContextBar.tsx +1 -1
  61. package/src/views/MessageInput/ModelSwitcher/index.tsx +4 -4
@@ -1,5 +1,4 @@
1
- import { AgentInfo, aiClient, ChatResponseWithPMResources, ChatResponseWithSteps, ChatStep, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
2
- import { ChatResponse3 } from '@stack-spot/portal-network/api/ai'
1
+ import { AgentInfo, aiClient, ChatResponse, ChatResponseWithPMResources, ChatResponseWithSteps, ChatStep, genAiInferenceClient, StackspotAPIError, StreamCanceledError } from '@stack-spot/portal-network'
3
2
  import { findLast } from 'lodash'
4
3
  import { ChatEntry, KnowledgeSource, TextChatEntry } from '../state/ChatEntry'
5
4
  import { ChatState } from '../state/ChatState'
@@ -27,13 +26,13 @@ export function createEntryValueFromChatResponse(
27
26
  const entry = {
28
27
  agentType: 'bot',
29
28
  type: 'md',
30
- content: response.answer ?? '',
29
+ content: response.message ?? '',
31
30
  messageId: response.message_id ?? undefined,
32
31
  knowledgeSources,
33
32
  agent: agent,
34
33
  updated: includeDate ? new Date().toISOString() : undefined,
35
34
  steps: response.steps,
36
- tools: response.tools,
35
+ tools: response.tools_id,
37
36
  opportunities: response.opportunities,
38
37
  hypothesis: response.hypothesis,
39
38
  prfaq: response.prfaq,
@@ -112,39 +111,44 @@ const updatePlanningMessage = (messages: ChatEntry[]) => {
112
111
 
113
112
  export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResponseWithSteps> & { opportunities?: any },
114
113
  chat: ChatState, botEntry: ChatEntry, knowledgeSources: KnowledgeSource[] | undefined) {
115
- if (value.agent_info?.type === 'planning') {
116
- if (value.agent_info.action === 'start') {
117
- chat.set('isPlaning', true)
118
- } else {
119
- chat.set('isPlaning', false)
114
+ for (const agent_info of value.agent_info ?? []) {
115
+ if (agent_info?.type === 'planning') {
116
+ if (agent_info.action === 'start') {
117
+ chat.set('isPlaning', true)
118
+ } else {
119
+ chat.set('isPlaning', false)
120
+ }
120
121
  }
121
- }
122
122
 
123
- if (value.agent_info?.type === 'chat' && value.agent_info?.action === 'end') {
124
- //When an error happens, the step can still be running, so we enforce the error
125
- const stepRunning = findLast(value.steps, (item) => item.status === 'running')
126
- if (stepRunning?.status) {
127
- stepRunning.status = 'error'
123
+ if (agent_info?.type === 'chat' && agent_info?.action === 'end') {
124
+ //When an error happens, the step can still be running, so we enforce the error
125
+ const stepRunning = findLast(value.steps, (item) => item.status === 'running')
126
+ if (stepRunning?.status) {
127
+ stepRunning.status = 'error'
128
+ }
128
129
  }
129
- }
130
130
 
131
- if (value.sources?.length !== knowledgeSources?.length && chat.get('features').showSourcesInResponse) {
132
- knowledgeSources = genericSourcesToKnowledgeSources(value.sources)
133
- }
131
+ const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
132
+ if (lastPlanningAwaiting && value.steps) {
133
+ value.steps?.map(step => {
134
+ if (step.type === 'planning') {
135
+ updatePlanningMessage(messages)
136
+ } else if (step.type === 'step') {
137
+ updateStepMessage(step, messages)
138
+ }
139
+ })
140
+ }
134
141
 
135
- const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
136
- if (lastPlanningAwaiting && value.steps) {
137
- value.steps?.map(step => {
138
- if (step.type === 'planning') {
139
- updatePlanningMessage(messages)
140
- } else if (step.type === 'step') {
141
- updateStepMessage(step, messages)
142
- }
143
- })
142
+ if (agent_info?.type === 'tool' && agent_info?.action !== 'awaiting_approval') {
143
+ updateToolStatus(agent_info, messages)
144
+ }
144
145
  }
145
146
 
146
- if (value.agent_info?.type === 'tool' && value.agent_info?.action !== 'awaiting_approval') {
147
- updateToolStatus(value.agent_info, messages)
147
+ if (chat.get('features').showSourcesInResponse) {
148
+ const mergedSources = [...(value.source ?? []), ...(value.cross_account_source ?? [])]
149
+ if (mergedSources.length !== (knowledgeSources?.length ?? 0)) {
150
+ knowledgeSources = genericSourcesToKnowledgeSources(mergedSources)
151
+ }
148
152
  }
149
153
 
150
154
  if (value.steps) {
@@ -193,7 +197,7 @@ export function helperSendMessage(messages: ChatEntry[], value: Partial<ChatResp
193
197
  export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState, signal: AbortSignal) {
194
198
  const { agentType, content, data } = entry.getValue()
195
199
  if (agentType !== 'user') return
196
- const context = buildConversationContext(chat, entry)
200
+ const context = buildConversationContext(chat, entry, buildPrompt(content, data))
197
201
  chat.set('isLoading', true)
198
202
  const untitled = chat.untitled
199
203
  const messages = chat.getMessages()
@@ -202,12 +206,8 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
202
206
  chat.set('label', content || entry.getValue().upload?.[0]?.name || 'Chat')
203
207
  chat.untitled = false
204
208
  }
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
- })
209
+
210
+ const stream = genAiInferenceClient.sendChatMessage(context)
211
211
  signal.addEventListener('abort', () => stream.cancel())
212
212
  const botEntry = ChatEntry.createStreamedBotEntry()
213
213
  // we add the chat entry and show the streaming if the streaming feature is enabled
@@ -220,14 +220,14 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
220
220
  helperSendMessage(messages, value, chat, botEntry, knowledgeSources)
221
221
  })
222
222
 
223
- let finalValue: Partial<ChatResponse3> | undefined
223
+ let finalValue: Partial<ChatResponse> | undefined
224
224
  try {
225
225
  finalValue = await stream.getValue()
226
226
 
227
227
  const lastPlanningAwaiting = getLastPlanningAwaiting(messages)
228
228
  if (lastPlanningAwaiting) {
229
229
  const value = lastPlanningAwaiting.getValue()
230
- value.content = finalValue.answer || value.content
230
+ value.content = finalValue.message || value.content
231
231
  lastPlanningAwaiting.setValue(value)
232
232
  }
233
233
  // if the streaming feature is not enabled, we only add the chat entry once the streaming has finished
@@ -247,7 +247,7 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
247
247
  })
248
248
  }
249
249
  }
250
- if (finalValue?.answer) {
250
+ if (finalValue?.message) {
251
251
  botEntry.setValue({
252
252
  ...createEntryValueFromChatResponse(finalValue, botEntry.getValue().knowledgeSources, chat.get('agent'), true),
253
253
  hasPlanning: botEntry.getValue().hasPlanning,
@@ -94,7 +94,7 @@ export const FileDescription = ({ fileName, icon, status, onRemove, onRetry }: F
94
94
  icon="TimesMini"
95
95
  onClick={onRemove}
96
96
  title={t.remove}
97
- arial-label={`${t.remove} ${name}`}
97
+ aria-label={name}
98
98
  style={{ alignSelf: 'start' }}
99
99
  size="xs"
100
100
  />}
@@ -4,7 +4,9 @@
4
4
 
5
5
  import { Children, Fragment } from 'react'
6
6
  import ReactMarkdown from 'react-markdown'
7
+ import rehypeKatex from 'rehype-katex'
7
8
  import remarkGfm from 'remark-gfm'
9
+ import remarkMath from 'remark-math'
8
10
  import { WithChildren } from '../types'
9
11
  import { Code, Props as CodeProps } from './Code'
10
12
 
@@ -29,26 +31,43 @@ export const Markdown = (
29
31
  onCopyCode,
30
32
  children,
31
33
  }: Props,
32
- ) => (
33
- <>
34
- <ReactMarkdown
35
- className="markdown apply-citric"
36
- remarkPlugins={[[remarkGfm]]}
37
- components={{
38
- a: props => <a target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'underline' }} {...props} />,
39
- code: props =>
40
- <Code
41
- {...props}
42
- onInsertCode={onInsertCode}
43
- onNewFile={onNewFile}
44
- onCopyCode={onCopyCode}
45
- showActionBar
46
- />,
47
- pre: ({ children }) => <>{children}</>,
48
- p: ({ children }) => <p>{Children.map(children, renderP)}</p>,
49
- }}
50
- >
51
- {children}
52
- </ReactMarkdown>
53
- </>
54
- )
34
+ ) => {
35
+
36
+ // fix parsing LaTex: https://github.com/remarkjs/react-markdown/issues/785
37
+ const content = typeof children === 'string'
38
+ ? children
39
+ .replace(/\\\\\[/g, '$$$$') // Replace '\\[' with '$$'
40
+ .replace(/\\\\\]/g, '$$$$') // Replace '\\]' with '$$'
41
+ .replace(/\\\\\(/g, '$$$$') // Replace '\\(' with '$$'
42
+ .replace(/\\\\\)/g, '$$$$') // Replace '\\)' with '$$'
43
+ .replace(/\\\[/g, '$$$$') // Replace '\[' with '$$'
44
+ .replace(/\\\]/g, '$$$$') // Replace '\]' with '$$'
45
+ .replace(/\\\(/g, '$$$$') // Replace '\(' with '$$'
46
+ .replace(/\\\)/g, '$$$$') // Replace '\)' with '$$';
47
+ : children
48
+
49
+ return (
50
+ <>
51
+ <ReactMarkdown
52
+ className="markdown apply-citric"
53
+ remarkPlugins={[[remarkMath, { singleDollarTextMath: true }], remarkGfm]}
54
+ rehypePlugins={[rehypeKatex]}
55
+ components={{
56
+ a: props => <a target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'underline' }} {...props} />,
57
+ code: props =>
58
+ <Code
59
+ {...props}
60
+ onInsertCode={onInsertCode}
61
+ onNewFile={onNewFile}
62
+ onCopyCode={onCopyCode}
63
+ showActionBar
64
+ />,
65
+ pre: ({ children }) => <>{children}</>,
66
+ p: ({ children }) => <p>{Children.map(children, renderP)}</p>,
67
+ }}
68
+ >
69
+ {content}
70
+ </ReactMarkdown>
71
+ </>
72
+ )
73
+ }
@@ -42,7 +42,7 @@ interface ListProps<T> {
42
42
  filter?: string,
43
43
  visibility?: SectionVisibility,
44
44
  selectorConfig: SelectorConfig<T>,
45
- onSelect: (item: T) => void,
45
+ onSelect: (item: T, selectedVersion?: number) => void,
46
46
  favorite?: Favorite<T>,
47
47
  }
48
48
 
@@ -76,7 +76,7 @@ interface SelectorConfig<T> {
76
76
  isEnabled: boolean,
77
77
  sections: SectionVisibility[],
78
78
  renderComponentItem: FC<T>,
79
- onSelect: (item: T) => void,
79
+ onSelect: (item: T, selectedVersion?: number) => void,
80
80
  /**
81
81
  * A react hook or a simple function that returns the selectable items.
82
82
  *
@@ -104,7 +104,7 @@ const ListItem = <T extends Item>({ item, selectorConfig, onSelect, favorite }:
104
104
  <li>
105
105
  <button
106
106
  className="selector"
107
- onClick={() => onSelect(item)}
107
+ onClick={() => onSelect(item, selectedVersion)}
108
108
  onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
109
109
  onFocus={(e) => e.target.closest('li')?.classList.add('focus')}
110
110
  onBlur={(e) => e.target.closest('li')?.classList.remove('focus')}
@@ -157,14 +157,14 @@ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, fa
157
157
  )
158
158
  }
159
159
 
160
- const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentProps<any>) => {
160
+ const SelectorContent = <T, > ({ filter, onClose, selectorConfig, favorite }: ContentProps<T>) => {
161
161
  const t = useTranslate(dictionary)
162
162
  const ref = useRef<HTMLDivElement>(null)
163
163
  const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
164
164
  const isGroupResourcesByScope = useWidgetState('features')?.groupResourcesByScope
165
165
  const { resourceName, icon, onSelect, sections } = selectorConfig
166
- const onSelectItem = useCallback((slug: string) => {
167
- onSelect(slug)
166
+ const onSelectItem = useCallback((item: any, selectedVersion?: number) => {
167
+ onSelect(item, selectedVersion)
168
168
  onClose()
169
169
  }, [])
170
170
 
@@ -5,7 +5,7 @@ import { Button, IconButton } from '@stack-spot/citric-react'
5
5
  import { listToClass, theme } from '@stack-spot/portal-theme'
6
6
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
7
7
  import { last } from 'lodash'
8
- import { useCallback, useEffect, useMemo, useRef } from 'react'
8
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
9
9
  import { styled } from 'styled-components'
10
10
  import { ButtonAction } from '../types'
11
11
  import { FadingOverflow } from './FadingOverflow'
@@ -152,19 +152,31 @@ export function TabManager<T, Key extends React.Key>(
152
152
  const t = useTranslate(dictionary)
153
153
  const tabList = useRef<HTMLUListElement>(null)
154
154
  const lastNumberOfTabs = useRef(tabs.length)
155
+ const [ariaMessage, setAriaMessage] = useState('')
155
156
 
156
157
  const onClickTab = useCallback((event: React.MouseEvent<HTMLElement, MouseEvent>) => {
157
158
  const target = event.target as HTMLElement
158
159
  if (target.tagName === 'LI') target.querySelector('button')?.click()
159
160
  }, [])
160
161
 
161
- const tabItems = useMemo(() => tabs.map((tab) => (
162
- <li key={keygen(tab)} className={keygen(tab) === active ? 'active' : undefined} onClick={onClickTab}>
163
- <button className="label" onClick={() => onSelect(tab)}><FadingOverflow>{renderLabel(tab)}</FadingOverflow></button>
164
- {tabs.length > 1 &&
165
- <IconButton appearance="text" icon="TimesMini" aria-label={t.close} title={t.close} onClick={() => onRemove(tab)} />}
166
- </li>
167
- )), [tabs, active])
162
+ const tabItems = useMemo(() => tabs.map((tab, idx) => {
163
+ const tabLabel = typeof renderLabel(tab) === 'string'
164
+ ? renderLabel(tab)
165
+ : `${t.tab} ${idx + 1}`
166
+
167
+ return (
168
+ <li key={keygen(tab)} className={keygen(tab) === active ? 'active' : undefined} onClick={onClickTab}>
169
+ <button className="label" aria-label={`${t.chat} ${tabLabel}`} onClick={() => onSelect(tab)}>
170
+ <FadingOverflow>{renderLabel(tab)}</FadingOverflow>
171
+ </button>
172
+ {tabs.length > 1 &&
173
+ <IconButton appearance="text" icon="TimesMini" aria-label={`${tabLabel}`}
174
+ title={t.close} onClick={() => { onRemove(tab), setAriaMessage(`${t.chat} ${tabLabel} ${t.closed}`)
175
+
176
+ // Clears the message after a short time to prevent it from repeating
177
+ setTimeout(() => setAriaMessage(''), 1000)}} />}
178
+ </li>
179
+ )}), [tabs, active])
168
180
 
169
181
  const extras = useMemo(() => buttons.map(({
170
182
  ariaLabel, title, label, onClick, group, icon, appearance, size, className, style, disabled }) => label
@@ -204,6 +216,11 @@ export function TabManager<T, Key extends React.Key>(
204
216
 
205
217
  return (
206
218
  <Tabs $numberOfExtraButtons={buttons.length} className="tabs">
219
+ <div
220
+ aria-live="polite" aria-atomic="true"
221
+ style={{ position: 'absolute', left: '-9999px', width: '1px', height: '1px', overflow: 'hidden' }}>
222
+ {ariaMessage}
223
+ </div>
207
224
  <FadingOverflow className="list-overflow" scroll="arrows" enableHorizontalScrollWithVerticalWheel>
208
225
  <ul ref={tabList}>{tabItems}</ul>
209
226
  </FadingOverflow>
@@ -215,9 +232,15 @@ export function TabManager<T, Key extends React.Key>(
215
232
  const dictionary = {
216
233
  en: {
217
234
  close: 'Close',
235
+ tab: 'Tab',
236
+ chat: 'Chat',
237
+ closed: 'Closed',
218
238
  },
219
239
  pt: {
220
240
  close: 'Fechar',
241
+ tab: 'Aba',
242
+ chat: 'Chat',
243
+ closed: 'Fechado',
221
244
  },
222
245
  } satisfies Dictionary
223
246
 
@@ -1,3 +1,4 @@
1
+ import { ChatRequest } from '@stack-spot/portal-network/api/genAiInference'
1
2
  import { dropRight, last, pull } from 'lodash'
2
3
  import { ulid } from 'ulid'
3
4
  import { AbortedError } from '../AbortedError'
@@ -63,6 +64,11 @@ export interface ChatPropertiesWithOptionalFeatures {
63
64
  * The current LLM (Large Language Model) being used for this chat.
64
65
  */
65
66
  selected_model_id?: string,
67
+
68
+ /**
69
+ * Extra options to be merged into the chat request payload sent to the backend.
70
+ */
71
+ requestOptions?: Pick<ChatRequest, 'deep_search_ks' | 'return_ks_in_response' | 'use_conversation'>,
66
72
  }
67
73
 
68
74
  export interface ChatProperties extends ChatPropertiesWithOptionalFeatures {
package/src/utils/chat.ts CHANGED
@@ -1,31 +1,32 @@
1
- import { FixedChatRequest } from '@stack-spot/portal-network'
1
+ import type { FixedChatRequest } from '@stack-spot/portal-network'
2
2
  import appData from '../app-metadata.json'
3
- import { ChatEntry } from '../state/ChatEntry'
4
- import { ChatState } from '../state/ChatState'
5
- import { defaultLanguage } from './programming-languages'
3
+ import type { ChatEntry } from '../state/ChatEntry'
4
+ import type { ChatState } from '../state/ChatState'
6
5
 
7
- /**
8
- * Builds a conversation context from a ChatState.
9
- *
10
- * The conversation context is needed by most backend services.
11
- *
12
- * @param state the ChatState to build the context from.
13
- * @returns the conversation context ready to be sent to the backend.
14
- */
15
- export function buildConversationContext(state: ChatState, message?: ChatEntry): FixedChatRequest['context'] {
6
+ export function buildConversationContext(state: ChatState, message?: ChatEntry | undefined, userPrompt?: string): FixedChatRequest {
7
+ const requestOptions = state.get('requestOptions')
16
8
  return {
17
- workspace: state.get('workspace')?.id,
9
+ streaming: true,
10
+ show_chat_processing_state: true,
11
+ return_ks_in_response: state.get('features')?.showSourcesInResponse,
12
+ deep_search_ks: false,
13
+ stackspot_knowledge: false,
14
+ user_prompt: userPrompt ?? '',
15
+ use_conversation: true,
18
16
  conversation_id: state.id,
17
+ workspace_id: state.get('workspace')?.id,
19
18
  stack_id: state.get('stack')?.id,
20
- language: state.get('codeLanguage') || (state.get('codeSelection') ? defaultLanguage : undefined),
21
19
  knowledge_sources: state.get('knowledgeSources')?.map(ks => ks.id),
22
20
  upload_ids: message?.getValue().upload?.map(f => f.id),
23
- agent_id: state.get('agent')?.id,
24
- agent_built_in: state.get('agent')?.builtIn,
25
- os: navigator.userAgent,
26
- platform: 'web-widget',
27
- platform_version: navigator.userAgent,
28
- stackspot_ai_version: appData.version,
29
- selected_model_id: state.get('selected_model_id'),
21
+ selected_model: state.get('selected_model_id') ?? null,
22
+ agent_version_number: state.get('agent')?.agent_version_number,
23
+ context: {
24
+ os: navigator.userAgent,
25
+ platform: 'web-widget',
26
+ platform_version: navigator.userAgent,
27
+ stackspot_ai_version: appData.version,
28
+ agent_id: state.get('agent')?.id,
29
+ },
30
+ ...requestOptions,
30
31
  }
31
32
  }
@@ -62,7 +62,7 @@ export function extractCodeFromKSDocument(document: DocumentResponse): { languag
62
62
  export function genericSourcesToKnowledgeSources(
63
63
  sources: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile3)[] | undefined,
64
64
  ): KnowledgeSource[] | undefined {
65
- return sources?.filter(s => s.type === 'knowledge_source').map(ks => {
65
+ return sources?.filter(s => s.type === 'knowledge_source' || s.type === 'cross_account').map(ks => {
66
66
  const { document_id: documentId, document_score: documentScore, name, slug } = ks as SourceKnowledgeSource
67
67
  return { documentId, documentScore, name, slug }
68
68
  })
@@ -140,10 +140,12 @@ function toolsFromAgentInfo(agentInfo: any[] | null | undefined): string[] {
140
140
  */
141
141
  export async function loadChat(widget: WidgetState, conversationId: string) {
142
142
  const chat = await aiClient.chat.query({ conversationId })
143
- const historyAgents = chat.history?.reduce<{ agent_core_id: string }[]>((accumulator, item) => {
143
+ const addedIds: Record<string, boolean> = {}
144
+ const historyAgents = chat.history?.reduce<{ agent_core_id: string, version_number?: number }[]>((accumulator, item) => {
144
145
  const agentId = item.custom_agent?.id
145
- if (agentId !== undefined) {
146
- accumulator.push({ agent_core_id: agentId })
146
+ if (agentId !== undefined && !addedIds[agentId]) {
147
+ addedIds[agentId] = true
148
+ accumulator.push({ agent_core_id: agentId, version_number: item.custom_agent?.version_number })
147
149
  }
148
150
  return accumulator
149
151
  }, []) ?? []
@@ -155,13 +157,16 @@ export async function loadChat(widget: WidgetState, conversationId: string) {
155
157
  ])
156
158
  const agentsAsLabeledAgents: LabeledAgent[] = agents
157
159
  .map((a) => ({ ...a, label: a.name, image: a.avatar ?? '', builtIn: a.visibility_level === 'built_in' }))
158
-
160
+
159
161
  const agent = agentsAsLabeledAgents.find(a => a.id === last(chat.history)?.custom_agent?.id)
160
162
  const builtIn = !!last(chat.history ?? [])?.custom_agent?.built_in
161
163
  widget.chatTabs.add(new ChatState({
162
164
  id: chat.id,
163
- initial: { features: widget.chatFeatures, label: chat.title, stack, workspace, agent: agent ? {
164
- ...agent, builtIn } : undefined },
165
+ initial: {
166
+ features: widget.chatFeatures, label: chat.title, stack, workspace, agent: agent ? {
167
+ ...agent, builtIn,
168
+ } : undefined,
169
+ },
165
170
  interceptors: widget.interceptors,
166
171
  entries: await Promise.all(chat.history?.map(async (item, index) => new ChatEntry({
167
172
  agentType: item.agent === 'USER' ? 'user' : 'bot',
@@ -189,7 +189,7 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
189
189
  hasNextPage={hasNextPage && !workspaceId}
190
190
  fetchNextPage={fetchNextPage}
191
191
  data={(ks: KnowledgeSourceItemResponse) => ({
192
- idOrSlug: ks.slug, description: ks.description, name: ks.name, listFavorites,
192
+ idOrSlug: ks.id, description: ks.description, name: ks.name, listFavorites,
193
193
  onAddFavorite, onRemoveFavorite,
194
194
  })}
195
195
  emptyResults={<Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}
@@ -21,7 +21,7 @@ const ContextBadge = ({ label, color, dismiss, onDismiss }: ContextBadgeProps) =
21
21
  appearance="none"
22
22
  onClick={onDismiss}
23
23
  title={dismiss}
24
- arial-label={dismiss}
24
+ aria-label={dismiss}
25
25
  style={{ padding: '2px 0 2px 2px', color: 'inherit' }}
26
26
  />}
27
27
  </Badge>
@@ -1,5 +1,5 @@
1
1
  import { CitricIconOutline, CitricIconSocial } from '@stack-spot/citric-icons'
2
- import { AsyncContent, Button, Column, FieldGroup, Icon, Input, Row } from '@stack-spot/citric-react'
2
+ import { Button, Column, FieldGroup, Icon, Input, ProgressCircular, Row } from '@stack-spot/citric-react'
3
3
  import { SelectionList } from '@stack-spot/portal-components/SelectionList'
4
4
  import { agentToolsClient, genAiInferenceClient } from '@stack-spot/portal-network'
5
5
  import { useMemo, useState } from 'react'
@@ -31,7 +31,8 @@ export const ModelSwitcher = () => {
31
31
 
32
32
  return (
33
33
  <RowWrapperStyled>
34
- <AsyncContent loading={isLoadingAgentData && isLoadingModels}>
34
+ {(isLoadingAgentData && isLoadingModels) ?
35
+ <ProgressCircular /> :
35
36
  <Button
36
37
  className="button-select-model"
37
38
  colorScheme="light"
@@ -46,8 +47,7 @@ export const ModelSwitcher = () => {
46
47
  />
47
48
  {modelName}
48
49
  <Icon icon="ChevronDown" group="fill" size="sm" />
49
- </Button>
50
- </AsyncContent>
50
+ </Button>}
51
51
  <SelectionList
52
52
  id="menuModelSwitcher"
53
53
  items={data || []}