@stack-spot/ai-chat-widget 1.0.0-dev.1769120820021 → 1.0.0-dev.1769797270860

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 (62) hide show
  1. package/CHANGELOG.md +27 -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 +29 -234
  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/Editor.d.ts.map +1 -1
  40. package/dist/views/Editor.js +16 -2
  41. package/dist/views/Editor.js.map +1 -1
  42. package/dist/views/KnowledgeSources.js +1 -1
  43. package/dist/views/KnowledgeSources.js.map +1 -1
  44. package/dist/views/MessageInput/ContextBar.js +1 -1
  45. package/dist/views/MessageInput/ContextBar.js.map +1 -1
  46. package/dist/views/Steps/dictionary.d.ts +1 -1
  47. package/package.json +5 -2
  48. package/src/StackspotAIWidget.tsx +1 -0
  49. package/src/app-metadata.json +15 -3
  50. package/src/chat-interceptors/quick-commands.ts +38 -278
  51. package/src/chat-interceptors/send-message.ts +40 -40
  52. package/src/components/FileDescription.tsx +1 -1
  53. package/src/components/Markdown.tsx +42 -23
  54. package/src/components/Selector/index.tsx +6 -6
  55. package/src/components/TabManager.tsx +31 -8
  56. package/src/state/ChatState.ts +6 -0
  57. package/src/utils/chat.ts +23 -22
  58. package/src/utils/knowledge-source.ts +1 -1
  59. package/src/views/ChatHistory/utils.ts +11 -6
  60. package/src/views/Editor.tsx +20 -3
  61. package/src/views/KnowledgeSources.tsx +1 -1
  62. package/src/views/MessageInput/ContextBar.tsx +1 -1
@@ -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',
@@ -21,6 +21,7 @@ const EditorBox = styled.div`
21
21
  --vscode-editor-background: transparent !important;
22
22
  --vscode-editorGutter-background: transparent !important;
23
23
  }
24
+
24
25
  `
25
26
 
26
27
  const TitleBox = styled.div`
@@ -69,7 +70,7 @@ const Title = () => {
69
70
 
70
71
  return (
71
72
  <TitleBox>
72
- <Text appearance="h5">Editor</Text>
73
+ <Text appearance="h5" tag="h2" >Editor</Text>
73
74
  <Select
74
75
  options={languages}
75
76
  renderLabel={l => l?.label ?? defaultLanguage}
@@ -91,6 +92,22 @@ const EditorPanel = () => {
91
92
  const selectionObserver = useRef<IDisposable | undefined>()
92
93
 
93
94
  const setup: OnMount = useCallback(async (editor) => {
95
+ const container = editor.getContainerDomNode()
96
+ const el = container.querySelector('.native-edit-context')
97
+ if (el && el.hasAttribute('aria-label')) {
98
+ el.removeAttribute('aria-label')
99
+ }
100
+
101
+ const observer = new MutationObserver(() => {
102
+ const el = container.querySelector('.native-edit-context')
103
+ if (el && el.hasAttribute('aria-label')) {
104
+ el.removeAttribute('aria-label')
105
+ }
106
+ })
107
+ observer.observe(container, { subtree: true, attributes: true, childList: true })
108
+
109
+ editor.onDidDispose(() => observer.disconnect())
110
+
94
111
  selectionObserver.current = editor.onDidChangeCursorSelection(debounce((e) => {
95
112
  const selectedText = editor.getModel()?.getValueInRange(e.selection)
96
113
  chat.set('codeSelection', selectedText?.trim() ? selectedText : undefined)
@@ -110,14 +127,14 @@ const EditorPanel = () => {
110
127
  selectionObserver.current?.dispose()
111
128
  }
112
129
  }, [])
113
-
130
+
114
131
  return (
115
132
  <EditorBox>
116
133
  <MonacoEditor
117
134
  height="100%"
118
135
  language={language}
119
136
  theme={themeKind === 'dark' ? 'vs-dark' : 'light'}
120
- options={{ minimap: { enabled: false } }}
137
+ options={{ minimap: { enabled: false }, accessibilitySupport: 'off' }}
121
138
  value={value}
122
139
  onChange={v => chat.set('code', v)}
123
140
  loading={<ProgressCircular />}
@@ -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>