@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.
- package/CHANGELOG.md +89 -6
- package/dist/app-metadata.json +4 -4
- package/dist/chat-interceptors/quick-commands.js +2 -2
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +8 -17
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.d.ts +3 -1
- package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.js +31 -19
- package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
- package/dist/context/hooks.d.ts +1 -0
- package/dist/context/hooks.d.ts.map +1 -1
- package/dist/context/hooks.js +24 -0
- package/dist/context/hooks.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +8 -0
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +1 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/ChatState.d.ts +6 -0
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js +15 -0
- package/dist/state/ChatState.js.map +1 -1
- package/dist/utils/tools.d.ts +17 -8
- package/dist/utils/tools.d.ts.map +1 -1
- package/dist/utils/tools.js +20 -9
- package/dist/utils/tools.js.map +1 -1
- package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
- package/dist/views/Agents/AgentDescription.js +5 -14
- package/dist/views/Agents/AgentDescription.js.map +1 -1
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.js +3 -2
- package/dist/views/Agents/AgentsTab.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts +1 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +19 -20
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts +2 -8
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +8 -7
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/ChatHistory/utils.d.ts.map +1 -1
- package/dist/views/ChatHistory/utils.js +94 -2
- package/dist/views/ChatHistory/utils.js.map +1 -1
- package/dist/views/KnowledgeSources.d.ts +1 -1
- package/dist/views/KnowledgeSources.d.ts.map +1 -1
- package/dist/views/KnowledgeSources.js +31 -45
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js +29 -52
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +9 -1
- package/dist/views/MessageInput/index.js.map +1 -1
- package/dist/views/MessageInput/styled.d.ts.map +1 -1
- package/dist/views/MessageInput/styled.js +4 -0
- package/dist/views/MessageInput/styled.js.map +1 -1
- package/dist/views/Resources.js +8 -5
- package/dist/views/Resources.js.map +1 -1
- package/dist/views/Tools.js +1 -1
- package/dist/views/Tools.js.map +1 -1
- package/package.json +3 -3
- package/src/app-metadata.json +4 -4
- package/src/chat-interceptors/quick-commands.ts +2 -2
- package/src/chat-interceptors/send-message.ts +8 -19
- package/src/components/form/DescribedCheckboxGroup.tsx +61 -35
- package/src/context/hooks.ts +24 -0
- package/src/state/ChatEntry.ts +9 -1
- package/src/state/ChatState.ts +16 -0
- package/src/utils/tools.ts +28 -17
- package/src/views/Agents/AgentDescription.tsx +40 -36
- package/src/views/Agents/AgentsTab.tsx +8 -7
- package/src/views/Chat/ChatMessage.tsx +23 -23
- package/src/views/Chat/StepsList.tsx +9 -9
- package/src/views/ChatHistory/utils.ts +96 -3
- package/src/views/KnowledgeSources.tsx +57 -77
- package/src/views/MessageInput/QuickCommandSelector.tsx +39 -93
- package/src/views/MessageInput/index.tsx +10 -0
- package/src/views/MessageInput/styled.ts +4 -0
- package/src/views/Resources.tsx +11 -10
- package/src/views/Tools.tsx +1 -1
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { Icon } from '@stack-spot/citric-icons'
|
|
2
|
-
import { Badge, Card, IconBox, ImageBox, ImageWithFallback,
|
|
2
|
+
import { AsyncContent, Badge, Card, IconBox, ImageBox, ImageWithFallback, Text } from '@stack-spot/citric-react'
|
|
3
3
|
import { agentToolsClient } from '@stack-spot/portal-network'
|
|
4
4
|
import { useMemo } from 'react'
|
|
5
|
-
import { toolById } from '../../utils/tools'
|
|
6
5
|
import { useAgentsDictionary } from './dictionary'
|
|
7
6
|
import { AgentDescriptionBox } from './styled'
|
|
8
7
|
|
|
9
8
|
export const AgentDescription = ({ agentId }: { agentId?: string }) => {
|
|
10
9
|
const t = useAgentsDictionary()
|
|
11
|
-
const [agent
|
|
12
|
-
const [toolKits, , , { isLoading: isLoadingToolKit }] = agentToolsClient.tools.useStatefulQuery({})
|
|
10
|
+
const [agent,, error, { isLoading }] = agentToolsClient.agent.useStatefulQuery({ agentId: agentId! }, { enabled: !!agentId })
|
|
13
11
|
const numberOfKnowledgeSources = agent?.knowledge_sources_config?.knowledge_sources.length ?? 0
|
|
14
12
|
|
|
15
13
|
const knowledgeSources = useMemo(
|
|
@@ -23,13 +21,6 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
|
|
|
23
21
|
)),
|
|
24
22
|
[agent],
|
|
25
23
|
)
|
|
26
|
-
const skeleton = useMemo(() => {
|
|
27
|
-
const loadingKS: React.ReactElement[] = []
|
|
28
|
-
for (let i = 0; i < numberOfKnowledgeSources; i++) {
|
|
29
|
-
loadingKS.push(<li key={i}><Badge colorPalette="teal" appearance="square"><Skeleton className="ks-skeleton" /></Badge></li>)
|
|
30
|
-
}
|
|
31
|
-
return loadingKS
|
|
32
|
-
}, [numberOfKnowledgeSources])
|
|
33
24
|
|
|
34
25
|
const { tools, multiAgents } = useMemo(() => {
|
|
35
26
|
const tools: React.ReactElement[] = []
|
|
@@ -39,12 +30,11 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
|
|
|
39
30
|
for (const toolkit of builtInTools) {
|
|
40
31
|
for (const tool of toolkit.tools ?? []) {
|
|
41
32
|
if (toolkit.id == 'UTILITIES'){
|
|
42
|
-
const toolWithImage = toolById(tool.id, toolKits)
|
|
43
33
|
tools.push(
|
|
44
34
|
<li key={tool.id}>
|
|
45
35
|
<Card gap="10px" direction="row" flex={1} size="xxs" bgLevel={500}>
|
|
46
|
-
<ImageBox><ImageWithFallback src={
|
|
47
|
-
<Text color="light.contrastText">{
|
|
36
|
+
<ImageBox><ImageWithFallback src={toolkit.image_url} fallback={<Icon icon="Cog" />} /></ImageBox>
|
|
37
|
+
<Text color="light.contrastText">{tool?.name || tool?.id || 'unknown'}</Text>
|
|
48
38
|
</Card>
|
|
49
39
|
</li>,
|
|
50
40
|
)
|
|
@@ -76,28 +66,42 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
|
|
|
76
66
|
return { tools, multiAgents }
|
|
77
67
|
}, [agent])
|
|
78
68
|
|
|
69
|
+
const mcpToolkits = useMemo(() => agent?.toolkits?.mcp_toolkits?.map(t => (
|
|
70
|
+
<li key={`mcp-${t.id}`}>
|
|
71
|
+
<Card gap="10px" direction="row" flex={1} size="xxs" bgLevel={500}>
|
|
72
|
+
<ImageBox><ImageWithFallback src={t.avatar ?? undefined} fallback={<Icon icon="Cog" />} /></ImageBox>
|
|
73
|
+
<Text color="light.contrastText">{t.name}</Text>
|
|
74
|
+
</Card>
|
|
75
|
+
</li>
|
|
76
|
+
)), [agent?.toolkits?.mcp_toolkits])
|
|
77
|
+
|
|
79
78
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
79
|
+
<AsyncContent loading={isLoading} error={error}>
|
|
80
|
+
<AgentDescriptionBox>
|
|
81
|
+
{agent?.description && <section>
|
|
82
|
+
<Text appearance="microtext1" className="title">{t.description}</Text>
|
|
83
|
+
<Text>{agent?.description}</Text>
|
|
84
|
+
</section>}
|
|
85
|
+
{(!!numberOfKnowledgeSources || !!knowledgeSources?.length) && <section>
|
|
86
|
+
<Text appearance="microtext1" className="title">Knowledge sources</Text>
|
|
87
|
+
<ul>{knowledgeSources}</ul>
|
|
88
|
+
</section>}
|
|
89
|
+
{!!tools?.length && <section>
|
|
90
|
+
<Text appearance="microtext1" className="title">{t.tools}</Text>
|
|
91
|
+
<ul>
|
|
92
|
+
{tools}
|
|
93
|
+
{mcpToolkits}
|
|
94
|
+
</ul>
|
|
95
|
+
</section>}
|
|
96
|
+
{!!multiAgents?.length && <section>
|
|
97
|
+
<Text appearance="microtext1" className="title">{t.multiAgent}</Text>
|
|
98
|
+
<ul>{multiAgents}</ul>
|
|
99
|
+
</section>}
|
|
100
|
+
{agent?.model_name && <section>
|
|
101
|
+
<Text appearance="microtext1" className="title">LLM</Text>
|
|
102
|
+
<Badge colorPalette="orange" appearance="square">{agent?.model_name}</Badge>
|
|
103
|
+
</section>}
|
|
104
|
+
</AgentDescriptionBox>
|
|
105
|
+
</AsyncContent>
|
|
102
106
|
)
|
|
103
107
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Icon } from '@stack-spot/citric-icons'
|
|
2
|
-
import { Button } from '@stack-spot/citric-react'
|
|
2
|
+
import { Button, ImageWithFallback } from '@stack-spot/citric-react'
|
|
3
3
|
import { Placeholder } from '@stack-spot/portal-components/Placeholder'
|
|
4
4
|
import { AgentResponseWithBuiltIn, agentToolsClient, AgentVisibilityLevel, workspaceAiClient } from '@stack-spot/portal-network'
|
|
5
5
|
import { WorkspaceResponse } from '@stack-spot/portal-network/api/workspace-ai'
|
|
@@ -30,7 +30,7 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
|
|
|
30
30
|
const [submitEnabled, setSubmitEnabled] = useState(false)
|
|
31
31
|
const listFavorites = useFavorites()
|
|
32
32
|
const agentDefault = agentToolsClient.agentDefault.useQuery()
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
const [filter, setFilter] = useState<string | undefined>()
|
|
35
35
|
const [apiFilter, setApiFilter] = useState<string | undefined>()
|
|
36
36
|
const [isPending, startTransition] = useTransition()
|
|
@@ -47,9 +47,9 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
|
|
|
47
47
|
const data = workspaceAiClient.getAgentFromWorkspaceAi.useStatefulQuery({ workspaceId: workspaceId! },
|
|
48
48
|
{ enabled: !!workspaceId })
|
|
49
49
|
const workspaceAgents = data?.[0] as AgentResponseWithBuiltIn[]
|
|
50
|
-
|
|
51
|
-
const [agentsData=[], { fetchNextPage, hasNextPage }] = agentToolsClient.agentsMultipleFilters.useInfiniteQuery({
|
|
52
|
-
filters: {
|
|
50
|
+
|
|
51
|
+
const [agentsData = [], { fetchNextPage, hasNextPage }] = agentToolsClient.agentsMultipleFilters.useInfiniteQuery({
|
|
52
|
+
filters: {
|
|
53
53
|
page: 1,
|
|
54
54
|
size: 20,
|
|
55
55
|
name: apiFilter,
|
|
@@ -78,7 +78,7 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
|
|
|
78
78
|
return <>
|
|
79
79
|
<div className="content">
|
|
80
80
|
<DescribedRadioGroup
|
|
81
|
-
fetchNextPage={fetchNextPage}
|
|
81
|
+
fetchNextPage={fetchNextPage}
|
|
82
82
|
hasNextPage={hasNextPage && !workspaceId}
|
|
83
83
|
options={agents}
|
|
84
84
|
initialValue={initialValue}
|
|
@@ -96,7 +96,8 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
|
|
|
96
96
|
isLoading={isPending}
|
|
97
97
|
data={ag => ({
|
|
98
98
|
idOrSlug: ag.id,
|
|
99
|
-
|
|
99
|
+
// below, "key" is important. I don't know why exactly, but sometimes the avatar doesn't update if we don't do this.
|
|
100
|
+
image: <ImageWithFallback key={ag.id} src={ag.avatar ?? undefined} fallback={<Icon icon="Agent" />} />,
|
|
100
101
|
description: <AgentDescription agentId={ag.id} />,
|
|
101
102
|
name: ag.name,
|
|
102
103
|
listFavorites,
|
|
@@ -9,7 +9,7 @@ import { PhoneInput } from 'react-international-phone'
|
|
|
9
9
|
import 'react-international-phone/style.css'
|
|
10
10
|
import { FileDescription } from '../../components/FileDescription'
|
|
11
11
|
import { Markdown } from '../../components/Markdown'
|
|
12
|
-
import { useChatEntry,
|
|
12
|
+
import { useChatEntry, useCurrentChat, useCurrentChatState, useWidget } from '../../context/hooks'
|
|
13
13
|
import { useMidnightUpdateView } from '../../hooks/midnight-update-view'
|
|
14
14
|
import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
|
|
15
15
|
import { useDateFormatter } from '../../utils/date'
|
|
@@ -213,26 +213,24 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
213
213
|
const widget = useWidget()
|
|
214
214
|
const chat = useCurrentChat()
|
|
215
215
|
const agentId = entry.agent?.id ?? ''
|
|
216
|
-
|
|
216
|
+
// enabled: we don't want to make any request if there is no agent
|
|
217
|
+
const [agent] = agentToolsClient.agent.useStatefulQuery({ agentId }, { enabled: !!agentId })
|
|
218
|
+
const toolkits = useMemo(() => [
|
|
219
|
+
...agent?.toolkits?.builtin_toolkits ?? [],
|
|
220
|
+
...agent?.toolkits?.custom_toolkits ?? [],
|
|
221
|
+
...agent?.toolkits?.mcp_toolkits ?? [],
|
|
222
|
+
], [agent])
|
|
217
223
|
const [agentsTools] = agentToolsClient.agentsByIds.useStatefulQuery(
|
|
218
|
-
{ searchAgentsRequest: { ids: entry.tools || [''] } }, { enabled: !!entry.tools })
|
|
224
|
+
{ searchAgentsRequest: { ids: entry.tools || [''] } }, { enabled: !!entry.tools?.length })
|
|
219
225
|
const [copied, setCopied] = useState(false)
|
|
220
226
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
227
|
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
228
|
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const userHasAlreadyAnswered = useMemo(() => {
|
|
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()
|
|
232
|
-
return nextMessage.agentType === 'user'
|
|
233
|
-
}, [messages, messages.length])
|
|
234
|
-
const isMessageHidden = toolsStep && userHasAlreadyAnswered
|
|
235
|
-
|
|
229
|
+
// Dynamic tool steps are identified by the "dynamic" id.
|
|
230
|
+
// We're temporarily hiding the toolbox for these dynamic tools while we finalize their UI.
|
|
231
|
+
const shouldHideToolbox = entry?.steps?.some((step) => step?.id === 'dynamic')
|
|
232
|
+
const showToolBox = (!!agentsTools?.length || !!entry.tools?.length) && !shouldHideToolbox
|
|
233
|
+
|
|
236
234
|
useChatScrollToBottomEffect(ref, [entry])
|
|
237
235
|
useMidnightUpdateView()
|
|
238
236
|
|
|
@@ -357,8 +355,10 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
357
355
|
widget.set('panel', 'resources')
|
|
358
356
|
}
|
|
359
357
|
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
const shouldShowToolsOnlyMessage = (entry.done !== false || entry.hasPlanning) && !!entry.steps?.length
|
|
359
|
+
const shouldRender = entry.content || entry.error || shouldShowToolsOnlyMessage || !!entry.upload?.length
|
|
360
|
+
|
|
361
|
+
return shouldRender && (
|
|
362
362
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
363
363
|
<div className="chat-message-container"
|
|
364
364
|
onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
|
|
@@ -372,8 +372,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
372
372
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
373
373
|
</div>}
|
|
374
374
|
|
|
375
|
-
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
376
|
-
userHasAlreadyAnswered={userHasAlreadyAnswered} />}
|
|
375
|
+
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
|
|
377
376
|
|
|
378
377
|
{renderContent()}
|
|
379
378
|
|
|
@@ -394,7 +393,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
394
393
|
))}</ul>
|
|
395
394
|
</div>}
|
|
396
395
|
|
|
397
|
-
{
|
|
396
|
+
{showToolBox &&
|
|
398
397
|
<div className="tools-box">
|
|
399
398
|
<Button appearance="none" onClick={openResourcesPanel} aria-label={t.openResourcesPanel}>
|
|
400
399
|
{agentsTools?.map((agent) => (
|
|
@@ -409,13 +408,14 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
409
408
|
</ImageBox>
|
|
410
409
|
))}
|
|
411
410
|
{entry?.tools?.map((id) => {
|
|
411
|
+
// In the multi-agents feature, an agent is returned as a tool, but we don't want to show agents here.
|
|
412
412
|
if (agentsTools?.some((agent) => agent.id === id)) return null
|
|
413
|
-
const tool = toolById(id,
|
|
413
|
+
const tool = toolById(id, toolkits)
|
|
414
414
|
return (
|
|
415
415
|
<ImageBox key={id} className="agent-info-avatar-resource">
|
|
416
416
|
<ImageWithFallback
|
|
417
417
|
src={tool?.image}
|
|
418
|
-
fallback={<Icon icon="Cog" />}
|
|
418
|
+
fallback={<Icon icon="Cog" title={tool?.name} aria-label={tool?.name} />}
|
|
419
419
|
alt={tool?.name}
|
|
420
420
|
aria-label={tool?.name}
|
|
421
421
|
title={tool?.name}
|
|
@@ -7,7 +7,7 @@ import { findLast, findLastIndex } from 'lodash'
|
|
|
7
7
|
import React, { useEffect, useMemo } from 'react'
|
|
8
8
|
import styled from 'styled-components'
|
|
9
9
|
import { Markdown } from '../../components/Markdown'
|
|
10
|
-
import { useChat, useChatMessages, useCurrentChat, useCurrentChatMessages, useWidget } from '../../context/hooks'
|
|
10
|
+
import { useChat, useChatMessages, useCurrentChat, useCurrentChatMessages, useIsLastEntryInCurrentChat, useWidget } from '../../context/hooks'
|
|
11
11
|
import { ChatEntry } from '../../state/ChatEntry'
|
|
12
12
|
import { planningToolDictionaryHelper } from '../../utils/planning-tool'
|
|
13
13
|
import { updateToolStep } from '../../utils/update-tool-step'
|
|
@@ -17,7 +17,6 @@ interface Props {
|
|
|
17
17
|
steps: ChatStep[],
|
|
18
18
|
messageId: number,
|
|
19
19
|
chatId: string,
|
|
20
|
-
userHasAlreadyAnswered?: boolean,
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
interface StepChatStepWithTarget extends Omit<StepChatStep, 'status' | 'id' | 'type'> {
|
|
@@ -152,7 +151,7 @@ const AwaitingApproval = ({ customApproveText, chatId }: { chatId: string, custo
|
|
|
152
151
|
const chat = useChat(chatId)
|
|
153
152
|
|
|
154
153
|
const onAnswer = (response: string) => {
|
|
155
|
-
chat.pushMessage(ChatEntry.createUserEntry(
|
|
154
|
+
chat.pushMessage(ChatEntry.createUserEntry(response))
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
return <>
|
|
@@ -174,10 +173,11 @@ const AwaitingApproval = ({ customApproveText, chatId }: { chatId: string, custo
|
|
|
174
173
|
</>
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
|
|
176
|
+
const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolChatStep, messageId: number, chatId: string }) => {
|
|
178
177
|
const t = useTranslate(dictionary)
|
|
179
178
|
const chat = useCurrentChat()
|
|
180
179
|
const messages = useCurrentChatMessages()
|
|
180
|
+
const isLastMessage = useIsLastEntryInCurrentChat(messageId)
|
|
181
181
|
const inputParsed = `\`\`\`json
|
|
182
182
|
${JSON.stringify(toolStep?.input, null, 2)}
|
|
183
183
|
\`\`\``
|
|
@@ -222,15 +222,16 @@ export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolC
|
|
|
222
222
|
</Markdown>
|
|
223
223
|
</Accordion>
|
|
224
224
|
|
|
225
|
-
<AwaitingApproval customApproveText={t.approveTool} chatId={chatId} />
|
|
225
|
+
{isLastMessage && <AwaitingApproval customApproveText={t.approveTool} chatId={chatId} />}
|
|
226
226
|
</Card>
|
|
227
227
|
</div>
|
|
228
228
|
</AnimatedHeight> : null}
|
|
229
229
|
</>
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
export const StepsList = ({ steps, messageId, chatId
|
|
232
|
+
export const StepsList = ({ steps, messageId, chatId }: Props) => {
|
|
233
233
|
const t = useTranslate(dictionary)
|
|
234
|
+
const isLastMessage = useIsLastEntryInCurrentChat(messageId)
|
|
234
235
|
|
|
235
236
|
const filteredSteps = steps.filter(s => s.type === 'step')
|
|
236
237
|
const actualSteps = useMemo(() => filteredSteps.filter((item) => {
|
|
@@ -302,7 +303,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
302
303
|
<Column gap="8px" mt="8px">
|
|
303
304
|
<Divider colorScheme="light" />
|
|
304
305
|
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
305
|
-
{
|
|
306
|
+
{isLastMessage && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
|
|
306
307
|
</Column>
|
|
307
308
|
|
|
308
309
|
{planning?.[0]?.status === 'success' && <ViewToolsDetails chatId={chatId} messageId={messageId}/>}
|
|
@@ -310,8 +311,7 @@ export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }:
|
|
|
310
311
|
</div>
|
|
311
312
|
</AnimatedHeight> : null}
|
|
312
313
|
|
|
313
|
-
{toolsStep && toolsStep.status === 'awaiting_approval' &&
|
|
314
|
-
<ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
314
|
+
{toolsStep && toolsStep.status === 'awaiting_approval' && <ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
315
315
|
</>
|
|
316
316
|
)
|
|
317
317
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { agentToolsClient, aiClient, workspaceClient } from '@stack-spot/portal-network'
|
|
1
|
+
import { agentToolsClient, aiClient, AnswerChatStep, ChatAgentTool, ChatStep, PlanningChatStep, workspaceClient } from '@stack-spot/portal-network'
|
|
2
|
+
import { AgentModel } from '@stack-spot/portal-network/api/agent-tools'
|
|
2
3
|
import { last } from 'lodash'
|
|
3
4
|
import { ChatEntry } from '../../state/ChatEntry'
|
|
4
5
|
import { ChatProperties, ChatState } from '../../state/ChatState'
|
|
@@ -41,6 +42,96 @@ export async function findWorkspace(id: string | null): Promise<ChatProperties['
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function toJSONString(data: any) {
|
|
46
|
+
if (data === undefined || data === null) return undefined
|
|
47
|
+
if (typeof data === 'object') {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(data, null, 2)
|
|
50
|
+
} catch { /* empty */ }
|
|
51
|
+
} else {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(JSON.parse(data), null, 2)
|
|
54
|
+
} catch { /* empty */ }
|
|
55
|
+
}
|
|
56
|
+
return `${data}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function findTool(agent: AgentModel | undefined, id: string) {
|
|
60
|
+
const allToolkits = [
|
|
61
|
+
...agent?.toolkits?.builtin_toolkits ?? [],
|
|
62
|
+
...agent?.toolkits?.custom_toolkits ?? [],
|
|
63
|
+
...agent?.toolkits?.mcp_toolkits ?? [],
|
|
64
|
+
]
|
|
65
|
+
for (const toolkit of allToolkits) {
|
|
66
|
+
for (const tool of toolkit.tools ?? []) {
|
|
67
|
+
if ('id' in tool && tool.id === id) return { toolkit, tool }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { toolkit: undefined, tool: { name: id } }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function stepsFromAgentInfo(
|
|
74
|
+
agentInfo: any[] | null | undefined, agentId: string | undefined, isLastEntry: boolean,
|
|
75
|
+
): Promise<ChatStep[] | undefined> {
|
|
76
|
+
const planningInfo = agentInfo?.find(i => i.type === 'planning' && !!i.data)
|
|
77
|
+
if (planningInfo) {
|
|
78
|
+
let agent: AgentModel | undefined
|
|
79
|
+
try {
|
|
80
|
+
agent = agentId ? await agentToolsClient.agent.query({ agentId }) : undefined
|
|
81
|
+
} catch { /* empty */ }
|
|
82
|
+
const planning: PlanningChatStep = {
|
|
83
|
+
type: 'planning',
|
|
84
|
+
goal: planningInfo.data.plan_goal,
|
|
85
|
+
status: planningInfo.action === 'awaiting_approval' ? 'awaiting_approval' : 'success',
|
|
86
|
+
id: `${Math.random()}`,
|
|
87
|
+
steps: planningInfo.data.steps,
|
|
88
|
+
duration: planningInfo.duration,
|
|
89
|
+
user_question: planningInfo.data.user_question,
|
|
90
|
+
}
|
|
91
|
+
const steps = planningInfo?.data?.steps?.map((s: any): ChatStep => {
|
|
92
|
+
const lastTool = agentInfo!.find(i => i.id === last(s.tools as any[])?.tool_execution_id && i.action === 'end')
|
|
93
|
+
return {
|
|
94
|
+
type: 'step',
|
|
95
|
+
id: s.id,
|
|
96
|
+
status: isLastEntry && planningInfo.action === 'awaiting_approval' ? 'pending' : 'success',
|
|
97
|
+
input: s.goal,
|
|
98
|
+
output: toJSONString(lastTool?.data?.output),
|
|
99
|
+
duration: lastTool?.duration,
|
|
100
|
+
attempts: [{
|
|
101
|
+
tools: s.tools?.map((t: any): ChatAgentTool => {
|
|
102
|
+
const { toolkit, tool } = findTool(agent, t.tool_id)
|
|
103
|
+
const start = agentInfo!.find(i => i.id === t.tool_execution_id && i.action === 'start')
|
|
104
|
+
const end = agentInfo!.find(i => i.id === t.tool_execution_id && i.action === 'end')
|
|
105
|
+
return {
|
|
106
|
+
id: t.tool_id,
|
|
107
|
+
executionId: t.tool_execution_id,
|
|
108
|
+
name: tool?.name,
|
|
109
|
+
description: 'description' in tool ? tool.description : undefined,
|
|
110
|
+
image: toolkit ? (('image_url' in toolkit ? toolkit?.image_url : toolkit?.avatar) ?? undefined) : undefined,
|
|
111
|
+
goal: t.goal,
|
|
112
|
+
duration: end?.duration,
|
|
113
|
+
input: toJSONString(start?.data?.input),
|
|
114
|
+
output: toJSONString(end?.data?.output),
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
}],
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
const answerInfo = agentInfo?.find(i => i.type === 'chat' && i.action === 'end')
|
|
121
|
+
const answer: AnswerChatStep | undefined = answerInfo ? {
|
|
122
|
+
id: `${Math.random()}`,
|
|
123
|
+
status: 'success',
|
|
124
|
+
type: 'answer',
|
|
125
|
+
duration: answerInfo.duration,
|
|
126
|
+
} : undefined
|
|
127
|
+
return answer ? [planning, ...steps, answer] : [planning, ...steps]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toolsFromAgentInfo(agentInfo: any[] | null | undefined): string[] {
|
|
132
|
+
return agentInfo?.find(i => i.type === 'chat' && i.action === 'end')?.data?.used_tools
|
|
133
|
+
}
|
|
134
|
+
|
|
44
135
|
/**
|
|
45
136
|
* Loads the chat identified by `conversationId` into the widget passed as parameter.
|
|
46
137
|
* @param widget
|
|
@@ -64,7 +155,7 @@ export async function loadChat(widget: WidgetState, conversationId: string) {
|
|
|
64
155
|
initial: { features: widget.chatFeatures, label: chat.title, stack, workspace, agent: agent ? {
|
|
65
156
|
...agent, builtIn } : undefined },
|
|
66
157
|
interceptors: widget.interceptors,
|
|
67
|
-
entries: chat.history?.map(item => new ChatEntry({
|
|
158
|
+
entries: await Promise.all(chat.history?.map(async (item, index) => new ChatEntry({
|
|
68
159
|
agentType: item.agent === 'USER' ? 'user' : 'bot',
|
|
69
160
|
content: item.content,
|
|
70
161
|
type: 'md',
|
|
@@ -72,7 +163,9 @@ export async function loadChat(widget: WidgetState, conversationId: string) {
|
|
|
72
163
|
messageId: item.message_id,
|
|
73
164
|
knowledgeSources: item.agent === 'USER' ? undefined : genericSourcesToKnowledgeSources(item.sources),
|
|
74
165
|
updated: item.updated,
|
|
75
|
-
|
|
166
|
+
steps: await stepsFromAgentInfo(item.agent_info, item.custom_agent?.id, index === chat.history!.length - 1),
|
|
167
|
+
tools: toolsFromAgentInfo(item.agent_info),
|
|
168
|
+
})) ?? []),
|
|
76
169
|
}))
|
|
77
170
|
widget.chatTabs.select(chat.id)
|
|
78
171
|
}
|