@stack-spot/ai-chat-widget 2.3.2 → 2.4.1
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.
- package/CHANGELOG.md +15 -0
- package/dist/app-metadata.json +6 -6
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +8 -3
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.js +3 -3
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/state/ChatState.d.ts +4 -0
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js.map +1 -1
- package/dist/utils/chat.d.ts.map +1 -1
- package/dist/utils/chat.js +1 -0
- package/dist/utils/chat.js.map +1 -1
- package/dist/utils/knowledge-source.d.ts +2 -2
- package/dist/views/Agents/styled.d.ts.map +1 -1
- package/dist/views/Agents/styled.js +1 -2
- package/dist/views/Agents/styled.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +7 -4
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.js +2 -2
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/Chat/styled.d.ts.map +1 -1
- package/dist/views/Chat/styled.js +4 -8
- package/dist/views/Chat/styled.js.map +1 -1
- package/dist/views/MessageInput/ButtonBar.d.ts.map +1 -1
- package/dist/views/MessageInput/ButtonBar.js +2 -1
- package/dist/views/MessageInput/ButtonBar.js.map +1 -1
- package/dist/views/MessageInput/ModelSwitcher/index.d.ts +2 -0
- package/dist/views/MessageInput/ModelSwitcher/index.d.ts.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/index.js +25 -0
- package/dist/views/MessageInput/ModelSwitcher/index.js.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.d.ts +30 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.d.ts.map +1 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.js +91 -0
- package/dist/views/MessageInput/ModelSwitcher/utils.js.map +1 -0
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
- package/dist/views/MessageInput/dictionary.js +6 -0
- package/dist/views/MessageInput/dictionary.js.map +1 -1
- package/dist/views/MessageInput/styled.d.ts +12 -0
- package/dist/views/MessageInput/styled.d.ts.map +1 -1
- package/dist/views/MessageInput/styled.js +35 -0
- package/dist/views/MessageInput/styled.js.map +1 -1
- package/dist/views/Resources.js +12 -5
- package/dist/views/Resources.js.map +1 -1
- package/package.json +4 -4
- package/src/app-metadata.json +6 -6
- package/src/chat-interceptors/quick-commands.ts +10 -3
- package/src/chat-interceptors/send-message.ts +8 -8
- package/src/state/ChatState.ts +4 -0
- package/src/utils/chat.ts +1 -0
- package/src/utils/knowledge-source.ts +2 -2
- package/src/views/Agents/styled.ts +1 -2
- package/src/views/Chat/ChatMessage.tsx +50 -60
- package/src/views/Chat/StepsList.tsx +5 -5
- package/src/views/Chat/styled.ts +4 -8
- package/src/views/MessageInput/ButtonBar.tsx +2 -0
- package/src/views/MessageInput/ModelSwitcher/index.tsx +68 -0
- package/src/views/MessageInput/ModelSwitcher/utils.tsx +143 -0
- package/src/views/MessageInput/dictionary.ts +6 -0
- package/src/views/MessageInput/styled.ts +37 -0
- package/src/views/Resources.tsx +18 -9
|
@@ -69,12 +69,12 @@ interface Props extends CustomMessage {
|
|
|
69
69
|
isLast: boolean,
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
interface RenderInputsEntryProps {
|
|
73
|
-
isLast: boolean,
|
|
74
|
-
entry: TextChatEntry,
|
|
75
|
-
value: string[],
|
|
72
|
+
interface RenderInputsEntryProps {
|
|
73
|
+
isLast: boolean,
|
|
74
|
+
entry: TextChatEntry,
|
|
75
|
+
value: string[],
|
|
76
76
|
setValue: Dispatch<React.SetStateAction<string[]>>,
|
|
77
|
-
labels: string[],
|
|
77
|
+
labels: string[],
|
|
78
78
|
setLabels: Dispatch<React.SetStateAction<string[]>>,
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -113,7 +113,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
113
113
|
<Input name={entry.name} onChange={v => setValue([v])} required style={{ height: '30px', width: '33%' }} />}
|
|
114
114
|
</Row>
|
|
115
115
|
)}
|
|
116
|
-
/>
|
|
116
|
+
/>
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (entry.type === 'button-list') {
|
|
@@ -145,7 +145,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
145
145
|
renderLabel={o => (
|
|
146
146
|
<Row gap={3}>
|
|
147
147
|
<Text>{o.label}</Text>
|
|
148
|
-
{o.hasInput && o.value && labels.findIndex((label) => label === o.value)!== -1 &&
|
|
148
|
+
{o.hasInput && o.value && labels.findIndex((label) => label === o.value) !== -1 &&
|
|
149
149
|
<div style={{ width: '33%' }}>
|
|
150
150
|
<Input
|
|
151
151
|
name={entry.name}
|
|
@@ -158,7 +158,7 @@ const RenderInputsEntry = ({ isLast, entry, value, setValue, labels, setLabels }
|
|
|
158
158
|
const newValue = [...value]
|
|
159
159
|
newValue[customIndex] = v
|
|
160
160
|
setValue(newValue)
|
|
161
|
-
}
|
|
161
|
+
}
|
|
162
162
|
}}
|
|
163
163
|
required={true}
|
|
164
164
|
style={{ height: '30px' }}
|
|
@@ -219,7 +219,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
219
219
|
const [copied, setCopied] = useState(false)
|
|
220
220
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
221
|
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
// when we have a steps but we are not showing any content of the step
|
|
224
224
|
// (because it is a tool and the user has already answered the question)
|
|
225
225
|
// we do not want to show an avatar with empty content, so we hide the entire message
|
|
@@ -227,12 +227,12 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
227
227
|
const messages = useChatMessages(chat.id)
|
|
228
228
|
const userHasAlreadyAnswered = useMemo(() => {
|
|
229
229
|
const messageIndex = messages.findIndex((messageItem) => messageItem.id === message.id)
|
|
230
|
-
if (messages.length-1 === messageIndex) return false
|
|
231
|
-
const nextMessage = messages[messageIndex+1].getValue()
|
|
230
|
+
if (messages.length - 1 === messageIndex) return false
|
|
231
|
+
const nextMessage = messages[messageIndex + 1].getValue()
|
|
232
232
|
return nextMessage.agentType === 'user'
|
|
233
233
|
}, [messages, messages.length])
|
|
234
234
|
const isMessageHidden = toolsStep && userHasAlreadyAnswered
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
useChatScrollToBottomEffect(ref, [entry])
|
|
237
237
|
useMidnightUpdateView()
|
|
238
238
|
|
|
@@ -277,7 +277,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const renderActions = useCallback(()=> <> {entry.actions?.length && (
|
|
280
|
+
const renderActions = useCallback(() => <> {entry.actions?.length && (
|
|
281
281
|
<div className="actions">
|
|
282
282
|
{entry.actions.map(
|
|
283
283
|
(a, index) => (<>
|
|
@@ -357,7 +357,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
357
357
|
widget.set('panel', 'resources')
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
return (entry.content || entry.error || !!entry.steps?.length
|
|
360
|
+
return (entry.content || entry.error || !!entry.steps?.length ||
|
|
361
361
|
entry.upload?.length) && (!isMessageHidden || !toolsStep || isPlanning) && (
|
|
362
362
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
363
363
|
<div className="chat-message-container"
|
|
@@ -371,33 +371,19 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
371
371
|
{!!entry.badges?.length && <div className="badges">
|
|
372
372
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
373
373
|
</div>}
|
|
374
|
-
|
|
375
|
-
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
374
|
+
|
|
375
|
+
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
376
376
|
userHasAlreadyAnswered={userHasAlreadyAnswered} />}
|
|
377
377
|
|
|
378
378
|
{renderContent()}
|
|
379
|
-
|
|
379
|
+
|
|
380
380
|
</div>
|
|
381
381
|
)}
|
|
382
|
-
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />
|
|
383
|
-
|
|
382
|
+
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder />}
|
|
383
|
+
|
|
384
384
|
{entry.error && <Alert type="error">{entry.error}</Alert>}
|
|
385
385
|
</div>
|
|
386
386
|
{afterMessage && createElement(afterMessage, { message })}
|
|
387
|
-
{/* {!!entry.tools?.length && <StackedBadge
|
|
388
|
-
aria-label={t.openToolsPanel}
|
|
389
|
-
title={t.openToolsPanel}
|
|
390
|
-
tabIndex={0}
|
|
391
|
-
role="button"
|
|
392
|
-
className="tools-badge"
|
|
393
|
-
label={t.tools}
|
|
394
|
-
images={entry.tools.slice(0, 3).map((id) => {
|
|
395
|
-
const tool = toolById(id, toolKits)
|
|
396
|
-
return { key: id, name: tool?.name || id, icon: <Icon icon="Cog" />, url: tool?.image }
|
|
397
|
-
})}
|
|
398
|
-
onClick={openToolsPanel}
|
|
399
|
-
style={{ marginTop: '12px', width: 'fit-content' }}
|
|
400
|
-
/>} */}
|
|
401
387
|
|
|
402
388
|
{!!entry.knowledgeSources?.length && <div className="ks-box">
|
|
403
389
|
<Text appearance="microtext1" color="light.700">Knowledge Sources:</Text>
|
|
@@ -408,36 +394,40 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
408
394
|
))}</ul>
|
|
409
395
|
</div>}
|
|
410
396
|
|
|
411
|
-
{(!!agentsTools?.length || !!entry.tools?.length) &&
|
|
412
|
-
<
|
|
413
|
-
{
|
|
414
|
-
|
|
415
|
-
<
|
|
416
|
-
src={agent.avatar ?? undefined}
|
|
417
|
-
fallback={<Icon icon="Agent" />}
|
|
418
|
-
alt={agent.name}
|
|
419
|
-
aria-label={agent.name}
|
|
420
|
-
title={agent.name}
|
|
421
|
-
/>
|
|
422
|
-
</ImageBox>
|
|
423
|
-
))}
|
|
424
|
-
{entry.tools?.map((id) => {
|
|
425
|
-
const tool = toolById(id, toolKits)
|
|
426
|
-
return (
|
|
427
|
-
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
397
|
+
{(!!agentsTools?.length || !!entry.tools?.length) &&
|
|
398
|
+
<div className="tools-box">
|
|
399
|
+
<Button appearance="none" onClick={openResourcesPanel} aria-label={t.openResourcesPanel}>
|
|
400
|
+
{agentsTools?.map((agent) => (
|
|
401
|
+
<ImageBox key={agent.id} className="agent-info-avatar-resource">
|
|
428
402
|
<ImageWithFallback
|
|
429
|
-
src={
|
|
430
|
-
fallback={<Icon icon="
|
|
431
|
-
alt={
|
|
432
|
-
aria-label={
|
|
433
|
-
title={
|
|
403
|
+
src={agent.avatar ?? undefined}
|
|
404
|
+
fallback={<Icon icon="Agent" />}
|
|
405
|
+
alt={agent.name}
|
|
406
|
+
aria-label={agent.name}
|
|
407
|
+
title={agent.name}
|
|
434
408
|
/>
|
|
435
409
|
</ImageBox>
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
410
|
+
))}
|
|
411
|
+
{entry?.tools?.map((id) => {
|
|
412
|
+
if (agentsTools?.some((agent) => agent.id === id)) return null
|
|
413
|
+
const tool = toolById(id, toolKits)
|
|
414
|
+
return (
|
|
415
|
+
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
416
|
+
<ImageWithFallback
|
|
417
|
+
src={tool?.image}
|
|
418
|
+
fallback={<Icon icon="Cog" />}
|
|
419
|
+
alt={tool?.name}
|
|
420
|
+
aria-label={tool?.name}
|
|
421
|
+
title={tool?.name}
|
|
422
|
+
/>
|
|
423
|
+
</ImageBox>
|
|
424
|
+
)
|
|
425
|
+
})}
|
|
426
|
+
</Button>
|
|
427
|
+
<ViewToolsDetails chatId={chat.id} />
|
|
428
|
+
</div>
|
|
429
|
+
}
|
|
430
|
+
|
|
441
431
|
{shouldShowFooter && <div className="message-footer">
|
|
442
432
|
{entry.agentType === 'bot' && !entry.error && <div className="message-actions">
|
|
443
433
|
{entry.type === 'md' && (
|
|
@@ -271,8 +271,8 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
271
271
|
const isLastStepDone = actualSteps[actualSteps.length - 1]?.status !== 'running' &&
|
|
272
272
|
actualSteps[actualSteps.length - 1]?.status !== 'pending'
|
|
273
273
|
const totalTools = useMemo(() => actualSteps?.reduce((sum, step) => {
|
|
274
|
-
const firstAttempt = step
|
|
275
|
-
const toolsCount = firstAttempt
|
|
274
|
+
const firstAttempt = step?.attempts && step.attempts[0]
|
|
275
|
+
const toolsCount = firstAttempt?.tools?.length ?? 0
|
|
276
276
|
return sum + toolsCount
|
|
277
277
|
}, 0), [steps])
|
|
278
278
|
|
|
@@ -302,7 +302,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
302
302
|
<Column gap="8px" mt="8px">
|
|
303
303
|
<Divider colorScheme="light" />
|
|
304
304
|
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
305
|
-
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' &&
|
|
305
|
+
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
|
|
306
306
|
</Column>
|
|
307
307
|
|
|
308
308
|
{userHasAlreadyAnswered && planning?.[0]?.status === 'success' && <ViewToolsDetails chatId={chatId} />}
|
|
@@ -310,7 +310,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
310
310
|
</div>
|
|
311
311
|
</AnimatedHeight> : null}
|
|
312
312
|
|
|
313
|
-
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
313
|
+
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
314
314
|
<ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
315
315
|
</>
|
|
316
316
|
)
|
|
@@ -331,7 +331,7 @@ export const ViewToolsDetails = ({ chatId }: { chatId: string }) => {
|
|
|
331
331
|
|
|
332
332
|
function openToolsPanel() {
|
|
333
333
|
const messageId = getPlanningMessageId()
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
if (messageId) {
|
|
336
336
|
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
337
337
|
widget.set('panel', 'steps')
|
package/src/views/Chat/styled.ts
CHANGED
|
@@ -220,7 +220,7 @@ export const ChatList: IStyledComponentBase<
|
|
|
220
220
|
|
|
221
221
|
.tools-box {
|
|
222
222
|
|
|
223
|
-
>
|
|
223
|
+
> button {
|
|
224
224
|
display: flex;
|
|
225
225
|
flex-direction: row;
|
|
226
226
|
flex-wrap: wrap;
|
|
@@ -229,15 +229,11 @@ export const ChatList: IStyledComponentBase<
|
|
|
229
229
|
margin-top: 8px;
|
|
230
230
|
padding: 0;
|
|
231
231
|
list-style: none;
|
|
232
|
-
gap:
|
|
232
|
+
gap: 0px;
|
|
233
233
|
|
|
234
234
|
&:hover{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.agent-info-avatar-resource {
|
|
238
|
-
transition: margin-left 0.3s ease-in;
|
|
239
|
-
margin-left: 0px;
|
|
240
|
-
}
|
|
235
|
+
transition: gap 0.3s ease-in;
|
|
236
|
+
gap: 6px;
|
|
241
237
|
}
|
|
242
238
|
}
|
|
243
239
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IconButton, Row } from '@stack-spot/citric-react'
|
|
2
2
|
import { useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
|
|
3
3
|
import { useMessageInputDictionary } from './dictionary'
|
|
4
|
+
import { ModelSwitcher } from './ModelSwitcher'
|
|
4
5
|
import { SelectContent } from './SelectContent'
|
|
5
6
|
import { SelectionBarWrapper } from './styled'
|
|
6
7
|
|
|
@@ -29,6 +30,7 @@ export const ButtonBar = ({ onSend, isLoading }: SelectionBarProps) => {
|
|
|
29
30
|
<IconButton icon="Code" appearance="square" aria-label={t.code} title={t.code} onClick={() => widget.set('panel', 'editor')} />
|
|
30
31
|
)}
|
|
31
32
|
</Row>
|
|
33
|
+
<ModelSwitcher />
|
|
32
34
|
{isLoading ? (
|
|
33
35
|
<IconButton
|
|
34
36
|
icon="Stop"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CitricIconOutline, CitricIconSocial } from '@stack-spot/citric-icons'
|
|
2
|
+
import { AsyncContent, Button, Column, FieldGroup, Icon, Input, Row } from '@stack-spot/citric-react'
|
|
3
|
+
import { SelectionList } from '@stack-spot/portal-components/SelectionList'
|
|
4
|
+
import { agentToolsClient, genAiInferenceClient } from '@stack-spot/portal-network'
|
|
5
|
+
import { useMemo, useState } from 'react'
|
|
6
|
+
import { CSSProperties } from 'styled-components'
|
|
7
|
+
import { useCurrentChat, useCurrentChatState } from '../../../context/hooks'
|
|
8
|
+
import { useMessageInputDictionary } from '../dictionary'
|
|
9
|
+
import { RowWrapperStyled, stylesModelSwitcher } from '../styled'
|
|
10
|
+
import { getListModelsData, handleFilterTypeModel, providerIcon } from './utils'
|
|
11
|
+
|
|
12
|
+
export const ModelSwitcher = () => {
|
|
13
|
+
const t = useMessageInputDictionary()
|
|
14
|
+
const agentCurrentChat = useCurrentChatState('agent')
|
|
15
|
+
const chat = useCurrentChat()
|
|
16
|
+
const [filter, setFilter] = useState('')
|
|
17
|
+
const [visibleMenu, setVisibleMenu] = useState(false)
|
|
18
|
+
const [agentData, isLoadingAgentData] = agentToolsClient.agent.useStatefulQuery({ agentId: agentCurrentChat?.id || '' })
|
|
19
|
+
const [models, isLoadingModels] =
|
|
20
|
+
genAiInferenceClient.listModels.useStatefulQuery({ pageSize: 999, active: true })
|
|
21
|
+
|
|
22
|
+
const { modelName, modelProviderType, listItemsData } =
|
|
23
|
+
getListModelsData(chat, setVisibleMenu, agentData, models)
|
|
24
|
+
|
|
25
|
+
const data = useMemo(() => {
|
|
26
|
+
const items = listItemsData ?? []
|
|
27
|
+
return handleFilterTypeModel(items, t, filter)
|
|
28
|
+
}, [agentCurrentChat?.id, listItemsData, filter])
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<RowWrapperStyled>
|
|
32
|
+
<AsyncContent loading={isLoadingAgentData && isLoadingModels}>
|
|
33
|
+
<Button
|
|
34
|
+
className="button-select-model"
|
|
35
|
+
colorScheme="light"
|
|
36
|
+
size="sm"
|
|
37
|
+
aria-label={t.agent}
|
|
38
|
+
title={t.agent}
|
|
39
|
+
onClick={() => setVisibleMenu(state => !state)}
|
|
40
|
+
>
|
|
41
|
+
<Icon
|
|
42
|
+
icon={providerIcon[modelProviderType as CitricIconOutline | CitricIconSocial]}
|
|
43
|
+
group={modelProviderType === 'stackspot' ? 'outline' : 'social'}
|
|
44
|
+
/>
|
|
45
|
+
{modelName}
|
|
46
|
+
<Icon icon="ChevronDown" group="fill" size="sm" />
|
|
47
|
+
</Button>
|
|
48
|
+
</AsyncContent>
|
|
49
|
+
<SelectionList
|
|
50
|
+
id="menuModelSwitcher"
|
|
51
|
+
items={data || []}
|
|
52
|
+
visible={visibleMenu}
|
|
53
|
+
onHide={() => setVisibleMenu(false)}
|
|
54
|
+
showListAsCard
|
|
55
|
+
style={stylesModelSwitcher.selection as CSSProperties}
|
|
56
|
+
before={
|
|
57
|
+
<Column>
|
|
58
|
+
<FieldGroup fullWidth style={{ marginTop: '8px' }}>
|
|
59
|
+
<Icon icon="Search" />
|
|
60
|
+
<Input type="search" value={filter} onChange={(value) => (setFilter(value))} />
|
|
61
|
+
</FieldGroup>
|
|
62
|
+
{!data.length ? <Row m="16px 8px">{t.nothingFound}</Row> : undefined}
|
|
63
|
+
</Column>
|
|
64
|
+
}
|
|
65
|
+
/>
|
|
66
|
+
</RowWrapperStyled>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { CitricIconOutline, CitricIconSocial } from '@stack-spot/citric-icons'
|
|
2
|
+
import { IconBox } from '@stack-spot/citric-react'
|
|
3
|
+
import { AgentModel } from '@stack-spot/portal-network/api/agent-tools'
|
|
4
|
+
import { LlmModelsResponse, PaginatedResponseLlmModelsResponse } from '@stack-spot/portal-network/api/genAiInference'
|
|
5
|
+
import { theme } from '@stack-spot/portal-theme'
|
|
6
|
+
import { Dispatch, ReactElement } from 'react'
|
|
7
|
+
import { ChatState } from '../../../state/ChatState'
|
|
8
|
+
|
|
9
|
+
export interface ItemProps {
|
|
10
|
+
active?: boolean,
|
|
11
|
+
label?: string,
|
|
12
|
+
icon?: ReactElement,
|
|
13
|
+
self_hosted?: boolean,
|
|
14
|
+
onClick?: VoidFunction,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const providerIcon: Record<string, CitricIconSocial | CitricIconOutline> = {
|
|
18
|
+
openai: 'OpenAI',
|
|
19
|
+
bedrock: 'AWSBedrock',
|
|
20
|
+
azure: 'Azure',
|
|
21
|
+
stackspot: 'StackSpot',
|
|
22
|
+
gemini: 'Gemini',
|
|
23
|
+
deepseek: 'DeepSeek',
|
|
24
|
+
anthropic: 'Anthropic',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getListModelsData(
|
|
28
|
+
chat: ChatState,
|
|
29
|
+
setVisibleMenu: Dispatch<React.SetStateAction<boolean>>,
|
|
30
|
+
agent?: AgentModel,
|
|
31
|
+
models?: PaginatedResponseLlmModelsResponse) {
|
|
32
|
+
|
|
33
|
+
const chatSelectedModelId = chat.get('selected_model_id')
|
|
34
|
+
|
|
35
|
+
const listModelsToShow = agent?.visibility_level !== 'built_in' && !!agent?.model_id ?
|
|
36
|
+
models?.items.filter((model) => agent?.available_llm_models?.find((modelAvailable) => modelAvailable.model_id === model.id)) :
|
|
37
|
+
models?.items
|
|
38
|
+
|
|
39
|
+
const modelAvailableDefault = agent?.available_llm_models?.find((model) => (model.is_default || model.model_id === agent.model_id))
|
|
40
|
+
const modelListData = parseModelList(chat, setVisibleMenu, listModelsToShow, modelAvailableDefault?.model_id)
|
|
41
|
+
|
|
42
|
+
if (agent?.visibility_level === 'built_in' || !agent?.model_id) {
|
|
43
|
+
|
|
44
|
+
const modelDefaultProviderType = models?.items.find((modelAccount) =>
|
|
45
|
+
chatSelectedModelId ? modelAccount.id === chatSelectedModelId :
|
|
46
|
+
modelAccount.resources.find((resource) => resource.is_default))?.model_configuration.provider.provider_type
|
|
47
|
+
|
|
48
|
+
const modelDefaultActive = modelListData.find((model) => model?.active)
|
|
49
|
+
|
|
50
|
+
return { modelName: modelDefaultActive?.label, modelProviderType: modelDefaultProviderType, listItemsData: modelListData }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const modelAccount = models?.items.find((modelAccount) => modelAccount.id === (chatSelectedModelId || agent.model_id))
|
|
54
|
+
const modelProviderType = modelAccount?.model_configuration.provider.provider_type
|
|
55
|
+
const modelSelectedName = modelAccount?.display_name || modelAvailableDefault?.model_name || agent?.model_name
|
|
56
|
+
|
|
57
|
+
const listItemsData =
|
|
58
|
+
modelListData && modelListData?.length > 0 ? modelListData :
|
|
59
|
+
[{
|
|
60
|
+
active: true,
|
|
61
|
+
label: modelSelectedName || '',
|
|
62
|
+
icon: <IconBox icon={providerIcon[modelProviderType || 'stackspot']}
|
|
63
|
+
appearance="square"
|
|
64
|
+
group={modelProviderType === 'stackspot' ? 'outline' : 'social'} />,
|
|
65
|
+
self_hosted: modelAccount?.self_hosted || false,
|
|
66
|
+
onClick: () => {
|
|
67
|
+
chat.set('selected_model_id', modelAvailableDefault?.model_id)
|
|
68
|
+
setVisibleMenu(false)
|
|
69
|
+
},
|
|
70
|
+
}]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
return { modelName: modelSelectedName, modelProviderType, listItemsData }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseModelList(
|
|
77
|
+
chat: ChatState,
|
|
78
|
+
setVisibleMenu: Dispatch<React.SetStateAction<boolean>>,
|
|
79
|
+
listModel?: LlmModelsResponse[],
|
|
80
|
+
modelAvailableDefaultId?: string,
|
|
81
|
+
) {
|
|
82
|
+
|
|
83
|
+
const data = Array<ItemProps>()
|
|
84
|
+
const chatModelId = chat.get('selected_model_id')
|
|
85
|
+
|
|
86
|
+
listModel?.forEach((model) => {
|
|
87
|
+
data.push({
|
|
88
|
+
active: chatModelId ? chatModelId === model.id : modelAvailableDefaultId === model.id ||
|
|
89
|
+
model.resources?.some((resource) => resource.is_default && resource.name === 'agents'),
|
|
90
|
+
label: model?.display_name || 'LLM',
|
|
91
|
+
icon: <IconBox
|
|
92
|
+
style={{ backgroundColor: theme.color.light[300] }}
|
|
93
|
+
appearance="square"
|
|
94
|
+
icon={providerIcon[model.model_configuration.provider.provider_type]}
|
|
95
|
+
group={model.model_configuration.provider.provider_type === 'stackspot' ? 'outline' : 'social'}
|
|
96
|
+
/>,
|
|
97
|
+
self_hosted: model.self_hosted,
|
|
98
|
+
onClick: () => {
|
|
99
|
+
chat.set('selected_model_id', model.id)
|
|
100
|
+
setVisibleMenu(false)
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
return data
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function handleFilterTypeModel(items: ItemProps[], t: Record<string, string>, filter?: string) {
|
|
108
|
+
const filterLower = filter?.toLocaleLowerCase()
|
|
109
|
+
const filteredItens = items.filter((item) => filterLower ? item?.label?.toLocaleLowerCase().includes(filterLower) : item)
|
|
110
|
+
|
|
111
|
+
const { selfHosted, hosted } = filteredItens.reduce(
|
|
112
|
+
(acc, model) => {
|
|
113
|
+
if (model?.self_hosted) {
|
|
114
|
+
acc.selfHosted.push(model)
|
|
115
|
+
} else {
|
|
116
|
+
acc.hosted.push(model)
|
|
117
|
+
}
|
|
118
|
+
return acc
|
|
119
|
+
},
|
|
120
|
+
{ selfHosted: [], hosted: [] } as { selfHosted: typeof items, hosted: typeof items },
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const sections = [
|
|
124
|
+
{
|
|
125
|
+
label: t.hosted,
|
|
126
|
+
children: hosted,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: t.selfHosted,
|
|
130
|
+
children: selfHosted,
|
|
131
|
+
},
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
const filteredByHavingItens = sections
|
|
135
|
+
.filter(section => section.children.length > 0)
|
|
136
|
+
.map(section => ({
|
|
137
|
+
type: 'section',
|
|
138
|
+
label: section.label,
|
|
139
|
+
children: section.children,
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
return filteredByHavingItens
|
|
143
|
+
}
|
|
@@ -35,6 +35,9 @@ const dictionary = {
|
|
|
35
35
|
chatAgent: 'Agents',
|
|
36
36
|
uploadSuccessStatus: 'File sent successfully',
|
|
37
37
|
chatViewMenu: 'Chat options menu',
|
|
38
|
+
nothingFound: 'Nothing Found',
|
|
39
|
+
hosted: 'Hosted',
|
|
40
|
+
selfHosted: 'Self Hosted',
|
|
38
41
|
},
|
|
39
42
|
pt: {
|
|
40
43
|
stack: 'Selecionar stack',
|
|
@@ -70,6 +73,9 @@ const dictionary = {
|
|
|
70
73
|
chatAgent: 'Agentes',
|
|
71
74
|
uploadSuccessStatus: 'Arquivo anexado com sucesso',
|
|
72
75
|
chatViewMenu: 'Menu de opções do chat',
|
|
76
|
+
nothingFound: 'Nada encontrado',
|
|
77
|
+
hosted: 'Hospedado',
|
|
78
|
+
selfHosted: 'Auto-hospedado',
|
|
73
79
|
},
|
|
74
80
|
} satisfies Dictionary
|
|
75
81
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Flex } from '@citric/core'
|
|
1
2
|
import { theme } from '@stack-spot/portal-theme'
|
|
2
3
|
import { styled } from 'styled-components'
|
|
3
4
|
|
|
@@ -302,3 +303,39 @@ export const MessageInputBox = styled.div`
|
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
`
|
|
306
|
+
|
|
307
|
+
export const stylesModelSwitcher = {
|
|
308
|
+
selection: {
|
|
309
|
+
minHeight: '300px',
|
|
310
|
+
position: 'absolute',
|
|
311
|
+
bottom: 'calc(100% + 10px)',
|
|
312
|
+
right: '0',
|
|
313
|
+
margin: '0',
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const RowWrapperStyled = styled(Flex)`
|
|
318
|
+
width: 100%;
|
|
319
|
+
justify-content: end;
|
|
320
|
+
margin-right: 4px;
|
|
321
|
+
ul {
|
|
322
|
+
margin: 0;
|
|
323
|
+
}
|
|
324
|
+
.button-select-model {
|
|
325
|
+
border-radius: 15px !important;
|
|
326
|
+
}
|
|
327
|
+
#menuModelSwitcher {
|
|
328
|
+
background-color: ${theme.color.light[500]};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.selection-list-content {
|
|
332
|
+
.action :hover {
|
|
333
|
+
cursor: pointer;
|
|
334
|
+
background-color: ${theme.color.light[600]};
|
|
335
|
+
}
|
|
336
|
+
ul :hover {
|
|
337
|
+
background-color: transparent;
|
|
338
|
+
cursor: default;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
`
|
package/src/views/Resources.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Accordion, Icon, ImageBox, ImageWithFallback, Row, Text } from '@stack-spot/citric-react'
|
|
1
|
+
import { Accordion, Column, Icon, ImageBox, ImageWithFallback, Row, Text } from '@stack-spot/citric-react'
|
|
2
2
|
import { agentToolsClient } from '@stack-spot/portal-network'
|
|
3
3
|
import { theme } from '@stack-spot/portal-theme'
|
|
4
4
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
@@ -27,9 +27,16 @@ export const Resources = () => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const StyledAccordion = styled(Accordion)`
|
|
30
|
+
padding: 8px;
|
|
31
|
+
margin: 8px;
|
|
30
32
|
&[data-citric="accordion"] {
|
|
31
33
|
background-color: ${theme.color.light[400]};
|
|
32
34
|
}
|
|
35
|
+
label {
|
|
36
|
+
display: flex;
|
|
37
|
+
width: 100%;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
}
|
|
33
40
|
`
|
|
34
41
|
|
|
35
42
|
const ResourcesPanel = () => {
|
|
@@ -50,11 +57,11 @@ const ResourcesPanel = () => {
|
|
|
50
57
|
const hasAgentTool = useMemo(() => message?.tools?.some(id => agentsTools?.find((agent) => agent.id === id)), [messageId, toolKits])
|
|
51
58
|
|
|
52
59
|
const header = (image?: string, label?: string) => (
|
|
53
|
-
<Row
|
|
54
|
-
<ImageBox>
|
|
60
|
+
<Row>
|
|
61
|
+
<ImageBox >
|
|
55
62
|
<ImageWithFallback src={image} fallback={<Icon icon="Agent" />} aria-label={label} title={label} />
|
|
56
63
|
</ImageBox>
|
|
57
|
-
<Text>{label}</Text>
|
|
64
|
+
<Text style={{ marginLeft: '8px' }}>{label}</Text>
|
|
58
65
|
</Row>
|
|
59
66
|
)
|
|
60
67
|
|
|
@@ -64,23 +71,25 @@ const ResourcesPanel = () => {
|
|
|
64
71
|
{[...(tools || []), ...(customTools || [])].map(
|
|
65
72
|
(tool) =>
|
|
66
73
|
tool && (
|
|
67
|
-
<StyledAccordion key={tool.id} header={header(tool?.image, tool?.name)} appearance="card">
|
|
74
|
+
<StyledAccordion key={tool.id} header={header(tool?.image, tool?.name)} appearance="card" maxHeight={120}>
|
|
68
75
|
{tool?.description}
|
|
69
76
|
</StyledAccordion>
|
|
70
77
|
))}
|
|
71
78
|
</>
|
|
72
79
|
{
|
|
73
80
|
hasAgentTool &&
|
|
74
|
-
|
|
81
|
+
<Column>
|
|
75
82
|
{message?.tools?.map((id) => {
|
|
76
83
|
const agentTool = agentsTools?.find((agent) => agent.id === id)
|
|
77
84
|
return (
|
|
78
|
-
<StyledAccordion key={id} header={header(agentTool?.avatar || undefined, agentTool?.name)}>
|
|
79
|
-
<
|
|
85
|
+
<StyledAccordion key={agentTool?.id} header={header(agentTool?.avatar || undefined, agentTool?.name)}>
|
|
86
|
+
<Row>
|
|
87
|
+
<AgentDescription agentId={id} />
|
|
88
|
+
</Row>
|
|
80
89
|
</StyledAccordion>
|
|
81
90
|
)},
|
|
82
91
|
)}
|
|
83
|
-
|
|
92
|
+
</Column>
|
|
84
93
|
}
|
|
85
94
|
</>
|
|
86
95
|
)
|