@stack-spot/ai-chat-widget 3.4.1-beta.5 → 3.6.0-beta.5

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 (81) hide show
  1. package/CHANGELOG.md +89 -6
  2. package/dist/app-metadata.json +4 -4
  3. package/dist/chat-interceptors/quick-commands.js +2 -2
  4. package/dist/chat-interceptors/quick-commands.js.map +1 -1
  5. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  6. package/dist/chat-interceptors/send-message.js +8 -17
  7. package/dist/chat-interceptors/send-message.js.map +1 -1
  8. package/dist/components/form/DescribedCheckboxGroup.d.ts +3 -1
  9. package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
  10. package/dist/components/form/DescribedCheckboxGroup.js +31 -19
  11. package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
  12. package/dist/context/hooks.d.ts +1 -0
  13. package/dist/context/hooks.d.ts.map +1 -1
  14. package/dist/context/hooks.js +24 -0
  15. package/dist/context/hooks.js.map +1 -1
  16. package/dist/state/ChatEntry.d.ts +8 -0
  17. package/dist/state/ChatEntry.d.ts.map +1 -1
  18. package/dist/state/ChatEntry.js +1 -1
  19. package/dist/state/ChatEntry.js.map +1 -1
  20. package/dist/state/ChatState.d.ts +6 -0
  21. package/dist/state/ChatState.d.ts.map +1 -1
  22. package/dist/state/ChatState.js +15 -0
  23. package/dist/state/ChatState.js.map +1 -1
  24. package/dist/utils/tools.d.ts +17 -8
  25. package/dist/utils/tools.d.ts.map +1 -1
  26. package/dist/utils/tools.js +20 -9
  27. package/dist/utils/tools.js.map +1 -1
  28. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  29. package/dist/views/Agents/AgentDescription.js +5 -14
  30. package/dist/views/Agents/AgentDescription.js.map +1 -1
  31. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  32. package/dist/views/Agents/AgentsTab.js +3 -2
  33. package/dist/views/Agents/AgentsTab.js.map +1 -1
  34. package/dist/views/Chat/ChatMessage.d.ts +1 -1
  35. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  36. package/dist/views/Chat/ChatMessage.js +19 -20
  37. package/dist/views/Chat/ChatMessage.js.map +1 -1
  38. package/dist/views/Chat/StepsList.d.ts +2 -8
  39. package/dist/views/Chat/StepsList.d.ts.map +1 -1
  40. package/dist/views/Chat/StepsList.js +8 -7
  41. package/dist/views/Chat/StepsList.js.map +1 -1
  42. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  43. package/dist/views/ChatHistory/utils.js +94 -2
  44. package/dist/views/ChatHistory/utils.js.map +1 -1
  45. package/dist/views/KnowledgeSources.d.ts +1 -1
  46. package/dist/views/KnowledgeSources.d.ts.map +1 -1
  47. package/dist/views/KnowledgeSources.js +31 -45
  48. package/dist/views/KnowledgeSources.js.map +1 -1
  49. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  50. package/dist/views/MessageInput/QuickCommandSelector.js +29 -52
  51. package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
  52. package/dist/views/MessageInput/index.d.ts.map +1 -1
  53. package/dist/views/MessageInput/index.js +9 -1
  54. package/dist/views/MessageInput/index.js.map +1 -1
  55. package/dist/views/MessageInput/styled.d.ts.map +1 -1
  56. package/dist/views/MessageInput/styled.js +4 -0
  57. package/dist/views/MessageInput/styled.js.map +1 -1
  58. package/dist/views/Resources.js +8 -5
  59. package/dist/views/Resources.js.map +1 -1
  60. package/dist/views/Tools.js +1 -1
  61. package/dist/views/Tools.js.map +1 -1
  62. package/package.json +3 -3
  63. package/src/app-metadata.json +4 -4
  64. package/src/chat-interceptors/quick-commands.ts +2 -2
  65. package/src/chat-interceptors/send-message.ts +8 -19
  66. package/src/components/form/DescribedCheckboxGroup.tsx +61 -35
  67. package/src/context/hooks.ts +24 -0
  68. package/src/state/ChatEntry.ts +9 -1
  69. package/src/state/ChatState.ts +16 -0
  70. package/src/utils/tools.ts +28 -17
  71. package/src/views/Agents/AgentDescription.tsx +40 -36
  72. package/src/views/Agents/AgentsTab.tsx +8 -7
  73. package/src/views/Chat/ChatMessage.tsx +23 -23
  74. package/src/views/Chat/StepsList.tsx +9 -9
  75. package/src/views/ChatHistory/utils.ts +96 -3
  76. package/src/views/KnowledgeSources.tsx +57 -77
  77. package/src/views/MessageInput/QuickCommandSelector.tsx +39 -93
  78. package/src/views/MessageInput/index.tsx +10 -0
  79. package/src/views/MessageInput/styled.ts +4 -0
  80. package/src/views/Resources.tsx +11 -10
  81. package/src/views/Tools.tsx +1 -1
