@stack-spot/ai-chat-widget 1.24.4 → 1.25.1-beta.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 (164) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/app-metadata.json +3 -3
  3. package/dist/chat-interceptors/send-message.js +3 -3
  4. package/dist/chat-interceptors/send-message.js.map +1 -1
  5. package/dist/components/AgentCard/dictionary.d.ts +4 -2
  6. package/dist/components/AgentCard/dictionary.d.ts.map +1 -1
  7. package/dist/components/AgentCard/dictionary.js +4 -2
  8. package/dist/components/AgentCard/dictionary.js.map +1 -1
  9. package/dist/components/FileDescription.d.ts +10 -0
  10. package/dist/components/FileDescription.d.ts.map +1 -0
  11. package/dist/components/FileDescription.js +85 -0
  12. package/dist/components/FileDescription.js.map +1 -0
  13. package/dist/components/Selector/index.d.ts +2 -2
  14. package/dist/components/Selector/index.d.ts.map +1 -1
  15. package/dist/components/Selector/index.js +2 -2
  16. package/dist/components/Selector/index.js.map +1 -1
  17. package/dist/state/ChatEntry.d.ts +9 -0
  18. package/dist/state/ChatEntry.d.ts.map +1 -1
  19. package/dist/state/ChatEntry.js.map +1 -1
  20. package/dist/state/ChatState.d.ts +5 -0
  21. package/dist/state/ChatState.d.ts.map +1 -1
  22. package/dist/state/ChatState.js +6 -0
  23. package/dist/state/ChatState.js.map +1 -1
  24. package/dist/state/constants.d.ts +5 -0
  25. package/dist/state/constants.d.ts.map +1 -0
  26. package/dist/state/constants.js +9 -0
  27. package/dist/state/constants.js.map +1 -0
  28. package/dist/state/types.d.ts +5 -1
  29. package/dist/state/types.d.ts.map +1 -1
  30. package/dist/utils/chat.d.ts +2 -1
  31. package/dist/utils/chat.d.ts.map +1 -1
  32. package/dist/utils/chat.js +2 -1
  33. package/dist/utils/chat.js.map +1 -1
  34. package/dist/utils/tools.d.ts +2 -2
  35. package/dist/utils/tools.d.ts.map +1 -1
  36. package/dist/utils/tools.js +3 -6
  37. package/dist/utils/tools.js.map +1 -1
  38. package/dist/utils/upload/FileUpload.d.ts +21 -0
  39. package/dist/utils/upload/FileUpload.d.ts.map +1 -0
  40. package/dist/utils/upload/FileUpload.js +55 -0
  41. package/dist/utils/upload/FileUpload.js.map +1 -0
  42. package/dist/utils/upload/UploadManager.d.ts +40 -0
  43. package/dist/utils/upload/UploadManager.d.ts.map +1 -0
  44. package/dist/utils/upload/UploadManager.js +131 -0
  45. package/dist/utils/upload/UploadManager.js.map +1 -0
  46. package/dist/utils/upload/context.d.ts +15 -0
  47. package/dist/utils/upload/context.d.ts.map +1 -0
  48. package/dist/utils/upload/context.js +37 -0
  49. package/dist/utils/upload/context.js.map +1 -0
  50. package/dist/utils/upload/errors.d.ts +17 -0
  51. package/dist/utils/upload/errors.d.ts.map +1 -0
  52. package/dist/utils/upload/errors.js +27 -0
  53. package/dist/utils/upload/errors.js.map +1 -0
  54. package/dist/utils/upload/types.d.ts +7 -0
  55. package/dist/utils/upload/types.d.ts.map +1 -0
  56. package/dist/utils/upload/types.js +2 -0
  57. package/dist/utils/upload/types.js.map +1 -0
  58. package/dist/utils/upload/utils.d.ts +4 -0
  59. package/dist/utils/upload/utils.d.ts.map +1 -0
  60. package/dist/utils/upload/utils.js +10 -0
  61. package/dist/utils/upload/utils.js.map +1 -0
  62. package/dist/views/Agents/AgentDescription.d.ts +2 -9
  63. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  64. package/dist/views/Agents/AgentDescription.js +11 -9
  65. package/dist/views/Agents/AgentDescription.js.map +1 -1
  66. package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
  67. package/dist/views/Agents/AgentsPanel.js +11 -11
  68. package/dist/views/Agents/AgentsPanel.js.map +1 -1
  69. package/dist/views/Agents/AgentsTab.d.ts +2 -2
  70. package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
  71. package/dist/views/Agents/AgentsTab.js +4 -4
  72. package/dist/views/Agents/AgentsTab.js.map +1 -1
  73. package/dist/views/Agents/useAgentFavorites.d.ts +1 -1
  74. package/dist/views/Agents/useAgentFavorites.js +4 -4
  75. package/dist/views/Agents/useAgentFavorites.js.map +1 -1
  76. package/dist/views/Chat/AgentInfo.d.ts +2 -1
  77. package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
  78. package/dist/views/Chat/AgentInfo.js +2 -2
  79. package/dist/views/Chat/AgentInfo.js.map +1 -1
  80. package/dist/views/Chat/ChatMessage.d.ts +1 -1
  81. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  82. package/dist/views/Chat/ChatMessage.js +27 -8
  83. package/dist/views/Chat/ChatMessage.js.map +1 -1
  84. package/dist/views/Chat/styled.d.ts.map +1 -1
  85. package/dist/views/Chat/styled.js +15 -1
  86. package/dist/views/Chat/styled.js.map +1 -1
  87. package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
  88. package/dist/views/ChatHistory/HistoryItem.js +8 -5
  89. package/dist/views/ChatHistory/HistoryItem.js.map +1 -1
  90. package/dist/views/ChatHistory/utils.d.ts +0 -6
  91. package/dist/views/ChatHistory/utils.d.ts.map +1 -1
  92. package/dist/views/ChatHistory/utils.js +1 -16
  93. package/dist/views/ChatHistory/utils.js.map +1 -1
  94. package/dist/views/Home/CustomAgent.js +3 -3
  95. package/dist/views/Home/CustomAgent.js.map +1 -1
  96. package/dist/views/MessageInput/AgentSelector.js +4 -4
  97. package/dist/views/MessageInput/AgentSelector.js.map +1 -1
  98. package/dist/views/MessageInput/ButtonAgent.js +2 -2
  99. package/dist/views/MessageInput/ButtonAgent.js.map +1 -1
  100. package/dist/views/MessageInput/{InfoBar.d.ts → ContextBar.d.ts} +2 -2
  101. package/dist/views/MessageInput/ContextBar.d.ts.map +1 -0
  102. package/dist/views/MessageInput/{InfoBar.js → ContextBar.js} +5 -5
  103. package/dist/views/MessageInput/ContextBar.js.map +1 -0
  104. package/dist/views/MessageInput/SelectContent.d.ts.map +1 -1
  105. package/dist/views/MessageInput/SelectContent.js +14 -17
  106. package/dist/views/MessageInput/SelectContent.js.map +1 -1
  107. package/dist/views/MessageInput/UploadBar.d.ts +2 -0
  108. package/dist/views/MessageInput/UploadBar.d.ts.map +1 -0
  109. package/dist/views/MessageInput/UploadBar.js +47 -0
  110. package/dist/views/MessageInput/UploadBar.js.map +1 -0
  111. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  112. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  113. package/dist/views/MessageInput/dictionary.js +18 -4
  114. package/dist/views/MessageInput/dictionary.js.map +1 -1
  115. package/dist/views/MessageInput/index.d.ts.map +1 -1
  116. package/dist/views/MessageInput/index.js +46 -5
  117. package/dist/views/MessageInput/index.js.map +1 -1
  118. package/dist/views/MessageInput/styled.d.ts.map +1 -1
  119. package/dist/views/MessageInput/styled.js +56 -27
  120. package/dist/views/MessageInput/styled.js.map +1 -1
  121. package/dist/views/Steps/dictionary.d.ts +1 -1
  122. package/dist/views/Tools.js +3 -3
  123. package/dist/views/Tools.js.map +1 -1
  124. package/dist/views/Workspaces/WorkspacesTab.js +1 -1
  125. package/package.json +2 -2
  126. package/src/app-metadata.json +3 -3
  127. package/src/chat-interceptors/send-message.ts +3 -3
  128. package/src/components/AgentCard/dictionary.ts +4 -2
  129. package/src/components/FileDescription.tsx +114 -0
  130. package/src/components/Selector/index.tsx +4 -5
  131. package/src/state/ChatEntry.ts +10 -0
  132. package/src/state/ChatState.ts +6 -0
  133. package/src/state/constants.ts +12 -0
  134. package/src/state/types.ts +6 -1
  135. package/src/utils/chat.ts +3 -1
  136. package/src/utils/tools.ts +5 -7
  137. package/src/utils/upload/FileUpload.ts +64 -0
  138. package/src/utils/upload/UploadManager.ts +147 -0
  139. package/src/utils/upload/context.tsx +44 -0
  140. package/src/utils/upload/errors.ts +34 -0
  141. package/src/utils/upload/types.ts +7 -0
  142. package/src/utils/upload/utils.ts +12 -0
  143. package/src/views/Agents/AgentDescription.tsx +18 -25
  144. package/src/views/Agents/AgentsPanel.tsx +11 -12
  145. package/src/views/Agents/AgentsTab.tsx +8 -16
  146. package/src/views/Agents/useAgentFavorites.ts +4 -4
  147. package/src/views/Chat/AgentInfo.tsx +3 -2
  148. package/src/views/Chat/ChatMessage.tsx +51 -16
  149. package/src/views/Chat/styled.ts +15 -1
  150. package/src/views/ChatHistory/HistoryItem.tsx +10 -5
  151. package/src/views/ChatHistory/utils.ts +1 -18
  152. package/src/views/Home/CustomAgent.tsx +4 -4
  153. package/src/views/MessageInput/AgentSelector.tsx +4 -4
  154. package/src/views/MessageInput/ButtonAgent.tsx +2 -2
  155. package/src/views/MessageInput/{InfoBar.tsx → ContextBar.tsx} +9 -9
  156. package/src/views/MessageInput/SelectContent.tsx +17 -21
  157. package/src/views/MessageInput/UploadBar.tsx +69 -0
  158. package/src/views/MessageInput/dictionary.ts +18 -4
  159. package/src/views/MessageInput/index.tsx +77 -32
  160. package/src/views/MessageInput/styled.ts +56 -27
  161. package/src/views/Tools.tsx +4 -3
  162. package/src/views/Workspaces/WorkspacesTab.tsx +1 -1
  163. package/dist/views/MessageInput/InfoBar.d.ts.map +0 -1
  164. package/dist/views/MessageInput/InfoBar.js.map +0 -1
