@stack-spot/ai-chat-widget 1.8.5 → 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.
Files changed (50) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/StackspotAIWidget.d.ts +5 -1
  3. package/dist/StackspotAIWidget.d.ts.map +1 -1
  4. package/dist/StackspotAIWidget.js +5 -5
  5. package/dist/StackspotAIWidget.js.map +1 -1
  6. package/dist/app-metadata.json +23 -19
  7. package/dist/components/Accordion.d.ts.map +1 -1
  8. package/dist/components/Code.d.ts.map +1 -1
  9. package/dist/components/FadingOverflow.d.ts.map +1 -1
  10. package/dist/components/FallbackBoundary/index.d.ts.map +1 -1
  11. package/dist/components/IconInput.d.ts.map +1 -1
  12. package/dist/components/Markdown.d.ts.map +1 -1
  13. package/dist/components/ProgressBar.d.ts.map +1 -1
  14. package/dist/components/QuickStartButton.d.ts.map +1 -1
  15. package/dist/components/RightPanelForm.d.ts.map +1 -1
  16. package/dist/components/RightPanelTabs.d.ts.map +1 -1
  17. package/dist/components/Selector/index.d.ts.map +1 -1
  18. package/dist/components/Tooltip/context.d.ts.map +1 -1
  19. package/dist/layout.css +13 -0
  20. package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
  21. package/dist/right-panel/RightPanelProvider.d.ts.map +1 -1
  22. package/dist/state/ChatEntry.d.ts +55 -3
  23. package/dist/state/ChatEntry.d.ts.map +1 -1
  24. package/dist/state/ChatEntry.js +4 -1
  25. package/dist/state/ChatEntry.js.map +1 -1
  26. package/dist/state/WidgetState.js +2 -2
  27. package/dist/state/WidgetState.js.map +1 -1
  28. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  29. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  30. package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
  31. package/dist/views/Chat/ChatMessage.d.ts +1 -0
  32. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  33. package/dist/views/Chat/ChatMessage.js +64 -7
  34. package/dist/views/Chat/ChatMessage.js.map +1 -1
  35. package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
  36. package/dist/views/Chat/index.d.ts.map +1 -1
  37. package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
  38. package/dist/views/Home/BuiltInAgent.d.ts.map +1 -1
  39. package/dist/views/Home/index.d.ts.map +1 -1
  40. package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
  41. package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
  42. package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
  43. package/dist/views/MinimizedHeader.d.ts.map +1 -1
  44. package/package.json +5 -4
  45. package/src/StackspotAIWidget.tsx +11 -4
  46. package/src/app-metadata.json +23 -19
  47. package/src/layout.css +13 -0
  48. package/src/state/ChatEntry.ts +55 -4
  49. package/src/state/WidgetState.ts +1 -1
  50. package/src/views/Chat/ChatMessage.tsx +116 -15
@@ -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 = useCallback((action: SerializableAction) => {
141
+ const runAction = (action: SerializableAction) => {
54
142
  if (action.type === 'link') {
55
143
  window.open(action.exec, '_blank')
56
144
  } else {
57
- chat.pushMessage(ChatEntry.createUserEntry(action.exec))
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
- {entry.type === 'md'
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="inverse"
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
  )}