@@ -1,12 +1,11 @@
1
- import { Box } from '@citric/core'
2
1
  import { Button, Tab } from '@stack-spot/citric-react'
3
2
  import { Placeholder } from '@stack-spot/portal-components/Placeholder'
4
3
  import { aiClient, dataIntegrationClient, workspaceAiClient } from '@stack-spot/portal-network'
5
4
  import { VisibilityLevelEnum } from '@stack-spot/portal-network/api/ai'
5
+ import { KnowledgeSourceItemResponse } from '@stack-spot/portal-network/api/dataIntegration'
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 React, { useCallback, useEffect, useMemo, useRef } from 'react'
9
- import InfiniteScroll from 'react-infinite-scroll-component'
10
9
  import { NavigationComponent } from '../components/ComponentNavigator'
11
10
  import { DescribedCheckboxGroup } from '../components/form/DescribedCheckboxGroup'
12
11
  import { RightPanelContentList } from '../components/RightPanelContentList'
@@ -66,13 +65,19 @@ const KnowledgeSourcesPanel = () => {
66
65
  }, [chat])
67
66
 
68
67
  const allTabsMap: Partial<Record<Scope, Omit<Tab, 'key'>>> = {
69
- favorite: { label: t.favorites,
70
- content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} /> },
71
- personal: { label: t.personal,
72
- content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} /> },
68
+ favorite: {
69
+ label: t.favorites,
70
+ content: <KnowledgeSourcesTab key="favorite" visibility="favorite" allKS={allKS} onSubmit={onSubmit} />,
71
+ },
72
+ personal: {
73
+ label: t.personal,
74
+ content: <KnowledgeSourcesTab key="personal" visibility="personal" allKS={allKS} onSubmit={onSubmit} />,
75
+ },
73
76
  shared: { label: t.shared, content: <KnowledgeSourcesTab key="shared" visibility="shared" allKS={allKS} onSubmit={onSubmit} /> },
74
- workspace: { label: t.spots,
75
- content: <KnowledgeSourcesTabWorkspace key="workspace" visibility="workspace" allKS={allKS} onSubmit={onSubmit} /> },
77
+ workspace: {
78
+ label: t.spots,
79
+ content: <KnowledgeSourcesTabWorkspace key="workspace" visibility="workspace" allKS={allKS} onSubmit={onSubmit} />,
80
+ },
76
81
  account: { label: t.account, content: <KnowledgeSourcesTab key="account" visibility="account" allKS={allKS} onSubmit={onSubmit} /> },
77
82
  }
78
83
 