@@ -8,15 +8,15 @@ import { FadingOverflow } from '../../components/FadingOverflow'
8
8
  import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
9
9
  import { useMessageInputDictionary } from './dictionary'
10
10
 
11
- interface InfoBadgeProps {
11
+ interface ContextBadgeProps {
12
12
  label: string,
13
13
  color: ColorPaletteName,
14
14
  dismiss: string,
15
15
  onDismiss?: () => void,
16
16
  }
17
17
 
18
- const InfoBadge = ({ label, color, dismiss, onDismiss }: InfoBadgeProps) => (
19
- <Badge appearance="square" palette={color} className="info-badge"
18
+ const ContextBadge = ({ label, color, dismiss, onDismiss }: ContextBadgeProps) => (
19
+ <Badge appearance="square" palette={color} className="context-badge"
20
20
  afterElement={
21
21
  onDismiss &&
22
22
  <IconButton appearance="square" colorIcon={`${color}.800`} onClick={onDismiss} title={dismiss} arial-label={dismiss}>
@@ -34,7 +34,7 @@ const InfoBadge = ({ label, color, dismiss, onDismiss }: InfoBadgeProps) => (
34
34
  * - which stack is being used;
35
35
  * - which knowledge sources are being used.
36
36
  */
37
- export const InfoBar = () => {
37
+ export const ContextBar = () => {
38
38
  const t = useMessageInputDictionary()
39
39
  const chat = useCurrentChat()
40
40
  const currentStack = useCurrentChatState('stack')
@@ -47,7 +47,7 @@ export const InfoBar = () => {
47
47
  const onDismiss = features.knowledgeSource
48
48
  ? (() => chat.set('knowledgeSources', currentKnowledgeSources.filter(({ id }) => id !== ks.id)))
49
49
  : undefined
50
- return <li key={ks.id}><InfoBadge label={ks.label} dismiss={t.removeKS} color="teal" onDismiss={onDismiss} /></li>
50
+ return <li key={ks.id}><ContextBadge label={ks.label} dismiss={t.removeKS} color="teal" onDismiss={onDismiss} /></li>
51
51
  }), [currentKnowledgeSources])
52
52
  const shouldRenderRemoveAllButton = (
53
53
  currentSelection
@@ -69,7 +69,7 @@ export const InfoBar = () => {
69
69
  }, [])
70
70
 
71
71
  return (
72
- <div className={listToClass(['info-bar', visible && 'visible'])}>
72
+ <div className={listToClass(['info-bar', 'context-bar', visible && 'visible'])}>
73
73
  <div className="space"></div>
74
74
  <div className="content">
75
75
  {shouldRenderRemoveAllButton && (
@@ -83,12 +83,12 @@ export const InfoBar = () => {
83
83
  <ul>
84
84
  {currentSelection && (
85
85
  <li>
86
- <InfoBadge label={t.selected} dismiss={t.removeSelection} color="blue" onDismiss={removeCodeSelection} />
86
+ <ContextBadge label={t.selected} dismiss={t.removeSelection} color="blue" onDismiss={removeCodeSelection} />
87
87
  </li>
88
88
  )}
89
89
  {currentStack && (
90
90
  <li>
91
- <InfoBadge
91
+ <ContextBadge
92
92
  label={currentStack.label}
93
93
  dismiss={t.removeStack}
94
94
  color="cyan"
@@ -98,7 +98,7 @@ export const InfoBar = () => {
98
98
  )}
99
99
  {currentWorkspace && (
100
100
  <li>
101
- <InfoBadge
101
+ <ContextBadge
102
102
  label={currentWorkspace.label}
103
103
  dismiss={t.removeWorkspace}
104
104
  color="pink"
@@ -1,9 +1,10 @@
1
- import { KnowledgeSource, Plus, Spaces, Stack } from '@citric/icons'
1
+ import { DocumentUpload, KnowledgeSource, Plus, Spaces, Stack } from '@citric/icons'
2
2
  import { IconButton } from '@citric/ui'
3
3
  import { SelectionList } from '@stack-spot/portal-components/SelectionList'
4
- import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
5
4
  import { useMemo, useState } from 'react'
6
5
  import { useCurrentChatState, useWidget } from '../../context/hooks'
6
+ import { useUploadManager } from '../../utils/upload/context'
7
+ import { useMessageInputDictionary } from './dictionary'
7
8
 
8
9
  type chatFeatures = 'workspace' | 'knowledgeSource' | 'stack'
9
10
  type chatPanel = 'ks' | 'workspace' | 'stack'
@@ -12,8 +13,9 @@ export const SelectContent = () => {
12
13
  const widget = useWidget()
13
14
  const [visibleMenu, setVisibleMenu] = useState(false)
14
15
  const features = useCurrentChatState('features')
16
+ const t = useMessageInputDictionary()
17
+ const uploadManager = useUploadManager()
15
18
  const hasFeatureButtons = features.workspace || features.knowledgeSource || features.stack
16
- const t = useTranslate(dictionary)
17
19
 
18
20
  const itemConfigs = [
19
21
  {
@@ -59,31 +61,25 @@ export const SelectContent = () => {
59
61
  title={visibleMenu ? t.collapse : t.expand}
60
62
  data-test-hint="button-options"
61
63
  aria-label={visibleMenu ? t.collapse : t.expand}
64
+ aria-controls="chatMessageMenu"
62
65
  onClick={() => setVisibleMenu(state => !state)}>
63
66
  <Plus />
64
67
  </IconButton>
65
68
  <SelectionList
66
- style={{
67
- position: 'absolute',
68
- top: '-140px',
69
- }}
70
- id="menuConfig"
69
+ className="message-menu"
70
+ id="chatMessageMenu"
71
71
  visible={visibleMenu}
72
72
  onHide={() => setVisibleMenu(false)}
73
- items={listItems}
73
+ items={[
74
+ ...listItems,
75
+ {
76
+ label: t.upload,
77
+ onClick: () => uploadManager.open(),
78
+ className: 'upload-item',
79
+ icon: <DocumentUpload />,
80
+ },
81
+ ]}
74
82
  />
75
83
  </>
76
84
  )
77
85
  }
78
-
79
- const dictionary = {
80
- en: {
81
- expand: 'Expand options',
82
- collapse: 'Collapse options',
83
- },
84
- pt: {
85
- expand: 'Mostrar opções',
86
- collapse: 'Esconder opções',
87
- },
88
- } satisfies Dictionary
89
-
@@ -0,0 +1,69 @@
1
+ import { listToClass } from '@stack-spot/portal-theme'
2
+ import { interpolate } from '@stack-spot/portal-translate'
3
+ import { useMemo } from 'react'
4
+ import { FadingOverflow } from '../../components/FadingOverflow'
5
+ import { FileDescription } from '../../components/FileDescription'
6
+ import { useCurrentChat } from '../../context/hooks'
7
+ import { ChatEntry } from '../../state/ChatEntry'
8
+ import { useUploadErrorEffect, useUploadManager, useUploads, useUploadStatus } from '../../utils/upload/context'
9
+ import { FileIsTooLarge, MaxFilesReached } from '../../utils/upload/errors'
10
+ import { FileUpload } from '../../utils/upload/FileUpload'
11
+ import { useMessageInputDictionary } from './dictionary'
12
+
13
+ interface UploadedFileProps {
14
+ upload: FileUpload,
15
+ }
16
+
17
+ function createImageFromFile(file: File) {
18
+ return <img src={URL.createObjectURL(file)} alt={file.name} />
19
+ }
20
+
21
+ const UploadItem = ({ upload }: UploadedFileProps) => {
22
+ const uploadManager = useUploadManager()
23
+ const icon = upload.file.type.toLowerCase().startsWith('image/') ? createImageFromFile(upload.file) : undefined
24
+ const status = useUploadStatus(upload)
25
+ return <FileDescription
26
+ fileName={upload.file.name}
27
+ icon={icon}
28
+ onRemove={() => uploadManager.remove(upload)}
29
+ onRetry={() => upload.retry()}
30
+ status={status}
31
+ />
32
+ }
33
+
34
+ export const UploadBar = () => {
35
+ const uploads = useUploads()
36
+ const listItems = useMemo(() => uploads.map((up) => <li key={up.id}><UploadItem upload={up} /></li>), [uploads])
37
+ const t = useMessageInputDictionary()
38
+ const chat = useCurrentChat()
39
+ const visible = !!uploads.length
40
+
41
+ useUploadErrorEffect((errors) => {
42
+ const sizeErrors = errors.filter(e => e instanceof FileIsTooLarge)
43
+ const maxItemsErrors = errors.filter(e => e instanceof MaxFilesReached)
44
+ const maxSize = sizeErrors[0]?.maxSize
45
+ const maxItems = maxItemsErrors[0]?.maxFiles
46
+ const sizeErrorsNames = sizeErrors.map(e => e.fileName)
47
+ const maxItemsErrorsNames = maxItemsErrors.map(e => e.fileName)
48
+ const lines: string[] = []
49
+ if (sizeErrors.length) {
50
+ lines.push(`${interpolate(t.uploadSizeError, `${maxSize.value} ${maxSize.unit}`)}\n- ${sizeErrorsNames.join('\n- ')}`)
51
+ }
52
+ if (maxItemsErrors.length) {
53
+ lines.push(`${interpolate(t.uploadItemLimitError, maxItems)}\n- ${maxItemsErrorsNames.join('\n- ')}`)
54
+ }
55
+ if (!lines.length) return
56
+ chat.pushMessage(new ChatEntry({ agentType: 'system', type: 'md', content: lines.join('\n\n') }))
57
+ })
58
+
59
+ return (
60
+ <div className={listToClass(['info-bar', 'upload-bar', visible && 'visible'])}>
61
+ <div className="space"></div>
62
+ <div className="content">
63
+ <FadingOverflow className="list-overflow" scroll="arrows" enableHorizontalScrollWithVerticalWheel>
64
+ <ul>{listItems}</ul>
65
+ </FadingOverflow>
66
+ </div>
67
+ </div>
68
+ )
69
+ }
@@ -7,8 +7,6 @@ const dictionary = {
7
7
  spot: 'Select spot',
8
8
  knowledgeSource: 'Select knowledge sources',
9
9
  agent: 'Select agent',
10
- collapse: 'Hide buttons',
11
- expand: 'Show buttons',
12
10
  send: 'Send message',
13
11
  placeholder: 'Message to $0 or use / or @.',
14
12
  cancel: 'Cancel',
@@ -19,6 +17,14 @@ const dictionary = {
19
17
  selected: 'Selected',
20
18
  removeSelection: 'Remove current code selection',
21
19
  remove: 'Remove',
20
+ upload: 'Upload file',
21
+ expand: 'Expand options',
22
+ collapse: 'Collapse options',
23
+ uploadSizeError: 'The following files were not added to the upload list because they\'re larger than $1:',
24
+ uploadItemLimitError: 'The following files were not added because no more than $1 items may be uploaded at a time:',
25
+ cantSendBecauseOfUploadError: 'Can\'t send the message because one of the files in the upload list could not be uploaded. Please, retry it or remove it from the list.',
26
+ cantSendBecauseOfUploadProgress: 'Please wait until all files are uploaded before sending the message. You can also cancel the upload by removing it from the list of uploads.',
27
+ cantSendBecauseOfEmptyContent: 'You can\'t send empty messages. Please write some text or upload a file.',
22
28
  },
23
29
  pt: {
24
30
  stack: 'Selecionar stack',
@@ -26,8 +32,6 @@ const dictionary = {
26
32
  spot: 'Selecionar spot',
27
33
  knowledgeSource: 'Selecionar knowledge sources',
28
34
  agent: 'Selecionar agente',
29
- collapse: 'Esconder botões',
30
- expand: 'Mostrar botões',
31
35
  send: 'Enviar mensagem',
32
36
  placeholder: 'Mensagem para $0 ou use / ou @.',
33
37
  cancel: 'Cancelar',
@@ -38,6 +42,16 @@ const dictionary = {
38
42
  selected: 'Selecionado',
39
43
  removeSelection: 'Desfazer seleção de código',
40
44
  remove: 'Remover',
45
+ upload: 'Enviar arquivo',
46
+ expand: 'Mostrar opções',
47
+ collapse: 'Esconder opções',
48
+ uploadSizeError: 'Os seguintes arquivos não foram adicionados à lista de upload porque eles são maiores que $0:',
49
+ uploadItemLimitError: 'Os seguintes arquivos não foram adicionados à lista de upload porque é permitido enviar no máximo $0 arquivos por vez:',
50
+ uploadError: 'Ocorreu um erro ao enviar o arquivo "$0".',
51
+ unknownUploadError: 'Ocorreu um erro ao enviar os arquivos.',
52
+ cantSendBecauseOfUploadError: 'Não é possível enviar a mensagem, pois um dos arquivos na lista de uploads não pôde ser enviado. Por favor, tente enviá-lo novamente ou remova-o da lista.',
53
+ cantSendBecauseOfUploadProgress: 'Por favor aguarde todos os uploads de arquivos antes de enviar a mensagem. Você pode cancelar o upload removendo o arquivo da lista de uploads.',
54
+ cantSendBecauseOfEmptyContent: 'Não é possível enviar mensagens vazia. Por favor, escreva algum texto ou envie um arquivo.',
41
55
  },
42
56
  } satisfies Dictionary
43
57
 
@@ -7,14 +7,16 @@ import { useCurrentChat, useCurrentChatState, useWidgetState } from '../../conte
7
7
  import { quickCommandRegex } from '../../regex'
8
8
  import { ChatEntry } from '../../state/ChatEntry'
9
9
  import { checkIsTrial } from '../../utils/check-is-trial'
10
+ import { UploadProvider } from '../../utils/upload/context'
10
11
  import { AgentSelector } from './AgentSelector'
11
12
  import { ButtonAgent } from './ButtonAgent'
12
13
  import { ButtonBar } from './ButtonBar'
13
14
  import { useUserEntryHistoryShortcut } from './chat-entry-history'
15
+ import { ContextBar } from './ContextBar'
14
16
  import { useMessageInputDictionary } from './dictionary'
15
- import { InfoBar } from './InfoBar'
16
17
  import { QuickCommandSelector } from './QuickCommandSelector'
17
18
  import { MAX_INPUT_HEIGHT, MessageInputBox, MIN_INPUT_HEIGHT } from './styled'
19
+ import { UploadBar } from './UploadBar'
18
20
 
19
21
  /**
20
22
  * This renders the MessageInput part of the layout which includes the progress bar, the actual textarea, the badges telling what is
@@ -35,14 +37,56 @@ export const MessageInput = () => {
35
37
  const { handleKeyDown, handleKeyUp } = useUserEntryHistoryShortcut()
36
38
  const isTrial = checkIsTrial()
37
39
 
40
+ const checkSendRequirements = useCallback(() => {
41
+ if (chat.uploadManager.status === 'error') {
42
+ chat.pushMessage(new ChatEntry({
43
+ agentType: 'system',
44
+ type: 'text',
45
+ content: t.cantSendBecauseOfUploadError,
46
+ }))
47
+ return false
48
+ }
49
+ if (chat.uploadManager.status === 'uploading') {
50
+ chat.pushMessage(new ChatEntry({
51
+ agentType: 'system',
52
+ type: 'text',
53
+ content: t.cantSendBecauseOfUploadProgress,
54
+ }))
55
+ return false
56
+ }
57
+ if (!chat.get('nextMessage') && !chat.uploadManager.get().length) {
58
+ chat.pushMessage(new ChatEntry({
59
+ agentType: 'system',
60
+ type: 'text',
61
+ content: t.cantSendBecauseOfEmptyContent,
62
+ }))
63
+ return false
64
+ }
65
+ return true
66
+ }, [chat])
67
+
38
68
  const onSend = useCallback(async () => {
39
- const message = chat.get('nextMessage')
40
- if (!message) return
69
+ const message = chat.get('nextMessage')
70
+ const canSend = checkSendRequirements()
71
+ if (!canSend) return
41
72
  const code = chat.get('codeSelection')
42
73
  const language = chat.get('codeLanguage')
43
- const prompt = code && !quickCommandRegex.test(message) ? `${message}\n\`\`\`${language}\n${code}\n\`\`\`` : message
44
- chat.pushMessage(ChatEntry.createUserEntry(prompt, true))
74
+ const prompt = code && !quickCommandRegex.test(message ?? '') ? `${message}\n\`\`\`${language}\n${code}\n\`\`\`` : message
75
+ chat.pushMessage(new ChatEntry({
76
+ type: 'md',
77
+ agentType: 'user',
78
+ content: prompt || '',
79
+ upload: chat.uploadManager.get().map(
80
+ up => ({
81
+ id: up.uploadId!, // we know that all files have been uploaded, so they have an id
82
+ name: up.file.name,
83
+ image: up.file.type.toLowerCase().startsWith('image/') ? URL.createObjectURL(up.file) : undefined,
84
+ }),
85
+ ),
86
+ updated: new Date().toISOString(),
87
+ }))
45
88
  chat.set('nextMessage', '')
89
+ chat.uploadManager.reset()
46
90
  }, [chat])
47
91
 
48
92
  const onKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
@@ -59,33 +103,34 @@ export const MessageInput = () => {
59
103
  }, [isLoading])
60
104
 
61
105
  return (
62
- <MessageInputBox aria-busy={isLoading} className="message-input" $inputFocused={focused}>
63
- <div className="wrapper-action">
64
- <QuickCommandSelector inputRef={textAreaRef} isTrial={isTrial} />
65
- <AgentSelector inputRef={textAreaRef} isTrial={isTrial} />
66
- <div className={listToClass(['action-box', focused && 'focused', isLoading && 'disabled'])}>
67
- <ButtonAgent />
68
- <AdaptiveTextArea
69
- ref={textAreaRef}
70
- placeholder={agentLabel && interpolate(t.placeholder, agentLabel)}
71
- onChange={e => chat.set('nextMessage', e.target.value)}
72
- value={value}
73
- onFocus={() => setFocused(true)}
74
- onBlur={() => setFocused(false)}
75
- onKeyDown={onKeyDown}
76
- onKeyUp={handleKeyUp}
77
- onIncreaseSize={() => setExpanded(false)}
78
- onResetSize={() => !expansionLocked.current && setExpanded(true)}
79
- maxHeight={isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT}
80
- />
106
+ <UploadProvider value={chat.uploadManager}>
107
+ <MessageInputBox aria-busy={isLoading} className="message-input" $inputFocused={focused}>
108
+ <div className="wrapper-action">
109
+ <QuickCommandSelector inputRef={textAreaRef} isTrial={isTrial} />
110
+ <AgentSelector inputRef={textAreaRef} isTrial={isTrial} />
111
+ <div className={listToClass(['action-box', focused && 'focused', isLoading && 'disabled'])}>
112
+ <ButtonAgent />
113
+ <AdaptiveTextArea
114
+ ref={textAreaRef}
115
+ placeholder={agentLabel && interpolate(t.placeholder, agentLabel)}
116
+ onChange={e => chat.set('nextMessage', e.target.value)}
117
+ value={value}
118
+ onFocus={() => setFocused(true)}
119
+ onBlur={() => setFocused(false)}
120
+ onKeyDown={onKeyDown}
121
+ onKeyUp={handleKeyUp}
122
+ onIncreaseSize={() => setExpanded(false)}
123
+ onResetSize={() => !expansionLocked.current && setExpanded(true)}
124
+ maxHeight={isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT}
125
+ />
126
+ </div>
81
127
  </div>
82
- </div>
83
- <ProgressBar visible={true} animate={isLoading}
84
- backgroundColor={isLoading || !focused ? theme.color.light[500] : theme.color.primary[500]} />
85
- <InfoBar />
86
- <ButtonBar onSend={onSend} isLoading={isLoading} />
87
- </MessageInputBox>
128
+ <ProgressBar visible={true} animate={isLoading}
129
+ backgroundColor={isLoading || !focused ? theme.color.light[500] : theme.color.primary[500]} />
130
+ <ContextBar />
131
+ <UploadBar />
132
+ <ButtonBar onSend={onSend} isLoading={isLoading} />
133
+ </MessageInputBox>
134
+ </UploadProvider>
88
135
  )
89
136
  }
90
-
91
-
@@ -2,8 +2,10 @@ import { IconButton } from '@citric/ui'
2
2
  import { theme } from '@stack-spot/portal-theme'
3
3
  import { styled } from 'styled-components'
4
4
 
5
- const INFO_BAR_HEIGHT = 38
6
- const INFO_BAR_DISPLACEMENT = 4
5
+ const CONTEXT_BAR_HEIGHT = 38
6
+ const CONTEXT_BAR_DISPLACEMENT = 4
7
+ const UPLOAD_BAR_HEIGHT = 60
8
+ const UPLOAD_BAR_DISPLACEMENT = 4
7
9
  export const MAX_INPUT_HEIGHT = 300
8
10
  export const MIN_INPUT_HEIGHT = 24
9
11
 
@@ -60,7 +62,7 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
60
62
 
61
63
  &.visible {
62
64
  > .space {
63
- height: ${INFO_BAR_HEIGHT - INFO_BAR_DISPLACEMENT}px;
65
+ height: var(--space-height, 'auto');
64
66
  }
65
67
  }
66
68
 
@@ -74,17 +76,15 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
74
76
  top: 0;
75
77
  left: 0;
76
78
  right: 0;
77
- height: ${INFO_BAR_HEIGHT}px;
79
+ height: var(--content-height, 'auto');
78
80
  padding-top: 8px;
79
81
  background-color: ${theme.color.light[500]};
80
82
  display: flex;
81
83
  flex-direction: row;
82
84
  gap: 4px;
83
- border-right: 2px solid ${({ $inputFocused }) => theme.color.light[$inputFocused ? 600 : 500]};
84
- border-left: 2px solid ${({ $inputFocused }) => theme.color.light[$inputFocused ? 600 : 500]};
85
85
 
86
86
  .list-overflow {
87
- max-width: calc(100% - 30px); // close button + gap
87
+ max-width: calc(100% - var(--list-margins, 0px));
88
88
  height: 24px;
89
89
  &:first-child {
90
90
  margin-left: 0.25rem; // space added to the left when the close all button isn't rendered
@@ -100,31 +100,54 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
100
100
  align-items: center;
101
101
  gap: 6px;
102
102
  }
103
+ }
104
+ }
103
105
 
104
- .info-badge > span {
105
- display: flex;
106
- flex-direction: row;
107
- align-items: center;
108
- gap: 4px;
109
- line-height: 0.75rem;
110
- white-space: nowrap;
111
-
112
- ${IconButton} {
113
- padding: 4px 0;
114
- background: none;
115
- border: none;
116
- width: auto;
117
- height: auto;
106
+ > .context-bar {
107
+ --space-height: ${CONTEXT_BAR_HEIGHT - CONTEXT_BAR_DISPLACEMENT}px;
108
+ --content-height: ${CONTEXT_BAR_HEIGHT}px;
109
+ --list-margins: 30px; // close button width + gap
118
110
 
119
- svg {
120
- width: auto;
121
- height: 12px;
122
- }
111
+ .context-badge > span {
112
+ display: flex;
113
+ flex-direction: row;
114
+ align-items: center;
115
+ gap: 4px;
116
+ line-height: 0.75rem;
117
+ white-space: nowrap;
118
+
119
+ ${IconButton} {
120
+ padding: 4px 0;
121
+ background: none;
122
+ border: none;
123
+ width: auto;
124
+ height: auto;
125
+
126
+ svg {
127
+ width: auto;
128
+ height: 12px;
123
129
  }
124
130
  }
125
131
  }
126
132
  }
127
133
 
134
+ > .upload-bar {
135
+ --space-height: ${UPLOAD_BAR_HEIGHT - UPLOAD_BAR_DISPLACEMENT}px;
136
+ --content-height: ${UPLOAD_BAR_HEIGHT}px;
137
+ --list-margins: 12px; // margins from .list-overflow
138
+
139
+ .list-overflow {
140
+ margin: 0 6px !important;
141
+ .scroll-to-left, .scroll-to-right {
142
+ top: 11px;
143
+ }
144
+ }
145
+
146
+ input[type="file"] {
147
+ display: none;
148
+ }
149
+ }
150
+
128
151
  .wrapper-action {
129
152
  position: relative;
130
153
 
@@ -153,8 +176,6 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
153
176
  }
154
177
  }
155
178
 
156
-
157
-
158
179
  .button-group {
159
180
  display: flex;
160
181
  flex-direction: row;
@@ -252,6 +273,14 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
252
273
  }
253
274
  }
254
275
 
276
+ .message-menu {
277
+ position: absolute;
278
+ bottom: 34px;
279
+ .upload-item {
280
+ border-top: 1px solid ${theme.color.light[600]};
281
+ }
282
+ }
283
+
255
284
  textarea {
256
285
  resize: none;
257
286
  border: none;
@@ -1,4 +1,4 @@
1
- import { agentClient } from '@stack-spot/portal-network'
1
+ import { agentToolsClient } from '@stack-spot/portal-network'
2
2
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
3
3
  import { useEffect, useMemo } from 'react'
4
4
  import styled from 'styled-components'
@@ -40,8 +40,9 @@ const ToolsPanel = () => {
40
40
  const chat = widget.chatTabs.getAll().find(c => c.id === chatId)
41
41
  return chat?.getMessages().find(m => m.id === messageId)?.getValue()
42
42
  }, [messageId])
43
- const [agent] = agentClient.agent.useStatefulQuery({ agentId: message?.agent?.id ?? '' }, { enabled: !!message?.agent?.id })
44
- const tools = useMemo(() => message?.tools?.map(id => toolById(id, agent?.toolkits) ?? { id }), [messageId, agent])
43
+
44
+ const [toolKits] = agentToolsClient.tools.useStatefulQuery({ enabled: !!message?.agent?.id })
45
+ const tools = useMemo(() => message?.tools?.map(id => toolById(id, toolKits) ?? { id }), [messageId, toolKits])
45
46
 
46
47
  return !!tools?.length && (
47
48
  <ToolList>
@@ -60,7 +60,7 @@ export const WorkspaceResources = ({ workspaceId, allKS, agent, stack }: Omit<Ta
60
60
  const handleNavigate = (resource: WorkspaceResource) => {
61
61
  startTransition(() => {
62
62
  if (resource.resourceType === 'agent')
63
- navigate({ component: 'agent', props: { visibility: 'WORKSPACE', agent, workspaceId, showSubmitButton }, fullScreen: true })
63
+ navigate({ component: 'agent', props: { visibility: 'workspace', agent, workspaceId, showSubmitButton }, fullScreen: true })
64
64
 
65
65
  if (resource.resourceType === 'ks')
66
66
  navigate({ component: 'ks', props: { visibility: 'workspace', allKS, workspaceId, showSubmitButton }, fullScreen: true })
@@ -1 +0,0 @@
1
- {"version":3,"file":"InfoBar.d.ts","sourceRoot":"","sources":["../../../src/views/MessageInput/InfoBar.tsx"],"names":[],"mappings":"AA6BA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,+CA8EnB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"InfoBar.js","sourceRoot":"","sources":["../../../src/views/MessageInput/InfoBar.tsx"],"names":[],"mappings":"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"}