@stack-spot/ai-chat-widget 2.3.0-alpha.1 → 2.3.0
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 +14 -0
- package/dist/StackspotAIWidget.d.ts +1 -5
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +2 -2
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/app-metadata.json +5 -5
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +125 -1
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/state/ChatEntry.d.ts.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/check-is-trial.d.ts.map +1 -1
- package/dist/utils/check-is-trial.js +2 -6
- package/dist/utils/check-is-trial.js.map +1 -1
- package/dist/utils/planning-tool.d.ts +17 -0
- package/dist/utils/planning-tool.d.ts.map +1 -0
- package/dist/utils/planning-tool.js +32 -0
- package/dist/utils/planning-tool.js.map +1 -0
- package/dist/utils/update-tool-step.d.ts +3 -0
- package/dist/utils/update-tool-step.d.ts.map +1 -0
- package/dist/utils/update-tool-step.js +23 -0
- package/dist/utils/update-tool-step.js.map +1 -0
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.js +3 -4
- package/dist/views/Agents/AgentsTab.js.map +1 -1
- package/dist/views/Agents/useAgentFavorites.d.ts.map +1 -1
- package/dist/views/Agents/useAgentFavorites.js +1 -3
- package/dist/views/Agents/useAgentFavorites.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 +21 -6
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/StepsList.d.ts +12 -2
- package/dist/views/Chat/StepsList.d.ts.map +1 -1
- package/dist/views/Chat/StepsList.js +155 -18
- 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 +17 -10
- package/dist/views/Chat/styled.js.map +1 -1
- package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/AgentSelector.js +2 -8
- package/dist/views/MessageInput/AgentSelector.js.map +1 -1
- package/dist/views/MessageInput/ButtonAgent.js +1 -1
- package/dist/views/MessageInput/ButtonAgent.js.map +1 -1
- package/dist/views/MessageInput/ButtonBar.d.ts +1 -2
- package/dist/views/MessageInput/ButtonBar.d.ts.map +1 -1
- package/dist/views/MessageInput/ButtonBar.js +2 -2
- package/dist/views/MessageInput/ButtonBar.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/index.d.ts +1 -2
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +2 -2
- package/dist/views/MessageInput/index.js.map +1 -1
- package/dist/views/Steps/FlowChart/NodeStep.js +1 -1
- package/dist/views/Steps/FlowChart/NodeStep.js.map +1 -1
- package/dist/views/Steps/FlowChart/layout.d.ts +1 -1
- package/dist/views/Steps/FlowChart/layout.d.ts.map +1 -1
- package/dist/views/Steps/FlowChart/layout.js +1 -0
- package/dist/views/Steps/FlowChart/layout.js.map +1 -1
- package/dist/views/Steps/FlowChart/types.d.ts +1 -1
- package/dist/views/Steps/FlowChart/types.d.ts.map +1 -1
- package/dist/views/Steps/StepModal.js +2 -2
- package/dist/views/Steps/StepModal.js.map +1 -1
- package/dist/views/Steps/dictionary.d.ts +1 -1
- package/dist/views/Steps/utils.d.ts +1 -1
- package/dist/views/Steps/utils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/StackspotAIWidget.tsx +1 -7
- package/src/app-metadata.json +5 -5
- package/src/chat-interceptors/send-message.ts +137 -2
- package/src/state/ChatEntry.ts +6 -6
- package/src/state/ChatState.ts +4 -0
- package/src/utils/check-is-trial.ts +2 -5
- package/src/utils/planning-tool.ts +41 -0
- package/src/utils/update-tool-step.tsx +27 -0
- package/src/views/Agents/AgentsTab.tsx +3 -4
- package/src/views/Agents/useAgentFavorites.ts +1 -3
- package/src/views/Chat/ChatMessage.tsx +25 -5
- package/src/views/Chat/StepsList.tsx +337 -44
- package/src/views/Chat/styled.ts +17 -10
- package/src/views/MessageInput/AgentSelector.tsx +2 -7
- package/src/views/MessageInput/ButtonAgent.tsx +1 -1
- package/src/views/MessageInput/ButtonBar.tsx +1 -3
- package/src/views/MessageInput/QuickCommandSelector.tsx +1 -1
- package/src/views/MessageInput/index.tsx +4 -4
- package/src/views/Steps/FlowChart/NodeStep.tsx +1 -1
- package/src/views/Steps/FlowChart/layout.ts +1 -0
- package/src/views/Steps/FlowChart/types.ts +1 -1
- package/src/views/Steps/StepModal.tsx +2 -2
package/src/state/ChatState.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface ChatPropertiesWithOptionalFeatures {
|
|
|
33
33
|
* Whether or not the chat is in a loading state.
|
|
34
34
|
*/
|
|
35
35
|
isLoading?: boolean,
|
|
36
|
+
/**
|
|
37
|
+
* Whether or not the chat is planning.
|
|
38
|
+
*/
|
|
39
|
+
isPlaning?: boolean,
|
|
36
40
|
/**
|
|
37
41
|
* The value of the next message. This is the value of the text typed in the textarea below the chat.
|
|
38
42
|
*/
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { NetworkClient } from '@stack-spot/portal-network'
|
|
2
2
|
|
|
3
3
|
export const checkIsTrial = () => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const trialInfo = NetworkClient.sessionManager?.getSession()?.getTokenData()?.trial_account_status
|
|
7
|
-
return !!trialInfo
|
|
8
|
-
} return false
|
|
4
|
+
const trialInfo = NetworkClient.sessionManager?.getSession()?.getTokenData()?.trial_account_status
|
|
5
|
+
return !!trialInfo
|
|
9
6
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class PlanningToolDictionaryHelper {
|
|
2
|
+
static instance: PlanningToolDictionaryHelper | undefined
|
|
3
|
+
private toolExecutionIdPlanningStep: Record<string, string> = {}
|
|
4
|
+
private toolExecutionIdToolStep: Record<string, string> = {}
|
|
5
|
+
private stepId: Record<string, number> = {}
|
|
6
|
+
|
|
7
|
+
private constructor() {
|
|
8
|
+
PlanningToolDictionaryHelper.instance = this
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static create() {
|
|
12
|
+
return PlanningToolDictionaryHelper.instance ?? new PlanningToolDictionaryHelper()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setMessageIdPlanningStepToolExecutionId(messageId: string, toolExecutionId: string){
|
|
16
|
+
this.toolExecutionIdPlanningStep[toolExecutionId] = messageId
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setMessageIdToolStepToolExecutionId(messageId: string, toolExecutionId: string){
|
|
20
|
+
this.toolExecutionIdToolStep[toolExecutionId] = messageId
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setMessageIdForStepId(messageId: number, stepId: string){
|
|
24
|
+
this.stepId[stepId] = messageId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getMessageIdFromStepId(stepId: string){
|
|
28
|
+
return this.stepId[stepId]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getMessageIdPlanningStepFromToolExecutionId(toolExecutionId: string){
|
|
32
|
+
return this.toolExecutionIdPlanningStep[toolExecutionId]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getMessageIdToolStepFromToolExecutionId(toolExecutionId: string){
|
|
36
|
+
return this.toolExecutionIdToolStep[toolExecutionId]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const planningToolDictionaryHelper = PlanningToolDictionaryHelper.create()
|
|
41
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChatEntry, TextChatEntry } from '../state/ChatEntry'
|
|
2
|
+
import { planningToolDictionaryHelper } from './planning-tool'
|
|
3
|
+
|
|
4
|
+
export const updateToolStep = (messages: ChatEntry[], executionId: string,
|
|
5
|
+
newStatus: 'pending' | 'running' | 'success' | 'error' | 'awaiting_approval') => {
|
|
6
|
+
|
|
7
|
+
// if last message is a user message, no update in tool status is needed
|
|
8
|
+
if (messages[messages.length-1].getValue().agentType === 'user') return
|
|
9
|
+
|
|
10
|
+
const messageId = planningToolDictionaryHelper.getMessageIdPlanningStepFromToolExecutionId(executionId)
|
|
11
|
+
const message = messages.find((message) => `${message.id}` === messageId)
|
|
12
|
+
let update = false
|
|
13
|
+
const messageValue = message?.getValue()
|
|
14
|
+
messageValue?.steps?.map((step) => {
|
|
15
|
+
if (step.type === 'step') {
|
|
16
|
+
const tool = step.attempts?.[0].tools?.[0]
|
|
17
|
+
if (tool?.executionId === executionId && step.status !== newStatus) {
|
|
18
|
+
step.status = newStatus
|
|
19
|
+
update = true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (update) {
|
|
25
|
+
message?.setValue({ ...messageValue as TextChatEntry })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -7,7 +7,7 @@ import { useMemo, useState } from 'react'
|
|
|
7
7
|
import { NavigationComponent } from '../../components/ComponentNavigator'
|
|
8
8
|
import { DescribedRadioGroup } from '../../components/form/DescribedRadioGroup'
|
|
9
9
|
import { WorkspaceTabNavigator } from '../../components/WorkspaceTabNavigator'
|
|
10
|
-
import { useCurrentChat
|
|
10
|
+
import { useCurrentChat } from '../../context/hooks'
|
|
11
11
|
import { useRightPanel } from '../../right-panel/hooks'
|
|
12
12
|
import { ChatProperties } from '../../state/ChatState'
|
|
13
13
|
import { AgentDescription } from './AgentDescription'
|
|
@@ -28,11 +28,10 @@ export const AgentsTab = ({ visibility, workspaceId, agent, showSubmitButton = t
|
|
|
28
28
|
const { useFavorites, onAddFavorite, onRemoveFavorite } = useAgentFavorites()
|
|
29
29
|
const [submitEnabled, setSubmitEnabled] = useState(false)
|
|
30
30
|
const listFavorites = useFavorites()
|
|
31
|
-
const
|
|
32
|
-
const agentDefault = agentToolsClient.agentDefault.useQuery({ enabled: isAgentEnabled })
|
|
31
|
+
const agentDefault = agentToolsClient.agentDefault.useQuery()
|
|
33
32
|
const agents = workspaceId
|
|
34
33
|
? workspaceAiClient.getAgentFromWorkspaceAi.useQuery({ workspaceId }) as AgentResponseWithBuiltIn[]
|
|
35
|
-
: agentToolsClient.agents.useQuery({ visibility }
|
|
34
|
+
: agentToolsClient.agents.useQuery({ visibility })
|
|
36
35
|
|
|
37
36
|
const initialValue = useMemo<AgentResponseWithBuiltIn | undefined>(
|
|
38
37
|
() => {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/* eslint-disable filenames/match-regex */
|
|
2
2
|
import { agentToolsClient } from '@stack-spot/portal-network'
|
|
3
|
-
import { useCurrentChatState } from '../../context/hooks'
|
|
4
3
|
|
|
5
4
|
export function useAgentFavorites() {
|
|
6
|
-
const
|
|
7
|
-
const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' }, { enabled: isAgentEnabled })
|
|
5
|
+
const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' })
|
|
8
6
|
const [addFavorite, pendingAddFav] = agentToolsClient.addFavorite.useMutation()
|
|
9
7
|
const [removeFavorite, pendingRemoveFav] = agentToolsClient.removeFavorite.useMutation()
|
|
10
8
|
|
|
@@ -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, useCurrentChat, useWidget } from '../../context/hooks'
|
|
12
|
+
import { useChatEntry, useChatMessages, 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'
|
|
@@ -17,7 +17,7 @@ import { toolById } from '../../utils/tools'
|
|
|
17
17
|
import { AgentInfo } from './AgentInfo'
|
|
18
18
|
import { useChatScrollToBottomEffect } from './chat-scroll'
|
|
19
19
|
import { onCopyAll, onCopyCode, onLikeOrDislike } from './events'
|
|
20
|
-
import { StepsList } from './StepsList'
|
|
20
|
+
import { StepsList, StepsPlaceholder, ViewToolsDetails } from './StepsList'
|
|
21
21
|
|
|
22
22
|
export interface CustomRenderResult {
|
|
23
23
|
/**
|
|
@@ -218,7 +218,21 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
218
218
|
{ searchAgentsRequest: { ids: entry.tools || [''] } }, { enabled: !!entry.tools })
|
|
219
219
|
const [copied, setCopied] = useState(false)
|
|
220
220
|
const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
|
|
221
|
-
|
|
221
|
+
const isPlanning = useCurrentChatState('isPlaning') ?? false
|
|
222
|
+
|
|
223
|
+
// when we have a steps but we are not showing any content of the step
|
|
224
|
+
// (because it is a tool and the user has already answered the question)
|
|
225
|
+
// we do not want to show an avatar with empty content, so we hide the entire message
|
|
226
|
+
const toolsStep = entry.steps?.find(s => s.type === 'tool')
|
|
227
|
+
const messages = useChatMessages(chat.id)
|
|
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
|
+
|
|
222
236
|
useChatScrollToBottomEffect(ref, [entry])
|
|
223
237
|
useMidnightUpdateView()
|
|
224
238
|
|
|
@@ -343,7 +357,8 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
343
357
|
widget.set('panel', 'resources')
|
|
344
358
|
}
|
|
345
359
|
|
|
346
|
-
return (entry.content || entry.error || !!entry.steps?.length
|
|
360
|
+
return (entry.content || entry.error || !!entry.steps?.length ||
|
|
361
|
+
entry.upload?.length) && (!isMessageHidden || !toolsStep || isPlanning) && (
|
|
347
362
|
<li key={entry.messageId} className={entry.agentType} ref={ref}>
|
|
348
363
|
<div className="chat-message-container"
|
|
349
364
|
onMouseEnter={entry.agentType === 'user' ? () => setShowUserButtonCopy(true) : undefined}
|
|
@@ -356,11 +371,15 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
356
371
|
{!!entry.badges?.length && <div className="badges">
|
|
357
372
|
{entry.badges.map((b, index) => <Badge key={index} colorPalette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
358
373
|
</div>}
|
|
374
|
+
|
|
375
|
+
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id}
|
|
376
|
+
userHasAlreadyAnswered={userHasAlreadyAnswered} />}
|
|
377
|
+
|
|
359
378
|
{renderContent()}
|
|
360
379
|
|
|
361
|
-
{!!entry.steps?.length && <StepsList steps={entry.steps} chatId={chat.id} messageId={message.id} />}
|
|
362
380
|
</div>
|
|
363
381
|
)}
|
|
382
|
+
{isPlanning && entry.agentType === 'bot' && isLast && <StepsPlaceholder /> }
|
|
364
383
|
|
|
365
384
|
{entry.error && <Alert type="error">{entry.error}</Alert>}
|
|
366
385
|
</div>
|
|
@@ -416,6 +435,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage, cust
|
|
|
416
435
|
</ImageBox>
|
|
417
436
|
)})}
|
|
418
437
|
</Button>
|
|
438
|
+
<ViewToolsDetails chatId={chat.id} />
|
|
419
439
|
</div>}
|
|
420
440
|
|
|
421
441
|
{shouldShowFooter && <div className="message-footer">
|
|
@@ -1,96 +1,389 @@
|
|
|
1
|
-
import { Icon } from '@stack-spot/citric-
|
|
2
|
-
import { Button, ProgressCircular, Text } from '@stack-spot/citric-react'
|
|
1
|
+
import { Accordion, Badge, Button, Card, Column, Divider, Icon, IconBox, ImageWithFallback, ProgressCircular, Row, Skeleton, Text } from '@stack-spot/citric-react'
|
|
3
2
|
import { AnimatedHeight } from '@stack-spot/portal-components/AnimatedHeight'
|
|
4
|
-
import { ChatStep, StepChatStep } from '@stack-spot/portal-network'
|
|
3
|
+
import { ChatStep, StepChatStep, ToolChatStep } from '@stack-spot/portal-network'
|
|
5
4
|
import { theme } from '@stack-spot/portal-theme'
|
|
6
5
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
7
|
-
import { findLastIndex } from 'lodash'
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
6
|
+
import { findLast, findLastIndex } from 'lodash'
|
|
7
|
+
import React, { useEffect, useMemo } from 'react'
|
|
8
|
+
import styled from 'styled-components'
|
|
9
|
+
import { Markdown } from '../../components/Markdown'
|
|
10
|
+
import { useChat, useChatMessages, useCurrentChat, useCurrentChatMessages, useWidget } from '../../context/hooks'
|
|
11
|
+
import { ChatEntry } from '../../state/ChatEntry'
|
|
12
|
+
import { planningToolDictionaryHelper } from '../../utils/planning-tool'
|
|
13
|
+
import { updateToolStep } from '../../utils/update-tool-step'
|
|
14
|
+
import { onCopyCode } from './events'
|
|
10
15
|
|
|
11
16
|
interface Props {
|
|
12
17
|
steps: ChatStep[],
|
|
13
18
|
messageId: number,
|
|
14
19
|
chatId: string,
|
|
20
|
+
userHasAlreadyAnswered?: boolean,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface StepChatStepWithTarget extends Omit<StepChatStep, 'status' | 'id' | 'type'> {
|
|
24
|
+
status: 'pending' | 'running' | 'success' | 'error' | 'target' | 'awaiting_approval',
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
interface StepProps {
|
|
18
|
-
step:
|
|
28
|
+
step: StepChatStepWithTarget,
|
|
19
29
|
index: number,
|
|
20
|
-
total
|
|
30
|
+
total?: number,
|
|
31
|
+
totalTools?: number,
|
|
32
|
+
isAllDone?: boolean,
|
|
21
33
|
onClick?: () => void,
|
|
22
34
|
}
|
|
23
35
|
|
|
24
|
-
function getStatusIcon(status:
|
|
36
|
+
function getStatusIcon(status: StepChatStepWithTarget['status'] | 'target', isDone?: boolean) {
|
|
25
37
|
const iconProps = { style: { color: theme.color.light[700] }, size: 'xs' } as const
|
|
26
38
|
switch (status) {
|
|
27
39
|
case 'error': return <Icon group="fill" icon="TimesCircle" {...iconProps} />
|
|
28
40
|
case 'success': return <Icon group="fill" icon="CheckCircle" {...iconProps} />
|
|
29
|
-
case 'pending': return <Icon icon="
|
|
41
|
+
case 'pending': return <Icon group="fill" icon="Circle" {...iconProps} style={{ color: theme.color.light[600] }} />
|
|
42
|
+
case 'awaiting_approval': return <Icon group="fill" icon="ExclamationTriangle" {...iconProps} />
|
|
43
|
+
case 'target': return <Icon icon="Target" {...iconProps} style={{ color: isDone ? theme.color.light[700] : theme.color.light[600] }} />
|
|
30
44
|
case 'running': return <ProgressCircular className="loading" colorScheme="inverse" size="xs" />
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
|
|
34
|
-
const
|
|
48
|
+
const StepAccordionHeader = ({ step, index, expand }: Pick<StepProps, 'step' | 'index'> & { expand: React.ReactElement }) => {
|
|
49
|
+
const t = useTranslate(dictionary)
|
|
50
|
+
return <Row gap="8px">
|
|
51
|
+
{expand}
|
|
52
|
+
{step.status === 'target' ? <Text className="step-title" appearance="body2" color="light.700">{t.planGoal}:</Text> :
|
|
53
|
+
<Badge colorScheme="inverse" appearance="square">
|
|
54
|
+
{t.step} {index}
|
|
55
|
+
</Badge>}
|
|
56
|
+
<Text className="step-title" appearance="body2">
|
|
57
|
+
{step.input}
|
|
58
|
+
</Text>
|
|
59
|
+
{step.status === 'awaiting_approval' &&
|
|
60
|
+
<Badge appearance="square" style={{ backgroundColor: theme.color.gray[800], color: theme.color.gray[50] }}>
|
|
61
|
+
<Icon icon="Security" />
|
|
62
|
+
{t.pendingReview}
|
|
63
|
+
</Badge>}
|
|
64
|
+
</Row>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const StyledCard = styled(Card)`
|
|
68
|
+
&:hover {
|
|
69
|
+
background-color: ${theme.color.light[500]}
|
|
70
|
+
}
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
const Step = ({ step, index, onClick, total, totalTools, isAllDone }: StepProps) => {
|
|
35
74
|
const t = useTranslate(dictionary)
|
|
75
|
+
const status = getStatusIcon(step.status, isAllDone)
|
|
76
|
+
const hasTools = step.attempts?.[0]?.tools && step.attempts?.[0]?.tools?.length > 0
|
|
77
|
+
|
|
36
78
|
return (
|
|
37
79
|
<li tabIndex={onClick ? 0 : undefined} onClick={onClick} role={onClick ? 'button' : 'listitem'}>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
80
|
+
<Row gap="4px" alignItems="center">
|
|
81
|
+
<div className="step-status-icon">{status}</div>
|
|
82
|
+
<StyledCard p="8px" w="80%">
|
|
83
|
+
<Accordion header={expand => <StepAccordionHeader step={step} index={index} expand={expand} />}>
|
|
84
|
+
<Column pt="12px">
|
|
85
|
+
{total ?
|
|
86
|
+
<Row gap="40px">
|
|
87
|
+
<Row gap="4px">
|
|
88
|
+
<Icon icon="Hashtag" size="sm" color="light.700" />
|
|
89
|
+
<Text color="light.700">{t.totalSteps}</Text>
|
|
90
|
+
{total}
|
|
91
|
+
</Row>
|
|
92
|
+
<Row gap="4px">
|
|
93
|
+
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
94
|
+
<Text color="light.700">{t.totalTools}</Text>
|
|
95
|
+
{totalTools ?? 0}
|
|
96
|
+
</Row>
|
|
97
|
+
</Row>
|
|
98
|
+
: <>
|
|
99
|
+
<Row pb="8px">
|
|
100
|
+
<Icon icon="Target" size="sm" color="light.700" />
|
|
101
|
+
<Text color="light.700" tag="span" style={{ margin: '0 4px' }}>{t.stepGoal}:</Text>
|
|
102
|
+
<Text tag="span">
|
|
103
|
+
{step.input}
|
|
104
|
+
</Text>
|
|
105
|
+
</Row>
|
|
106
|
+
{hasTools ? <>
|
|
107
|
+
<Row gap="4px">
|
|
108
|
+
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
109
|
+
<Text color="light.700">{t.toolsToBeExecuted}:</Text>
|
|
110
|
+
</Row>
|
|
111
|
+
<ul className="tools-list">
|
|
112
|
+
{step.attempts?.[0]?.tools?.map((tool, index) => (<li key={`${tool.id}-${index}`}>
|
|
113
|
+
<Text>{tool.name}: {tool.goal}</Text>
|
|
114
|
+
</li>))}
|
|
115
|
+
</ul>
|
|
116
|
+
</> : <Row gap="4px">
|
|
117
|
+
<Icon icon="BorderRadius" size="sm" color="light.700" />
|
|
118
|
+
<Text color="light.700">{t.noToolToBeUsed}</Text>
|
|
119
|
+
</Row>
|
|
120
|
+
}
|
|
121
|
+
</>
|
|
122
|
+
}
|
|
123
|
+
</Column>
|
|
124
|
+
</Accordion>
|
|
125
|
+
</StyledCard>
|
|
126
|
+
</Row>
|
|
42
127
|
</li>
|
|
43
128
|
)
|
|
44
129
|
}
|
|
45
130
|
|
|
46
|
-
export const
|
|
131
|
+
export const StepsPlaceholder = () => {
|
|
132
|
+
const t = useTranslate(dictionary)
|
|
133
|
+
return <Card gap="8px">
|
|
134
|
+
<Row gap="8px">
|
|
135
|
+
<ProgressCircular colorScheme="inverse" size="xs" />
|
|
136
|
+
<Text color="light.700">{t.generatingPlan}</Text>
|
|
137
|
+
</Row>
|
|
138
|
+
<Text color="light.700">
|
|
139
|
+
{t.analyzingRequirements}
|
|
140
|
+
</Text>
|
|
141
|
+
<Row gap="12px">
|
|
142
|
+
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
143
|
+
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
144
|
+
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
145
|
+
<Skeleton height="31px" width="148px" bgLevel={600} />
|
|
146
|
+
</Row>
|
|
147
|
+
</Card>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const AwaitingApproval = ({ customApproveText, chatId }: { chatId: string, customApproveText?: string }) => {
|
|
151
|
+
const t = useTranslate(dictionary)
|
|
152
|
+
const chat = useChat(chatId)
|
|
153
|
+
|
|
154
|
+
const onAnswer = (response: string) => {
|
|
155
|
+
chat.pushMessage(ChatEntry.createUserEntry('', false, undefined, undefined, response))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return <>
|
|
159
|
+
<Row gap="8px">
|
|
160
|
+
<Button colorScheme="light" onClick={() => onAnswer(t.cancel)}>
|
|
161
|
+
<Row gap="8px">
|
|
162
|
+
<Icon icon="Stop" />
|
|
163
|
+
{t.cancel}
|
|
164
|
+
</Row>
|
|
165
|
+
</Button>
|
|
166
|
+
|
|
167
|
+
<Button colorScheme="inverse" onClick={() => onAnswer(customApproveText ?? t.approve)}>
|
|
168
|
+
<Row gap="8px">
|
|
169
|
+
<Icon group="fill" icon="Play" />
|
|
170
|
+
{customApproveText ?? t.approve}
|
|
171
|
+
</Row>
|
|
172
|
+
</Button>
|
|
173
|
+
</Row>
|
|
174
|
+
</>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const ToolStepsList = ({ toolStep, messageId, chatId }: { toolStep: ToolChatStep, messageId: number, chatId: string }) => {
|
|
178
|
+
const t = useTranslate(dictionary)
|
|
179
|
+
const chat = useCurrentChat()
|
|
180
|
+
const messages = useCurrentChatMessages()
|
|
181
|
+
const inputParsed = `\`\`\`json
|
|
182
|
+
${JSON.stringify(toolStep?.input, null, 2)}
|
|
183
|
+
\`\`\``
|
|
184
|
+
|
|
185
|
+
const tool = useMemo(() => {
|
|
186
|
+
if (!toolStep) return undefined
|
|
187
|
+
return toolStep.attempts?.[0].tools?.[0]
|
|
188
|
+
}, [toolStep])
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!toolStep) return undefined
|
|
192
|
+
const executionId = toolStep.attempts?.[0].tools?.[0].executionId
|
|
193
|
+
if (!executionId) return
|
|
194
|
+
|
|
195
|
+
updateToolStep(messages, executionId, toolStep.status)
|
|
196
|
+
|
|
197
|
+
}, [messages, toolStep, toolStep.status])
|
|
198
|
+
|
|
199
|
+
return <>
|
|
200
|
+
{toolStep && tool ? <AnimatedHeight>
|
|
201
|
+
<div className="steps">
|
|
202
|
+
<Badge colorPalette="yellow" appearance="square">
|
|
203
|
+
<Icon icon="StopWatch" />
|
|
204
|
+
<Text>{tool.name} {t.keepWorking}</Text>
|
|
205
|
+
</Badge>
|
|
206
|
+
<Card mt="16px" gap="8px" bgLevel={500}>
|
|
207
|
+
<Row>
|
|
208
|
+
<ImageWithFallback src={tool.image} width="32px" fallback={<IconBox appearance="circle" icon="StackSpot" />} />
|
|
209
|
+
<Text>{tool.name}</Text>
|
|
210
|
+
</Row>
|
|
211
|
+
|
|
212
|
+
<Text>
|
|
213
|
+
{toolStep.user_question}
|
|
214
|
+
</Text>
|
|
215
|
+
|
|
216
|
+
<Accordion header={expand => <Row gap="8px" mb="4px">
|
|
217
|
+
<Card p="4px" bgLevel={400}>{expand}</Card>
|
|
218
|
+
<Text > {t.viewDetails} </Text>
|
|
219
|
+
</Row>}>
|
|
220
|
+
<Markdown onCopyCode={(code) => onCopyCode(code, `${messageId}`, chat)}>
|
|
221
|
+
{inputParsed}
|
|
222
|
+
</Markdown>
|
|
223
|
+
</Accordion>
|
|
224
|
+
|
|
225
|
+
<AwaitingApproval customApproveText={t.approveTool} chatId={chatId} />
|
|
226
|
+
</Card>
|
|
227
|
+
</div>
|
|
228
|
+
</AnimatedHeight> : null}
|
|
229
|
+
</>
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const StepsList = ({ steps, messageId, chatId, userHasAlreadyAnswered }: Props) => {
|
|
47
233
|
const t = useTranslate(dictionary)
|
|
48
|
-
|
|
49
|
-
const
|
|
234
|
+
|
|
235
|
+
const filteredSteps = steps.filter(s => s.type === 'step')
|
|
236
|
+
const actualSteps = useMemo(() => filteredSteps.filter((item) => {
|
|
237
|
+
const messageIdForStep = planningToolDictionaryHelper.getMessageIdFromStepId(item.id)
|
|
238
|
+
if (!messageIdForStep) {
|
|
239
|
+
planningToolDictionaryHelper.setMessageIdForStepId(messageId, item.id)
|
|
240
|
+
return true
|
|
241
|
+
} else if (messageIdForStep === messageId) {
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
244
|
+
// If a step is from a planning and it is already inserted in the planningToolDictionaryHelper it means the step is already in a
|
|
245
|
+
// previous message and we do not want to show it again (for instance, when required a approval in the planning, we will have
|
|
246
|
+
// two messages with the same step id one for the planning awaiting and one for the planning end, so we want to show only one)
|
|
247
|
+
return false
|
|
248
|
+
}), [filteredSteps])
|
|
249
|
+
|
|
250
|
+
const planning = steps.filter(s => s.type === 'planning')
|
|
251
|
+
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
actualSteps.map((item) => {
|
|
254
|
+
const executionId = item.attempts[0]?.tools?.[0].executionId
|
|
255
|
+
if (executionId) {
|
|
256
|
+
planningToolDictionaryHelper.setMessageIdPlanningStepToolExecutionId(`${messageId}`, executionId)
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}, [actualSteps, messageId])
|
|
260
|
+
|
|
261
|
+
const toolsStep = findLast(steps, s => s.type === 'tool')
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
const executionId = toolsStep?.attempts?.[0]?.tools?.[0]?.executionId
|
|
265
|
+
if (executionId) {
|
|
266
|
+
planningToolDictionaryHelper.setMessageIdToolStepToolExecutionId(`${messageId}`, executionId)
|
|
267
|
+
}
|
|
268
|
+
}, [toolsStep, messageId])
|
|
269
|
+
|
|
270
|
+
const planningGoal = planning?.[0]?.goal
|
|
271
|
+
const isLastStepDone = actualSteps[actualSteps.length - 1]?.status !== 'running' &&
|
|
272
|
+
actualSteps[actualSteps.length - 1]?.status !== 'pending'
|
|
273
|
+
const totalTools = useMemo(() => actualSteps?.reduce((sum, step) => {
|
|
274
|
+
const firstAttempt = step.attempts && step.attempts[0]
|
|
275
|
+
const toolsCount = firstAttempt.tools?.length ?? 0
|
|
276
|
+
return sum + toolsCount
|
|
277
|
+
}, 0), [steps])
|
|
278
|
+
|
|
50
279
|
let currentStepIndex = findLastIndex(actualSteps, s => s.status === 'running' || s.status === 'success')
|
|
51
280
|
if (currentStepIndex === -1) currentStepIndex = 0
|
|
52
|
-
const widget = useWidget()
|
|
53
281
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
widget.set('panel', 'steps')
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<AnimatedHeight>
|
|
282
|
+
return (<>
|
|
283
|
+
{actualSteps.length > 0 ? <AnimatedHeight>
|
|
61
284
|
<div className="steps">
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
285
|
+
<Row gap="14px" mb="8px" ml="5px">
|
|
286
|
+
<Icon icon="Target" size="sm" color="light.600" />
|
|
287
|
+
<Text>{t.executionPlan}</Text>
|
|
288
|
+
</Row>
|
|
289
|
+
|
|
290
|
+
<ul className="steps-list">
|
|
291
|
+
{actualSteps.map((s, i) => <Step step={s} key={i} index={i + 1} />)}
|
|
292
|
+
|
|
293
|
+
<Step
|
|
294
|
+
step={{ status: 'target', input: planningGoal, attempts: [] }}
|
|
295
|
+
index={currentStepIndex + 1}
|
|
296
|
+
total={actualSteps.length}
|
|
297
|
+
totalTools={totalTools}
|
|
298
|
+
isAllDone={isLastStepDone}
|
|
299
|
+
/>
|
|
72
300
|
</ul>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
301
|
+
|
|
302
|
+
<Column gap="8px" mt="8px">
|
|
303
|
+
<Divider colorScheme="light" />
|
|
304
|
+
<Text color="light.700">{planning?.[0]?.user_question}</Text>
|
|
305
|
+
{!userHasAlreadyAnswered && planning?.[0]?.status === 'awaiting_approval' && <AwaitingApproval chatId={chatId} />}
|
|
306
|
+
</Column>
|
|
307
|
+
|
|
80
308
|
</div>
|
|
81
|
-
</AnimatedHeight>
|
|
309
|
+
</AnimatedHeight> : null}
|
|
310
|
+
|
|
311
|
+
{toolsStep && toolsStep.status === 'awaiting_approval' && !userHasAlreadyAnswered &&
|
|
312
|
+
<ToolStepsList toolStep={toolsStep} messageId={messageId} chatId={chatId} />}
|
|
313
|
+
</>
|
|
82
314
|
)
|
|
83
315
|
}
|
|
84
316
|
|
|
317
|
+
export const ViewToolsDetails = ({ chatId }: { chatId: string }) => {
|
|
318
|
+
const t = useTranslate(dictionary)
|
|
319
|
+
const messages = useChatMessages(chatId)
|
|
320
|
+
const messageId = useMemo(() => {
|
|
321
|
+
const messageWithPlanning = findLast(messages, (message) => {
|
|
322
|
+
const steps = message.getValue().steps
|
|
323
|
+
const planningStep = steps?.find((step) => step.type === 'planning' && step.status === 'success')
|
|
324
|
+
return planningStep ? true : false
|
|
325
|
+
})
|
|
326
|
+
return messageWithPlanning?.id
|
|
327
|
+
|
|
328
|
+
}, [messages])
|
|
329
|
+
const widget = useWidget()
|
|
330
|
+
|
|
331
|
+
function openToolsPanel() {
|
|
332
|
+
if (messageId) {
|
|
333
|
+
widget.set('currentMessageInPanel', { chatId, messageId })
|
|
334
|
+
widget.set('panel', 'steps')
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return <>
|
|
339
|
+
<div className="step-actions">
|
|
340
|
+
<Button colorScheme="light" size="sm" className="icon-button details" onClick={openToolsPanel}>
|
|
341
|
+
<Icon group="fill" icon="Play" size="xs" />
|
|
342
|
+
{t.detailed}
|
|
343
|
+
</Button>
|
|
344
|
+
</div>
|
|
345
|
+
</>
|
|
346
|
+
}
|
|
347
|
+
|
|
85
348
|
const dictionary = {
|
|
86
349
|
en: {
|
|
87
350
|
step: 'Step',
|
|
88
351
|
hideSteps: 'Hide steps',
|
|
89
352
|
detailed: 'View detailed mode',
|
|
353
|
+
generatingPlan: 'Generating execution plan...',
|
|
354
|
+
analyzingRequirements: 'Analyzing task requirements',
|
|
355
|
+
executionPlan: 'Execution Plan',
|
|
356
|
+
planGoal: 'Plan Goal',
|
|
357
|
+
toolsToBeExecuted: 'Tools to be executed',
|
|
358
|
+
totalSteps: 'Total Steps',
|
|
359
|
+
totalTools: 'Total Tools',
|
|
360
|
+
stepGoal: 'Step Goal',
|
|
361
|
+
cancel: 'Cancel execution',
|
|
362
|
+
approve: 'Approve & Execute Plan',
|
|
363
|
+
keepWorking: 'will keep working after your answer',
|
|
364
|
+
viewDetails: 'View details',
|
|
365
|
+
approveTool: 'Approve execution',
|
|
366
|
+
pendingReview: 'Pending review',
|
|
367
|
+
noToolToBeUsed: 'No tool will be needed',
|
|
90
368
|
},
|
|
91
369
|
pt: {
|
|
92
370
|
step: 'Passo',
|
|
93
371
|
hideSteps: 'Esconder passos',
|
|
94
372
|
detailed: 'Ver modo detalhado',
|
|
373
|
+
generatingPlan: 'Gerando plano de execução...',
|
|
374
|
+
analyzingRequirements: 'Analisando os requisitos da task',
|
|
375
|
+
executionPlan: 'Plano de Execução',
|
|
376
|
+
planGoal: 'Finalidade do Plano',
|
|
377
|
+
toolsToBeExecuted: 'Tools a serem executadas',
|
|
378
|
+
totalSteps: 'Total de Passos',
|
|
379
|
+
totalTools: 'Total de Tools',
|
|
380
|
+
stepGoal: 'Objetivo do passo',
|
|
381
|
+
cancel: 'Cancelar execução',
|
|
382
|
+
approve: 'Aprovar & Executar plano',
|
|
383
|
+
keepWorking: 'continuará trabalhando após a sua resposta',
|
|
384
|
+
viewDetails: 'Ver detalhes',
|
|
385
|
+
approveTool: 'Aprovar execução',
|
|
386
|
+
pendingReview: 'Revisão pendente',
|
|
387
|
+
noToolToBeUsed: 'Nenhuma tool será necessária',
|
|
95
388
|
},
|
|
96
389
|
} satisfies Dictionary
|