@@ -97,47 +102,29 @@ const KnowledgeSourcesPanel = () => {
97
102
 
98
103
  export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId, showSubmitButton = true }: TabProps) => {
99
104
  const t = useTranslate(dictionary)
100
-
101
- let knowledgeSources: any[] = []
102
- let fetchNextPage: (() => void) | undefined
103
- let hasNextPage: boolean | undefined
104
- let isLoadingKnowledgeSources: boolean | undefined
105
- let ksListError: any
106
-
107
- if (workspaceId) {
108
- knowledgeSources = workspaceAiClient.getKSFromWorkspaceAi.useQuery({ workspaceId })
109
- fetchNextPage = undefined
110
- hasNextPage = false
111
- isLoadingKnowledgeSources = false
112
- ksListError = undefined
113
- } else {
114
- const [
115
- ksListPages,
116
- _isLoadingKnowledgeSources,
117
- _ksListError,
118
- { fetchNextPage: _fetchNextPage, hasNextPage: _hasNextPage },
119
- ] = dataIntegrationClient.knowledgeSourcesV2.useStatefulInfiniteQuery(
120
- {
121
- order: 'a-to-z',
122
- visibilityList: visibility ? [visibility] : [],
123
- types: ['snippet', 'api', 'event', 'custom'],
124
- size: 20,
125
- },
126
- { enabled: true },
127
- )
128
- knowledgeSources = ksListPages?.flat() ?? []
129
- fetchNextPage = _fetchNextPage
130
- hasNextPage = _hasNextPage
131
- isLoadingKnowledgeSources = _isLoadingKnowledgeSources
132
- ksListError = _ksListError
133
- }
105
+ const [data = []] = workspaceAiClient.getKSFromWorkspaceAi.useStatefulQuery(
106
+ { workspaceId: workspaceId! }, { enabled: !!workspaceId })
107
+ const workspaceKs = data as KnowledgeSourceItemResponse[]
108
+
109
+ const [ksListPages = [], { fetchNextPage, hasNextPage }] = dataIntegrationClient.knowledgeSourcesV2.useInfiniteQuery(
110
+ {
111
+ order: 'a-to-z',
112
+ visibilityList: visibility ? [visibility] : [],
113
+ types: ['snippet', 'api', 'event', 'custom'],
114
+ size: 20,
115
+ page: 1,
116
+ },
117
+ { enabled: !workspaceId },
118
+ )
119
+ const knowledgeSources = workspaceId ? workspaceKs : ksListPages
134
120
 
135
121
  const initialValue = useMemo(() => {
136
122
  const currentlySelected = allKS.current?.map(ks => ks.id)
137
- return knowledgeSources.filter(ks => currentlySelected?.includes(ks.slug))
138
- }, [knowledgeSources, allKS])
123
+ return knowledgeSources?.filter((ks: KnowledgeSourceItemResponse) => currentlySelected?.includes(ks.slug))
124
+ }, [knowledgeSources])
125
+
126
+ const listFavorites = dataIntegrationClient.knowledgeSourcesV2.useQuery({ visibilityList: ['favorite'] })?.items
139
127
 
140
- const listFavorites = dataIntegrationClient.knowledgeSources.useQuery({ visibility: 'favorite' })
141
128
  const [addFavorite, pendingAddFav] = dataIntegrationClient.addFavoriteKnowledgeSource.useMutation()
142
129
  const [removeFavorite, pendingRemoveFav] = dataIntegrationClient.removeFavoriteKnowledgeSource.useMutation()
143
130
 
@@ -145,7 +132,7 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
145
132
  try {
146
133
  await removeFavorite({ slug: idOrSlug })
147
134
  await aiClient.knowledgeSources.invalidate()
148
- await dataIntegrationClient.knowledgeSources.invalidate()
135
+ await dataIntegrationClient.knowledgeSourcesV2.invalidate()
149
136
  } catch (error) {
150
137
  // eslint-disable-next-line no-console
151
138
  console.error(error)
@@ -156,7 +143,7 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
156
143
  const onRemoveFavorite = (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
157
144
  try {
158
145
  await removeFavoriteKs(idOrSlug)
159
- if (!pendingRemoveFav){
146
+ if (!pendingRemoveFav) {
160
147
  resolve(true)
161
148
  }
162
149
 
@@ -164,17 +151,14 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
164
151
  // eslint-disable-next-line no-console
165
152
  console.error(error)
166
153
  reject(error)
167
- }
154
+ }
168
155
  })
169
156
 
