@stack-spot/ai-chat-widget 1.8.4 → 1.9.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 +5 -1
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +5 -5
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/app-metadata.json +23 -19
- package/dist/components/Accordion.d.ts.map +1 -1
- package/dist/components/Code.d.ts.map +1 -1
- package/dist/components/FadingOverflow.d.ts.map +1 -1
- package/dist/components/FallbackBoundary/index.d.ts.map +1 -1
- package/dist/components/IconInput.d.ts.map +1 -1
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/ProgressBar.d.ts.map +1 -1
- package/dist/components/QuickStartButton.d.ts.map +1 -1
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelTabs.d.ts.map +1 -1
- package/dist/components/Selector/index.d.ts.map +1 -1
- package/dist/components/Tooltip/context.d.ts.map +1 -1
- package/dist/layout.css +13 -0
- package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
- package/dist/right-panel/RightPanelProvider.d.ts.map +1 -1
- package/dist/state/ChatEntry.d.ts +55 -3
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +4 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/WidgetState.js +2 -2
- package/dist/state/WidgetState.js.map +1 -1
- package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts +1 -0
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +64 -7
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
- package/dist/views/Chat/index.d.ts.map +1 -1
- package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
- package/dist/views/Home/BuiltInAgent.d.ts.map +1 -1
- package/dist/views/Home/index.d.ts.map +1 -1
- package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/AgentSelector.js +4 -3
- package/dist/views/MessageInput/AgentSelector.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
- package/dist/views/MinimizedHeader.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/StackspotAIWidget.tsx +11 -4
- package/src/app-metadata.json +23 -19
- package/src/layout.css +13 -0
- package/src/state/ChatEntry.ts +55 -4
- package/src/state/WidgetState.ts +1 -1
- package/src/views/Chat/ChatMessage.tsx +116 -15
- package/src/views/MessageInput/AgentSelector.tsx +5 -3
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Button, IconBox, Text } from '@citric/core'
|
|
1
|
+
import { Box, Button, Checkbox, Flex, IconBox, Input, Label, Radio, Text } from '@citric/core'
|
|
2
2
|
import { Copy, Dislike, DislikeFill, Like, LikeFill, TimesCircle } from '@citric/icons'
|
|
3
3
|
import { Avatar, Badge, IconButton } from '@citric/ui'
|
|
4
4
|
import { listToClass } from '@stack-spot/portal-theme'
|
|
5
5
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
-
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
6
|
+
import { Dispatch, useCallback, useMemo, useRef, useState } from 'react'
|
|
7
|
+
import { PhoneInput } from 'react-international-phone'
|
|
8
|
+
import 'react-international-phone/style.css'
|
|
7
9
|
import { Markdown } from '../../components/Markdown'
|
|
8
10
|
import { useChatEntry, useCurrentChat, useWidget } from '../../context/hooks'
|
|
9
11
|
import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
|
|
@@ -28,12 +30,98 @@ interface Props {
|
|
|
28
30
|
isLast: boolean,
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
interface RenderInputsEntryProps {
|
|
34
|
+
isLast: boolean,
|
|
35
|
+
entry: TextChatEntry,
|
|
36
|
+
value: string[],
|
|
37
|
+
setValue: Dispatch<React.SetStateAction<string[]>>,
|
|
38
|
+
labels: string[],
|
|
39
|
+
setLabels: Dispatch<React.SetStateAction<string[]>>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const RenderInputsEntry = ({ isLast, entry, value, setValue, setLabels }: RenderInputsEntryProps) => {
|
|
43
|
+
const chat = useCurrentChat()
|
|
44
|
+
|
|
45
|
+
const renderInputs = () => {
|
|
46
|
+
if (entry.type === 'input-text') {
|
|
47
|
+
return <Input name={entry.name} {...entry.validations} onChange={(data) => setValue([data.target.value])} required={entry.required} />
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (entry.type === 'input-radio') {
|
|
51
|
+
return <Flex>
|
|
52
|
+
{entry.options?.map((option) => (<Box w={6} key={option.value}>
|
|
53
|
+
<Label htmlFor={option.value} colorScheme="light.contrastText">
|
|
54
|
+
<Radio name={entry.name} id={option.value} onChange={(data) => {
|
|
55
|
+
if (data.target.checked) {
|
|
56
|
+
setValue([option.label])
|
|
57
|
+
option.value && setLabels([option.value])
|
|
58
|
+
} else {
|
|
59
|
+
setValue([])
|
|
60
|
+
}
|
|
61
|
+
}} />
|
|
62
|
+
<Text ml={3}>{option.label}</Text>
|
|
63
|
+
</Label>
|
|
64
|
+
</Box>))}
|
|
65
|
+
</Flex>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (entry.type === 'button-list') {
|
|
69
|
+
return <Flex>
|
|
70
|
+
{entry.options?.map((item) => (<Button key={item.label} colorScheme={item.color}
|
|
71
|
+
onClick={() => {
|
|
72
|
+
item.value && chat.pushMessage(
|
|
73
|
+
ChatEntry.createUserEntry(item.value, false, entry.name, item?.label ? [item?.label] : undefined),
|
|
74
|
+
)
|
|
75
|
+
}}>
|
|
76
|
+
{item.label}
|
|
77
|
+
</Button>))
|
|
78
|
+
}</Flex>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (entry.type === 'input-checkbox') {
|
|
82
|
+
return <Flex>
|
|
83
|
+
{entry.options?.map((option) => (
|
|
84
|
+
<Flex w={6} key={option.label}>
|
|
85
|
+
<Checkbox name={entry.name} key={option.label} onChange={(data) => {
|
|
86
|
+
if (data.target.checked) {
|
|
87
|
+
setValue([...value, option.label])
|
|
88
|
+
option.value && setLabels([option.value])
|
|
89
|
+
} else {
|
|
90
|
+
const newValue = value.filter(((item) => item !== option.label))
|
|
91
|
+
setValue([...newValue])
|
|
92
|
+
}
|
|
93
|
+
}}/>
|
|
94
|
+
<Text ml={3}>{option.label}</Text>
|
|
95
|
+
</Flex>))}
|
|
96
|
+
</Flex>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (entry.type === 'input-phone') {
|
|
100
|
+
return (<PhoneInput
|
|
101
|
+
defaultCountry="br"
|
|
102
|
+
value={value[0]}
|
|
103
|
+
onChange={(phone) => setValue([phone])}
|
|
104
|
+
className="input-phone"
|
|
105
|
+
placeholder="11961234567"
|
|
106
|
+
/>)
|
|
107
|
+
}
|
|
108
|
+
return <p className="plain-text">{entry.content}</p>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return <Flex flexDirection="column">
|
|
112
|
+
<Text appearance="body2" mb={4}>{entry.content}</Text>
|
|
113
|
+
{isLast && renderInputs()}
|
|
114
|
+
</Flex>
|
|
115
|
+
}
|
|
116
|
+
|
|
31
117
|
/**
|
|
32
118
|
* Renders a message (ChatEntry) in the chat.
|
|
33
119
|
*/
|
|
34
120
|
export const ChatMessage = ({ message, username, isLast }: Props) => {
|
|
35
121
|
const t = useTranslate(dictionary)
|
|
36
122
|
const [liked, setLiked] = useState<boolean | undefined>()
|
|
123
|
+
const [value, setValue] = useState<string[]>([])
|
|
124
|
+
const [labels, setLabels] = useState<string[]>([])
|
|
37
125
|
const entry = useChatEntry(message)
|
|
38
126
|
const dateFormatter = useDateFormatter()
|
|
39
127
|
const userInfo = entry.agentType === 'user' ? <Avatar size="xs">{username}</Avatar> : <AgentInfo agent={entry.agent} />
|
|
@@ -50,13 +138,17 @@ export const ChatMessage = ({ message, username, isLast }: Props) => {
|
|
|
50
138
|
widget.set('panel', 'ks-details')
|
|
51
139
|
}, [])
|
|
52
140
|
|
|
53
|
-
const runAction =
|
|
141
|
+
const runAction = (action: SerializableAction) => {
|
|
54
142
|
if (action.type === 'link') {
|
|
55
143
|
window.open(action.exec, '_blank')
|
|
56
144
|
} else {
|
|
57
|
-
|
|
145
|
+
if (action.exec) {
|
|
146
|
+
chat.pushMessage(ChatEntry.createUserEntry(action.exec))
|
|
147
|
+
} else {
|
|
148
|
+
value && chat.pushMessage(ChatEntry.createUserEntry(value.toString(), false, entry.name, labels))
|
|
149
|
+
}
|
|
58
150
|
}
|
|
59
|
-
}
|
|
151
|
+
}
|
|
60
152
|
|
|
61
153
|
const { like, dislike } = useMemo(() => {
|
|
62
154
|
async function feedback(like: boolean) {
|
|
@@ -82,6 +174,17 @@ export const ChatMessage = ({ message, username, isLast }: Props) => {
|
|
|
82
174
|
}
|
|
83
175
|
}
|
|
84
176
|
|
|
177
|
+
const renderContent = () => {
|
|
178
|
+
if (entry.type === 'md') {
|
|
179
|
+
return <Markdown onCopyCode={(code) => onCopyCode(code, entry.messageId ?? '', chat)}>{entry.content}</Markdown>
|
|
180
|
+
}
|
|
181
|
+
if (entry.type === 'text') {
|
|
182
|
+
return <p className="plain-text">{entry.content}</p>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return <RenderInputsEntry entry={entry} isLast={isLast} value={value} setValue={setValue} setLabels={setLabels} labels={labels} />
|
|
186
|
+
}
|
|
187
|
+
|
|
85
188
|
return (entry.content || entry.error) && (
|
|
86
189
|
<li className={entry.agentType} ref={ref}>
|
|
87
190
|
<div className="chat-message" ref={chatRef} onKeyDown={handleKeyDown} tabIndex={0}>
|
|
@@ -90,24 +193,22 @@ export const ChatMessage = ({ message, username, isLast }: Props) => {
|
|
|
90
193
|
{entry.badges?.length && <div className="badges">
|
|
91
194
|
{entry.badges.map((b, index) => <Badge key={index} palette={b.color ?? 'cyan'} appearance="square">{b.label}</Badge>)}
|
|
92
195
|
</div>}
|
|
93
|
-
{
|
|
94
|
-
? <Markdown onCopyCode={(code) => onCopyCode(code, entry.messageId ?? '', chat)}>{entry.content}</Markdown>
|
|
95
|
-
: <p className="plain-text">{entry.content}</p>
|
|
96
|
-
}
|
|
196
|
+
{renderContent()}
|
|
97
197
|
{entry.actions?.length && (
|
|
98
198
|
<div className="actions">
|
|
99
199
|
{entry.actions.map(
|
|
100
|
-
(a, index) => (
|
|
101
|
-
<Button
|
|
200
|
+
(a, index) => (<>
|
|
201
|
+
{(!a.hideWhenNotLast || isLast) && <Button
|
|
102
202
|
key={index}
|
|
103
203
|
appearance={a.appearance === 'primary' ? 'contained' : 'outlined'}
|
|
104
|
-
colorScheme=
|
|
204
|
+
colorScheme={a.colorScheme ? a.colorScheme : 'inverse'}
|
|
105
205
|
onClick={() => runAction(a)}
|
|
106
|
-
disabled={!isLast}
|
|
206
|
+
disabled={(entry.required && !value.length) ?? !isLast}
|
|
207
|
+
type={a.buttonType ?? 'button'}
|
|
107
208
|
>
|
|
108
209
|
{a.title}
|
|
109
|
-
</Button>
|
|
110
|
-
),
|
|
210
|
+
</Button>}
|
|
211
|
+
</>),
|
|
111
212
|
)}
|
|
112
213
|
</div>
|
|
113
214
|
)}
|
|
@@ -37,11 +37,13 @@ export const AgentSelector = ({ inputRef }: { inputRef: React.RefObject<HTMLText
|
|
|
37
37
|
}, [chat, inputRef])
|
|
38
38
|
|
|
39
39
|
const getAgents = () => {
|
|
40
|
-
const
|
|
41
|
-
const publicAgents = agentClient.publicAgents.useQuery({})
|
|
40
|
+
const personalAgents = agentClient.agents.useQuery({ visibility: 'PERSONAL' })
|
|
42
41
|
const accountAgents = agentClient.agents.useQuery({ visibility: 'ACCOUNT' })
|
|
42
|
+
const sharedAgents = agentClient.agents.useQuery({ visibility: 'SHARED' })
|
|
43
|
+
const publicAgents = agentClient.publicAgents.useQuery({})
|
|
43
44
|
const buildIns = [...[defaultAgent], ...publicAgents.map((agent) => ({ ...agent, visibility_level: 'builtIn' }))]
|
|
44
|
-
|
|
45
|
+
|
|
46
|
+
return uniqBy([...personalAgents, ...accountAgents, ...sharedAgents, ...buildIns], 'id')
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
const AgentItem = ({ avatar, name, slug }: AgentResponse) => {
|