170
- if (isLoadingKnowledgeSources && knowledgeSources.length === 0) {return null}
171
- if (ksListError) {return null}
172
-
173
157
  const addFavoriteKs = async (idOrSlug: string) => {
174
158
  try {
175
159
  await addFavorite({ slug: idOrSlug })
176
160
  await aiClient.knowledgeSources.invalidate()
177
- await dataIntegrationClient.knowledgeSources.invalidate()
161
+ await dataIntegrationClient.knowledgeSourcesV2.invalidate()
178
162
  } catch (error) {
179
163
  // eslint-disable-next-line no-console
180
164
  console.error(error)
@@ -185,7 +169,7 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
185
169
  const onAddFavorite = (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
186
170
  try {
187
171
  await addFavoriteKs(idOrSlug)
188
- if (!pendingAddFav){
172
+ if (!pendingAddFav) {
189
173
  resolve(true)
190
174
  }
191
175
 
@@ -193,31 +177,27 @@ export const KnowledgeSourcesTab = ({ visibility, allKS, onSubmit, workspaceId,
193
177
  // eslint-disable-next-line no-console
194
178
  console.error(error)
195
179
  reject(error)
196
- }
180
+ }
197
181
  })
198
182
 
199
- return (
200
- <>
201
- <Box className="content" id="ks-scrollable" style={{ overflow: 'auto', height: '100%' }}>
202
- <InfiniteScroll
203
- scrollableTarget="ks-scrollable"
204
- dataLength={knowledgeSources.length}
205
- next={fetchNextPage ?? (() => {})}
206
- hasMore={hasNextPage}
207
- loader={isLoadingKnowledgeSources}
208
- >
209
- <DescribedCheckboxGroup
210
- options={knowledgeSources}
211
- initialValue={initialValue}
212
- globalSelection={allKS}
213
- data={ks => ({ idOrSlug: ks.slug, description: ks.description, name: ks.name, listFavorites, onAddFavorite, onRemoveFavorite })}
214
- emptyResults={<Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}
215
- emptyDataset={<Placeholder title={t.noData} description={t.noDataDescription} className="no-data-placeholder" />}
216
- />
217
- </InfiniteScroll>
218
- </Box>
219
- {!!knowledgeSources.length && showSubmitButton && <Button onClick={onSubmit}>{t.apply}</Button>}
220
- </>
183
+ return (<>
184
+ <div className="content">
185
+ <DescribedCheckboxGroup
186
+ options={knowledgeSources}
187
+ initialValue={initialValue}
188
+ globalSelection={allKS}
189
+ hasNextPage={hasNextPage && !workspaceId}
190
+ fetchNextPage={fetchNextPage}
191
+ data={(ks: KnowledgeSourceItemResponse) => ({
192
+ idOrSlug: ks.slug, description: ks.description, name: ks.name, listFavorites,
193
+ onAddFavorite, onRemoveFavorite,
194
+ })}
195
+ emptyResults={<Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}
196
+ emptyDataset={<Placeholder title={t.noData} description={t.noDataDescription} className="no-data-placeholder" />}
197
+ />
198
+ </div>
199
+ {!!knowledgeSources?.length && showSubmitButton && <Button onClick={onSubmit}>{t.apply}</Button>}
200
+ </>
221
201
  )
222
202
  }
223
203
 
@@ -228,7 +208,7 @@ export function KnowledgeSourcesTabWorkspace({ allKS, onSubmit }: TabProps) {
228
208
  component: 'ks',
229
209
  props: { visibility: 'workspace', workspaceId: workspace.id, allKS, onSubmit },
230
210
  })
231
-
211
+
232
212
  return <WorkspaceTabNavigator components={workspaceTabComponents} getNavigateParam={buildNavigateParams} />
233
213
  }
234
214
 
@@ -1,9 +1,7 @@
1
- import { Box } from '@citric/core'
2
1
  import { Icon } from '@stack-spot/citric-icons'
3
2
  import { aiClient, workspaceAiClient } from '@stack-spot/portal-network'
4
3
  import { QuickCommandResponse } from '@stack-spot/portal-network/api/ai'
5
- import { useCallback, useMemo } from 'react'
6
- import InfiniteScroll from 'react-infinite-scroll-component'
4
+ import { useCallback } from 'react'
7
5
  import { Selector } from '../../components/Selector'
8
6
  import { useCurrentChat, useCurrentChatState, useWidgetState } from '../../context/hooks'
9
7
  import { quickCommandRegex } from '../../regex'
@@ -79,103 +77,51 @@ export const QuickCommandSelector = ({ inputRef, isTrial }:
79
77
  inputRef.current.focus()
80
78
  }, [chat, inputRef])
81
79
 
82
- const [quickCommandsPages = [], isLoadingQuickCommands, _qcListError, infiniteQueryResult,
83
- ] = aiClient.allQuickCommandsV3.useStatefulInfiniteQuery(
84
- {
85
- size: 20,
86
- page: 1,
87
- order: 'a-to-z',
88
- },
89
- { enabled: !spotId },
90
- )
91
-
92
- const quickCommands = useMemo(() => quickCommandsPages, [quickCommandsPages])
93
-
94
- const quickCommandsFiltered = useMemo(() => quickCommands.filter(
95
- (qc) => qc.visibility_level.toLowerCase() !== 'workspace'), [quickCommands],
96
- )
97
-
98
- const workspaceQuickCommands = workspaceAiClient.workspacesContentsByType.useQuery({
99
- contentType: 'quick_command',
100
- }) || []
101
-
102
- const workspaceQuickCommandsWithWorkspaceName: QuickCommandResponseWithSpaceName[] =
103
- useMemo(
104
- () =>
105
- workspaceQuickCommands.flatMap(({ qcs, space_name }) =>
106
- qcs?.map((qc) => ({ ...qc, spaceName: space_name })),
107
- ) as QuickCommandResponseWithSpaceName[],
108
- [workspaceQuickCommands],
80
+ const getQuickCommands = () => {
81
+ if (spotId) {
82
+ return workspaceAiClient.getQCFromWorkspaceAi.useQuery({ workspaceId: spotId })
83
+ }
84
+
85
+ const quickCommands = aiClient.allQuickCommands.useQuery({ order: 'a-to-z' })
86
+ const quickCommandsFiltered = quickCommands.filter(
87
+ (qc) => qc.visibility_level.toLowerCase() !== 'workspace',
109
88
  )
110
89
 
111
- const allQuickCommands =
112
- spotId
113
- ? workspaceAiClient.getQCFromWorkspaceAi.useQuery({ workspaceId: spotId }) || []
114
- : [...quickCommandsFiltered, ...workspaceQuickCommandsWithWorkspaceName]
90
+ const workspaceQuickCommands = workspaceAiClient.workspacesContentsByType.useQuery({
91
+ contentType: 'quick_command',
92
+ })
115
93
 
116
- const fetchNextPage = !spotId ? infiniteQueryResult?.fetchNextPage : undefined
117
- const hasNextPage = !spotId ? infiniteQueryResult?.hasNextPage : false
94
+ const workspaceQuickCommandsWithWorkspaceName: QuickCommandResponseWithSpaceName[] =
95
+ workspaceQuickCommands.flatMap(({ qcs, space_name }) =>
96
+ qcs?.map((qc) => ({ ...qc, spaceName: space_name })),
97
+ ) as QuickCommandResponseWithSpaceName[]
118
98
 
119
- const QuickCommandItem = ({ slug, description, spaceName }: QuickCommandResponseWithSpaceName) => (<>
99
+ return [...quickCommandsFiltered, ...workspaceQuickCommandsWithWorkspaceName]
100
+ }
101
+
102
+ const QuickCommandItem = ({ slug, description, spaceName }: QuickCommandResponseWithSpaceName) => <>
120
103
  <p className="selector-description">{spaceName}</p>
121
104
  <p className="selector-title">/{slug?.toUpperCase()}</p>
122
105
  <p className="selector-description">{description}</p>
123
106
  </>
124
- )
125
-
126
- if (spotId) {
127
- return (
128
- <Selector
129
- inputRef={inputRef}
130
- favorite={{
131
- useFavorites, onAddFavorite, onRemoveFavorite, favoriteIsSlug: true,
132
- }}
133
- selectorConfig={{
134
- resourceName: 'Quick Command',
135
- shortcut: '/',
136
- icon: <Icon icon="QuickCommand" />,
137
- searchProp: 'slug',
138
- urlBuilder: (qc) => `/quick-command/${qc?.slug}`,
139
- regex: quickCommandRegex,
140
- sections: isTrial ? ['favorite', 'personal'] : ['favorite', 'personal', 'workspace', 'account', 'shared'],
141
- isEnabled: isQuickCommandEnabled,
142
- onSelect: onSelectItem,
143
- renderComponentItem: QuickCommandItem,
144
- useData: () => allQuickCommands,
145
- }}
146
- />
147
- )
148
- }
149
107
 
150
- return (
151
- <Box className="content" id="qc-scrollable" style={{ overflow: 'auto', height: '100%' }}>
152
- <InfiniteScroll
153
- scrollableTarget="qc-scrollable"
154
- dataLength={allQuickCommands.length}
155
- next={fetchNextPage as any}
156
- hasMore={!!hasNextPage}
157
- loader={isLoadingQuickCommands}
158
- >
159
- <Selector
160
- inputRef={inputRef}
161
- favorite={{
162
- useFavorites, onAddFavorite, onRemoveFavorite, favoriteIsSlug: true,
163
- }}
164
- selectorConfig={{
165
- resourceName: 'Quick Command',
166
- shortcut: '/',
167
- icon: <Icon icon="QuickCommand" />,
168
- searchProp: 'slug',
169
- urlBuilder: (qc) => `/quick-command/${qc?.slug}`,
170
- regex: quickCommandRegex,
171
- sections: isTrial ? ['favorite', 'personal'] : ['favorite', 'personal', 'workspace', 'account', 'shared'],
172
- isEnabled: isQuickCommandEnabled,
173
- onSelect: onSelectItem,
174
- renderComponentItem: QuickCommandItem,
175
- useData: () => allQuickCommands,
176
- }}
177
- />
178
- </InfiniteScroll>
179
- </Box>
180
- )
108
+ return <Selector
109
+ inputRef={inputRef}
110
+ favorite={{
111
+ useFavorites, onAddFavorite, onRemoveFavorite, favoriteIsSlug: true,
112
+ }}
113
+ selectorConfig={{
114
+ resourceName: 'Quick Command',
115
+ shortcut: '/',
116
+ icon: <Icon icon="QuickCommand" />,
117
+ searchProp: 'slug',
118
+ urlBuilder: (qc) => `/quick-command/${qc?.slug}`,
119
+ regex: quickCommandRegex,
120
+ sections: isTrial ? ['favorite', 'personal'] : ['favorite', 'personal', 'workspace', 'account', 'shared'],
121
+ isEnabled: isQuickCommandEnabled,
122
+ onSelect: onSelectItem,
123
+ renderComponentItem: QuickCommandItem,
124
+ useData: getQuickCommands,
125
+ }}
126
+ />
181
127
  }
@@ -39,6 +39,7 @@ export const MessageInput = ({ chatWindowRef, customInputMessage }:
39
39
  const { handleKeyDown, handleKeyUp } = useUserEntryHistoryShortcut()
40
40
  const isTrial = checkIsTrial()
41
41
  const { isDragging, handleDrop, handleDragLeave } = useUploadDragDrop()
42
+ const [disabled, setDisabled] = useState(false)
42
43
 
43
44
  usePasteUpload({
44
45
  textAreaRef,
@@ -88,6 +89,14 @@ export const MessageInput = ({ chatWindowRef, customInputMessage }:
88
89
  }, [chat, t])
89
90
 
90
91
  const onSend = useCallback(async () => {
92
+ if (disabled) return
93
+
94
+ if (chat.get('isLoading')) {
95
+ setDisabled(true)
96
+ await chat.whenLoadingEnds()
97
+ setDisabled(false)
98
+ }
99
+
91
100
  const message = chat.get('nextMessage')
92
101
  const code = chat.get('codeSelection')
93
102
  const language = chat.get('codeLanguage')
@@ -162,6 +171,7 @@ export const MessageInput = ({ chatWindowRef, customInputMessage }:
162
171
  onKeyUp={handleKeyUp}
163
172
  onIncreaseSize={() => setExpanded(false)}
164
173
  onResetSize={() => !expansionLocked.current && setExpanded(true)}
174
+ disabled={disabled}
165
175
  />
166
176
  </div>
167
177
  </div>
@@ -301,6 +301,10 @@ export const MessageInputBox = styled.div`
301
301
  &:focus {
302
302
  box-shadow: none !important;
303
303
  }
304
+ &:disabled {
305
+ background-color: transparent !important;
306
+ opacity: 0.6;
307
+ }
304
308
  }
305
309
  `
306
310
 
@@ -46,29 +46,30 @@ const ResourcesPanel = () => {
46
46
  const chat = widget.chatTabs.getAll().find(c => c.id === chatId)
47
47
  return chat?.getMessages().find(m => m.id === messageId)?.getValue()
48
48
  }, [messageId])
49
- const [toolKits] = agentToolsClient.tools.useStatefulQuery({}, { enabled: !!message?.agent?.id })
50
49
  const [agent] = agentToolsClient.agent.useStatefulQuery({ agentId: message?.agent?.id || '' },
51
50
  { enabled: !!message?.agent?.id })
52
- const tools = useMemo(() => message?.tools?.map(id => toolById(id, toolKits)), [messageId, toolKits])
53
- const customTools = useMemo(() => message?.tools?.map(id => toolById(id, agent?.toolkits?.custom_toolkits)),
54
- [messageId, agent?.toolkits?.custom_toolkits])
55
-
51
+ const toolkits = useMemo(() => [
52
+ ...agent?.toolkits?.builtin_toolkits ?? [],
53
+ ...agent?.toolkits?.custom_toolkits ?? [],
54
+ ...agent?.toolkits?.mcp_toolkits ?? [],
55
+ ], [agent])
56
+ const tools = useMemo(() => message?.tools?.map(id => toolById(id, toolkits)), [messageId, toolkits])
56
57
  const [agentsTools] = agentToolsClient.agentsByIds.useStatefulQuery({ searchAgentsRequest:{ ids: message?.tools || [] } })
57
- const hasAgentTool = useMemo(() => message?.tools?.some(id => agentsTools?.find((agent) => agent.id === id)), [messageId, toolKits])
58
+ const hasAgentTool = useMemo(() => message?.tools?.some(id => agentsTools?.find((agent) => agent.id === id)), [messageId])
58
59
 
59
60
  const header = (image?: string, label?: string) => (
60
61
  <Row>
61
- <ImageBox >
62
+ <ImageBox>
62
63
  <ImageWithFallback src={image} fallback={<Icon icon="Agent" />} aria-label={label} title={label} />
63
64
  </ImageBox>
64
65
  <Text style={{ marginLeft: '8px' }}>{label}</Text>
65
66
  </Row>
66
67
  )
67
-
68
- return !!(tools?.length || customTools?.length) && (
68
+
69
+ return !!tools?.length && (
69
70
  <>
70
71
  <>
71
- {[...(tools || []), ...(customTools || [])].map(
72
+ {tools.map(
72
73
  (tool) =>
73
74
  tool && (
74
75
  <StyledAccordion key={tool.id} header={header(tool?.image, tool?.name)} appearance="card" maxHeight={120}>
@@ -54,7 +54,7 @@ const ToolsPanel = () => {
54
54
  tool && (
55
55
  <li key={tool.id}>
56
56
  <ToolBadge
57
- name={tool.name || tool.id}
57
+ name={tool.name || tool.id || ''}
58
58
  image={tool.image ?? ''}
59
59
  description={tool.description ?? ''}
60
60
  />