@stack-spot/ai-chat-widget 1.36.3-beta.1 → 1.36.4

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 (81) hide show
  1. package/CHANGELOG.md +39 -35
  2. package/dist/StackspotAIWidget.d.ts.map +1 -1
  3. package/dist/StackspotAIWidget.js +2 -1
  4. package/dist/StackspotAIWidget.js.map +1 -1
  5. package/dist/app-metadata.json +4 -4
  6. package/dist/components/FileDescription.d.ts +0 -1
  7. package/dist/components/FileDescription.d.ts.map +1 -1
  8. package/dist/components/FileDescription.js.map +1 -1
  9. package/dist/components/HistoryList.d.ts.map +1 -1
  10. package/dist/components/HistoryList.js +2 -0
  11. package/dist/components/HistoryList.js.map +1 -1
  12. package/dist/hooks/midnight-update-view.d.ts +5 -0
  13. package/dist/hooks/midnight-update-view.d.ts.map +1 -0
  14. package/dist/hooks/midnight-update-view.js +30 -0
  15. package/dist/hooks/midnight-update-view.js.map +1 -0
  16. package/dist/state/WidgetState.d.ts +1 -1
  17. package/dist/state/WidgetState.d.ts.map +1 -1
  18. package/dist/state/constants.d.ts.map +1 -1
  19. package/dist/state/constants.js +1 -0
  20. package/dist/state/constants.js.map +1 -1
  21. package/dist/utils/tools.d.ts +2 -1
  22. package/dist/utils/tools.d.ts.map +1 -1
  23. package/dist/utils/tools.js +12 -1
  24. package/dist/utils/tools.js.map +1 -1
  25. package/dist/views/Agents/AgentDescription.d.ts.map +1 -1
  26. package/dist/views/Agents/AgentDescription.js +26 -14
  27. package/dist/views/Agents/AgentDescription.js.map +1 -1
  28. package/dist/views/Agents/dictionary.d.ts +1 -1
  29. package/dist/views/Agents/dictionary.d.ts.map +1 -1
  30. package/dist/views/Agents/dictionary.js +2 -0
  31. package/dist/views/Agents/dictionary.js.map +1 -1
  32. package/dist/views/Agents/styled.d.ts.map +1 -1
  33. package/dist/views/Agents/styled.js +14 -3
  34. package/dist/views/Agents/styled.js.map +1 -1
  35. package/dist/views/Agents/useAgentFavorites.js +3 -3
  36. package/dist/views/Agents/useAgentFavorites.js.map +1 -1
  37. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  38. package/dist/views/Chat/ChatMessage.js +17 -9
  39. package/dist/views/Chat/ChatMessage.js.map +1 -1
  40. package/dist/views/Chat/styled.d.ts.map +1 -1
  41. package/dist/views/Chat/styled.js +24 -0
  42. package/dist/views/Chat/styled.js.map +1 -1
  43. package/dist/views/MessageInput/SelectContent.d.ts.map +1 -1
  44. package/dist/views/MessageInput/SelectContent.js +1 -1
  45. package/dist/views/MessageInput/SelectContent.js.map +1 -1
  46. package/dist/views/MessageInput/UploadBar.d.ts.map +1 -1
  47. package/dist/views/MessageInput/UploadBar.js +2 -3
  48. package/dist/views/MessageInput/UploadBar.js.map +1 -1
  49. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  50. package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
  51. package/dist/views/MessageInput/dictionary.js +2 -4
  52. package/dist/views/MessageInput/dictionary.js.map +1 -1
  53. package/dist/views/MessageInput/styled.js +8 -8
  54. package/dist/views/Resources.d.ts +2 -0
  55. package/dist/views/Resources.d.ts.map +1 -0
  56. package/dist/views/Resources.js +56 -0
  57. package/dist/views/Resources.js.map +1 -0
  58. package/dist/views/Steps/dictionary.d.ts +1 -1
  59. package/dist/views/Tools.js +4 -2
  60. package/dist/views/Tools.js.map +1 -1
  61. package/package.json +3 -3
  62. package/src/StackspotAIWidget.tsx +2 -0
  63. package/src/app-metadata.json +4 -4
  64. package/src/components/FileDescription.tsx +0 -1
  65. package/src/components/HistoryList.tsx +3 -0
  66. package/src/hooks/midnight-update-view.ts +36 -0
  67. package/src/state/WidgetState.ts +1 -1
  68. package/src/state/constants.ts +2 -1
  69. package/src/utils/tools.ts +23 -2
  70. package/src/views/Agents/AgentDescription.tsx +64 -23
  71. package/src/views/Agents/dictionary.ts +2 -0
  72. package/src/views/Agents/styled.ts +14 -3
  73. package/src/views/Agents/useAgentFavorites.ts +3 -3
  74. package/src/views/Chat/ChatMessage.tsx +33 -19
  75. package/src/views/Chat/styled.ts +24 -0
  76. package/src/views/MessageInput/SelectContent.tsx +3 -4
  77. package/src/views/MessageInput/UploadBar.tsx +2 -5
  78. package/src/views/MessageInput/dictionary.ts +2 -4
  79. package/src/views/MessageInput/styled.ts +8 -8
  80. package/src/views/Resources.tsx +99 -0
  81. package/src/views/Tools.tsx +19 -13
@@ -4,6 +4,7 @@ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
4
4
  import { groupBy, map } from 'lodash'
5
5
  import { useMemo } from 'react'
6
6
  import { styled } from 'styled-components'
7
+ import { useMidnightUpdateView } from '../hooks/midnight-update-view'
7
8
  import { subtractDays } from '../utils/date'
8
9
 
9
10
  type HistorySectionName = 'today' | 'yesterday' | 'last7' | 'last30' | 'older'
@@ -69,6 +70,8 @@ function dateToSectionName(date: Date): HistorySectionName {
69
70
  */
70
71
  export function HistoryList<T>({ getDate, items, keygen, className, style, renderItem }: Props<T>) {
71
72
  const t = useTranslate(dictionary)
73
+ useMidnightUpdateView()
74
+
72
75
  const sections = useMemo(() => {
73
76
  const byDate = groupBy(items, item => dateToSectionName(getDate(item)))
74
77
  return map(byDate, (value: T[], key: HistorySectionName) => (
@@ -0,0 +1,36 @@
1
+ import { useManualRender } from '@stack-spot/portal-components'
2
+ import { useEffect } from 'react'
3
+
4
+ /**
5
+ * Returns the number of milliseconds remaining until the next midnight.
6
+ */
7
+ function getTimeUntilMidnight() {
8
+ const now = new Date()
9
+ const midnight = new Date(now)
10
+ midnight.setHours(24, 0, 0, 0)
11
+ return midnight.getTime() - now.getTime()
12
+ }
13
+
14
+ /**
15
+ * Hook that triggers a component re-render at midnight every day.
16
+ */
17
+ export function useMidnightUpdateView() {
18
+ const { repaint } = useManualRender()
19
+
20
+ useEffect(() => {
21
+ let timer: ReturnType<typeof setTimeout> | undefined
22
+
23
+ const scheduleUpdate = () => {
24
+ const timeUntilMidnight = getTimeUntilMidnight()
25
+
26
+ timer = setTimeout(() => {
27
+ repaint()
28
+ scheduleUpdate()
29
+ }, timeUntilMidnight)
30
+ }
31
+
32
+ scheduleUpdate()
33
+
34
+ return () => { timer && clearTimeout(timer) }
35
+ }, [])
36
+ }
@@ -13,7 +13,7 @@ export interface WidgetProperties {
13
13
  /**
14
14
  * Current content of the right panel. Undefined for closed right panel.
15
15
  */
16
- panel?: 'stack' | 'workspace' | 'agent' | 'ks' | 'editor' | 'history' | 'ks-details' | 'steps' | 'tools',
16
+ panel?: 'stack' | 'workspace' | 'agent' | 'ks' | 'editor' | 'history' | 'ks-details' | 'steps' | 'tools' | 'resources',
17
17
  /**
18
18
  * KS to use when the right panel "ks-details" is open.
19
19
  */
@@ -4,7 +4,8 @@ export const acceptedFileTypes = [
4
4
  'json', 'yaml', 'txt', 'md', 'json', 'yaml', 'pdf', /*'xls',*/ 'xlsx', 'csv', 'cbl', 'cpp', 'cxx', 'cc', 'c', 'hpp', 'hxx', 'hh', 'h',
5
5
  'cs', 'go', 'html', 'htm', 'kt', 'kts', 'md', 'php', 'proto', 'py', 'java', 'js', 'jsx', 'ts', 'tsx', 'rst', 'rb', 'rs', 'scala', 'swift',
6
6
  'sql', 'yaml', 'yml', 'tf', 'sh', 'ps1', 'psd1', 'psm1', 'bat', 'cmd', 'rego', 'f', 'for', 'r', 'pl', 'vb', 'dart', 'hs', 'lua',
7
- 'asm', 'groovy', 'gvy', 'gy', 'mat', 'clj', 'lisp', 'm', 'cls', 'css', 'scss', 'json', 'jpg', 'jpeg', 'png', 'docx', 'pptx',
7
+ 'asm', 'groovy', 'gvy', 'gy', 'mat', 'clj', 'lisp', 'm', 'cls', 'css', 'scss', 'json', 'jpg', 'jpeg', 'png', 'docx', 'pptx',
8
+ 'tiff', 'tif', 'bmp',
8
9
  ]
9
10
 
10
11
  export const maxFileSize: FileSize = { value: 10, unit: 'MB' }
@@ -1,9 +1,30 @@
1
1
  import { BuiltinToolkitResponse, BuiltinToolResponse } from '@stack-spot/portal-network/api/agent'
2
+ import { CustomToolkitDto } from '@stack-spot/portal-network/api/agent-tools'
2
3
  import { keyBy } from 'lodash'
3
4
 
4
5
  export type ToolWithImage = BuiltinToolResponse & { id: string, image?: string }
5
6
 
6
- export function toolById(id: string, toolkits: BuiltinToolkitResponse[] | undefined): ToolWithImage | undefined {
7
- const tools = toolkits?.map(({ image_url, tools }) => tools?.map((tool) => ({ ...tool, id: tool.id!, image: image_url! }))).flat()
7
+ function isBuiltinToolkit(
8
+ toolkit: BuiltinToolkitResponse | CustomToolkitDto,
9
+ ): toolkit is BuiltinToolkitResponse {
10
+ return 'image_url' in toolkit
11
+ }
12
+
13
+
14
+ export function toolById(
15
+ id: string,
16
+ toolkits: (BuiltinToolkitResponse | CustomToolkitDto)[] | undefined,
17
+ ): ToolWithImage | undefined {
18
+ const tools = toolkits
19
+ ?.map((toolkit) =>
20
+ toolkit.tools?.map((tool) => ({
21
+ ...tool,
22
+ id: tool.id!,
23
+ image: isBuiltinToolkit(toolkit)
24
+ ? toolkit.image_url ?? undefined
25
+ : (toolkit as CustomToolkitDto).avatar ?? undefined,
26
+ })),
27
+ )
28
+ .flat()
8
29
  return keyBy(tools, 'id')[id]
9
30
  }
@@ -1,8 +1,8 @@
1
- import { Text } from '@citric/core'
2
- import { Badge, Skeleton } from '@citric/ui'
1
+ import { Box, Flex, IconBox, Text } from '@citric/core'
2
+ import { Agent, Cog } from '@citric/icons'
3
+ import { Avatar, Badge, Skeleton } from '@citric/ui'
3
4
  import { agentToolsClient } from '@stack-spot/portal-network'
4
5
  import { useMemo } from 'react'
5
- import { ToolBadge } from '../../components/ToolBadge'
6
6
  import { toolById } from '../../utils/tools'
7
7
  import { useAgentsDictionary } from './dictionary'
8
8
  import { AgentDescriptionBox } from './styled'
@@ -15,43 +15,80 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
15
15
 
16
16
  const knowledgeSources = useMemo(
17
17
  () => agent?.knowledge_sources_config?.knowledge_sources_details?.map((ks, index) => (
18
- <li key={index}><Badge palette="teal" appearance="square">{ks.name}</Badge></li>
18
+ <li key={index}>
19
+ <Box className="card-agent-info">
20
+ <Flex alignItems="center" justifyContent="space-between" pl={2}>
21
+ <Text colorScheme="light.contrastText">{ks.name}</Text>
22
+ <Badge palette="gray" appearance="square">{ks.type}</Badge>
23
+ </Flex>
24
+ </Box>
25
+ </li>
19
26
  )),
20
27
  [agent],
21
28
  )
22
29
  const skeleton = useMemo(() => {
23
30
  const loadingKS: React.ReactElement[] = []
24
31
  for (let i = 0; i < numberOfKnowledgeSources; i++) {
25
- loadingKS.push(<li key={i}><Badge palette="teal" appearance="square"><Skeleton className="ks-skeleton" /></Badge></li>)
32
+ loadingKS.push(<li key={i}><Skeleton className="ks-skeleton" /></li>)
26
33
  }
27
34
  return loadingKS
28
35
  }, [numberOfKnowledgeSources])
29
- const tools = useMemo(() => {
30
- const result: React.ReactElement[] = []
31
- const builtInTools = agent?.toolkits?.builtin_toolkits?.[0]?.tools
36
+
37
+ const { tools, multiAgents } = useMemo(() => {
38
+ const tools: React.ReactElement[] = []
39
+ const multiAgents: React.ReactElement[] = []
40
+ const builtInTools = agent?.toolkits?.builtin_toolkits ?? []
32
41
  const customToolkits = agent?.toolkits?.custom_toolkits ?? []
33
- for (const tool of builtInTools ?? []) {
34
- const toolWithImage = toolById(tool.id, toolKits)
35
- result.push(
36
- <li key={tool.id}>
37
- <ToolBadge
38
- name={toolWithImage?.name || toolWithImage?.id || 'unknown'}
39
- image={toolWithImage?.image}
40
- style={{ maxWidth: '150px' }}
41
- />
42
- </li>,
43
- )
42
+ for (const toolkit of builtInTools) {
43
+ for (const tool of toolkit.tools ?? []) {
44
+ if (toolkit.id == 'UTILITIES'){
45
+ const toolWithImage = toolById(tool.id, toolKits)
46
+ tools.push(
47
+ <li key={tool.id}>
48
+ <Box className="card-agent-info">
49
+ <Flex alignItems="center">
50
+ {toolWithImage?.image ?
51
+ <Avatar size="xxs" sx={{ mr: 3 }}> <img src={toolWithImage?.image} /></Avatar> :
52
+ <IconBox colorIcon="light" sx={{ mr: 3 }}><Cog /></IconBox>}
53
+ <Text colorScheme="light.contrastText">{toolWithImage?.name || toolWithImage?.id || 'unknown'}</Text>
54
+ </Flex>
55
+ </Box>
56
+ </li>,
57
+ )
58
+ }
59
+ if (toolkit.id == 'MULTI_AGENTS'){
60
+ multiAgents.push(
61
+ <li key={tool.id}>
62
+ <Box className="card-agent-info">
63
+ <Flex alignItems="center">
64
+ <IconBox colorIcon="light" sx={{ mr: 3 }}><Agent /></IconBox>
65
+ <Text colorScheme="light.contrastText">
66
+ {tool.name}
67
+ </Text>
68
+ </Flex>
69
+ </Box>
70
+ </li>,
71
+ )
72
+ }
73
+ }
44
74
  }
45
75
  for (const toolkit of customToolkits) {
46
76
  for (const tool of toolkit.tools) {
47
- result.push(
77
+ tools.push(
48
78
  <li key={tool.id}>
49
- <ToolBadge name={tool.name} image={toolkit.avatar ?? undefined} style={{ maxWidth: '150px' }} />
79
+ <Box className="card-agent-info">
80
+ <Flex alignItems="center">
81
+ {toolkit.avatar ?
82
+ <Avatar size="xxs" sx={{ mr: 3 }}><img src={toolkit.avatar} /></Avatar> :
83
+ <IconBox colorIcon="light" sx={{ mr: 3 }}><Cog /></IconBox>}
84
+ <Text colorScheme="light.contrastText">{tool.name}</Text>
85
+ </Flex>
86
+ </Box>
50
87
  </li>,
51
88
  )
52
89
  }
53
90
  }
54
- return result
91
+ return { tools, multiAgents }
55
92
  }, [agent])
56
93
 
57
94
  return (
@@ -64,10 +101,14 @@ export const AgentDescription = ({ agentId }: { agentId?: string }) => {
64
101
  <Text appearance="microtext1" className="title">Knowledge sources</Text>
65
102
  <ul>{isLoading || isLoadingToolKit ? skeleton : knowledgeSources}</ul>
66
103
  </section>}
67
- {!!tools.length && <section>
104
+ {!!tools?.length && <section>
68
105
  <Text appearance="microtext1" className="title">{t.tools}</Text>
69
106
  <ul>{tools}</ul>
70
107
  </section>}
108
+ {!!multiAgents?.length && <section>
109
+ <Text appearance="microtext1" className="title">{t.multiAgent}</Text>
110
+ <ul>{multiAgents}</ul>
111
+ </section>}
71
112
  {agent?.model_name && <section>
72
113
  <Text appearance="microtext1" className="title">LLM</Text>
73
114
  <Badge palette="orange" appearance="square">{agent?.model_name}</Badge>
@@ -18,6 +18,7 @@ const dictionary = {
18
18
  favorites: 'Favorites',
19
19
  tools: 'Tools',
20
20
  spots: 'Spots',
21
+ multiAgent: 'Multi-agents',
21
22
  },
22
23
  pt: {
23
24
  title: 'Agentes',
@@ -36,6 +37,7 @@ const dictionary = {
36
37
  favorites: 'Favoritos',
37
38
  tools: 'Ferramentas',
38
39
  spots: 'Spots',
40
+ multiAgent: 'Multi-agents',
39
41
 
40
42
  },
41
43
  } satisfies Dictionary
@@ -18,9 +18,11 @@ export const AgentLabel = styled.div`
18
18
  export const AgentDescriptionBox = styled.div`
19
19
  color: ${theme.color.light[700]};
20
20
  line-height: 18px;
21
+ background-color: ${theme.color.light[400]};
22
+ margin: -10px;
23
+ padding: 8px;
21
24
 
22
25
  section {
23
- border-bottom: 1px solid ${theme.color.light[600]};
24
26
  padding-bottom: 10px;
25
27
  margin-bottom: 10px;
26
28
 
@@ -34,6 +36,14 @@ export const AgentDescriptionBox = styled.div`
34
36
  display: block;
35
37
  margin-bottom: 6px;
36
38
  }
39
+
40
+ .card-agent-info {
41
+ background-color: ${theme.color.light[500]};
42
+ border: 1px solid ${theme.color.light[600]};
43
+ border-radius: 3px;
44
+ padding: 4px;
45
+ width: 100%;
46
+ }
37
47
  }
38
48
 
39
49
  ul {
@@ -44,10 +54,11 @@ export const AgentDescriptionBox = styled.div`
44
54
  flex-direction: row;
45
55
  flex-wrap: wrap;
46
56
  white-space: nowrap;
47
- gap: 6px;
48
57
 
49
58
  li {
50
- margin: 3px 0;
59
+ margin-bottom: 3px;
60
+ display: flex;
61
+ width: 100%;
51
62
  }
52
63
  }
53
64
 
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable filenames/match-regex */
2
- import { agentClient, agentToolsClient } from '@stack-spot/portal-network'
2
+ import { agentToolsClient } from '@stack-spot/portal-network'
3
3
 
4
4
  export function useAgentFavorites() {
5
5
  const useFavorites = () => agentToolsClient.agents.useQuery({ visibility: 'favorite' })
6
- const [addFavorite, pendingAddFav] = agentClient.addFavoriteAgent.useMutation()
7
- const [removeFavorite, pendingRemoveFav] = agentClient.removeFavoriteAgent.useMutation()
6
+ const [addFavorite, pendingAddFav] = agentToolsClient.addFavorite.useMutation()
7
+ const [removeFavorite, pendingRemoveFav] = agentToolsClient.removeFavorite.useMutation()
8
8
 
9
9
  const removeFavoriteAgent = async (idOrSlug?: string) => {
10
10
  try {
@@ -1,6 +1,6 @@
1
1
  import { Box, Button, Checkbox, Flex, IconBox, Input, Label, Radio, Text } from '@citric/core'
2
- import { Check, Cog, Copy, Dislike, DislikeFill, Like, LikeFill, TimesCircle } from '@citric/icons'
3
- import { Badge, IconButton, Tooltip } from '@citric/ui'
2
+ import { Agent, Check, Cog, Copy, Dislike, DislikeFill, Like, LikeFill, TimesCircle } from '@citric/icons'
3
+ import { Avatar, AvatarGroup, Badge, IconButton, Tooltip } from '@citric/ui'
4
4
  import { agentToolsClient } from '@stack-spot/portal-network'
5
5
  import { listToClass } from '@stack-spot/portal-theme'
6
6
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
@@ -10,8 +10,8 @@ import { PhoneInput } from 'react-international-phone'
10
10
  import 'react-international-phone/style.css'
11
11
  import { FileDescription } from '../../components/FileDescription'
12
12
  import { Markdown } from '../../components/Markdown'
13
- import { StackedBadge } from '../../components/StackedBadge'
14
13
  import { useChatEntry, useCurrentChat, useWidget } from '../../context/hooks'
14
+ import { useMidnightUpdateView } from '../../hooks/midnight-update-view'
15
15
  import { ChatEntry, SerializableAction, TextChatEntry } from '../../state/ChatEntry'
16
16
  import { useDateFormatter } from '../../utils/date'
17
17
  import { toolById } from '../../utils/tools'
@@ -194,10 +194,13 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage }: Pr
194
194
  const chat = useCurrentChat()
195
195
  const agentId = entry.agent?.id ?? ''
196
196
  const [toolKits] = agentToolsClient.tools.useStatefulQuery({}, { enabled: !!agentId })
197
+ const [agentsTools] = agentToolsClient.agentsByIds.useStatefulQuery(
198
+ { searchAgentsRequest: { ids: entry.tools || [''] } }, { enabled: !!entry.tools })
197
199
  const [copied, setCopied] = useState(false)
198
200
  const [showUserButtonCopy, setShowUserButtonCopy] = useState(false)
199
201
 
200
202
  useChatScrollToBottomEffect(ref, [entry])
203
+ useMidnightUpdateView()
201
204
 
202
205
  const detailKS = useCallback(({ name, slug, documentScore, documentId }: Required<TextChatEntry>['knowledgeSources'][number]) => {
203
206
  widget.set('currentKSInPanel', { name, slug, score: documentScore, documentId })
@@ -307,12 +310,11 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage }: Pr
307
310
  </form>
308
311
  }
309
312
 
310
- function openToolsPanel() {
313
+ function openResourcesPanel() {
311
314
  widget.set('currentMessageInPanel', { chatId: chat.id, messageId: message.id })
312
- widget.set('panel', 'tools')
315
+ widget.set('panel', 'resources')
313
316
  }
314
317
 
315
-
316
318
  return (entry.content || entry.error || !!entry.steps?.length || entry.upload?.length) && (
317
319
  <li key={entry.messageId} className={entry.agentType} ref={ref}>
318
320
  <div className="chat-message-container"
@@ -340,19 +342,7 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage }: Pr
340
342
  )}
341
343
  </div>
342
344
  {afterMessage && createElement(afterMessage, { message })}
343
- {!!entry.tools?.length && <StackedBadge
344
- aria-label={t.openToolsPanel}
345
- title={t.openToolsPanel}
346
- tabIndex={0}
347
- role="button"
348
- className="tools-badge"
349
- label={t.tools}
350
- images={entry.tools.slice(0, 3).map((id) => {
351
- const tool = toolById(id, toolKits)
352
- return { key: id, name: tool?.name || id, icon: <Cog />, url: tool?.image }
353
- })}
354
- onClick={openToolsPanel}
355
- />}
345
+
356
346
  {!!entry.knowledgeSources?.length && <div className="ks-box">
357
347
  <Text appearance="microtext1" colorScheme="light.700">Knowledge Sources:</Text>
358
348
  <ul>{entry.knowledgeSources.map((ks, index) => (
@@ -361,6 +351,28 @@ export const ChatMessage = ({ message, isLast, beforeMessage, afterMessage }: Pr
361
351
  </li>
362
352
  ))}</ul>
363
353
  </div>}
354
+
355
+ {(!!agentsTools?.length || !!entry.tools?.length) && <div className="tools-box">
356
+ <AvatarGroup onClick={openResourcesPanel} aria-label={t.openResourcesPanel} role="button">
357
+ {agentsTools?.map((agent) => (
358
+ <Avatar size="xxs" key={agent.id} className="agent-info-avatar-resource">
359
+ {agent.avatar ?
360
+ <img alt={agent.name} aria-label={agent.name} title={agent.name} src={agent.avatar} /> :
361
+ <IconBox appearance="circle" aria-label={agent.name} title={agent.name} color="gray"><Agent /></IconBox>}
362
+ </Avatar>
363
+ ))}
364
+ {entry.tools?.map((id) => {
365
+ const tool = toolById(id, toolKits)
366
+ return (
367
+ <Avatar size="xxs" key={id} className="agent-info-avatar-resource">
368
+ {tool?.image ?
369
+ <img alt={tool.name} aria-label={tool.name} title={tool.name} src={tool.image} /> :
370
+ <IconBox appearance="circle" aria-label={tool?.name} title={tool?.name} color="gray"><Cog /></IconBox>}
371
+ </Avatar>
372
+ )})}
373
+
374
+ </AvatarGroup>
375
+ </div>}
364
376
 
365
377
  {shouldShowFooter && <div className="message-footer">
366
378
  {entry.agentType === 'bot' && !entry.error && <div className="message-actions">
@@ -450,6 +462,7 @@ const dictionary = {
450
462
  dislike: 'Dislike',
451
463
  tools: 'Tools',
452
464
  openToolsPanel: 'Open the tools panel to see more details.',
465
+ openResourcesPanel: 'Open the resources panel to see more details.',
453
466
  copied: 'Copied',
454
467
  },
455
468
  pt: {
@@ -458,6 +471,7 @@ const dictionary = {
458
471
  dislike: 'Não gostei',
459
472
  tools: 'Ferramentas',
460
473
  openToolsPanel: 'Abrir o painel de ferramentas para ver mais detalhes.',
474
+ openResourcesPanel: 'Abrir o painel de recursos para ver mais detalhes.',
461
475
  copied: 'Copiado',
462
476
  },
463
477
  } satisfies Dictionary
@@ -219,6 +219,30 @@ export const ChatList: IStyledComponentBase<
219
219
  }
220
220
  }
221
221
 
222
+ .tools-box {
223
+
224
+ > ul {
225
+ display: flex;
226
+ flex-direction: row;
227
+ flex-wrap: wrap;
228
+ white-space: nowrap;
229
+ margin: 0;
230
+ margin-top: 8px;
231
+ padding: 0;
232
+ list-style: none;
233
+ gap: 6px;
234
+
235
+ &:hover{
236
+ cursor: pointer;
237
+
238
+ .agent-info-avatar-resource {
239
+ transition: margin-left 0.3s ease-in;
240
+ margin-left: 0px;
241
+ }
242
+ }
243
+ }
244
+ }
245
+
222
246
  .steps {
223
247
  ul {
224
248
  list-style: none;
@@ -91,10 +91,9 @@ export const SelectContent = () => {
91
91
  id="button-content-select"
92
92
  color="light"
93
93
  appearance="square"
94
- role="button"
95
- title={visibleMenu ? t.collapse && t.chatViewMenu : t.expand}
94
+ title={t.chatViewMenu}
96
95
  data-test-hint="button-options"
97
- aria-label={visibleMenu ? t.collapse : t.expand && t.chatMenu}
96
+ aria-label={t.chatViewMenu}
98
97
  aria-controls="chatMessageMenu"
99
98
  aria-expanded={visibleMenu}
100
99
  onClick={() => setVisibleMenu(state => !state)}>
@@ -106,7 +105,7 @@ export const SelectContent = () => {
106
105
  visible={visibleMenu}
107
106
  onHide={() => setVisibleMenu(false)}
108
107
  items={listItems}
109
- aria-label="Menu de opções do chat"
108
+ aria-label={t.chatViewMenu}
110
109
  />
111
110
  </>
112
111
  ) : (
@@ -22,7 +22,6 @@ const UploadItem = ({ upload }: UploadedFileProps) => {
22
22
  const uploadManager = useUploadManager()
23
23
  const icon = upload.file.type.toLowerCase().startsWith('image/') ? createImageFromFile(upload.file) : undefined
24
24
  const status = useUploadStatus(upload)
25
- const errorMessage = status === 'error' ? (upload.error?.message || 'Falha ao anexar o arquivo') : undefined
26
25
 
27
26
  return <FileDescription
28
27
  fileName={upload.file.name}
@@ -30,7 +29,6 @@ const UploadItem = ({ upload }: UploadedFileProps) => {
30
29
  onRemove={() => uploadManager.remove(upload)}
31
30
  onRetry={() => upload.retry()}
32
31
  status={status}
33
- errorMessage={errorMessage}
34
32
  />
35
33
  }
36
34
 
@@ -62,7 +60,7 @@ export const UploadBar = () => {
62
60
  if (uploads.some(up => up.status === 'error')) {
63
61
  setTimeout(() => setAriaMessage(''), 2000)
64
62
  }
65
- }, [uploads, t.uploadSuccessStatus])
63
+ }, [uploads])
66
64
 
67
65
  useUploadErrorEffect((errors) => {
68
66
  const sizeErrors = errors.filter(e => e instanceof FileIsTooLarge)
@@ -89,8 +87,7 @@ export const UploadBar = () => {
89
87
  chat.pushMessage(new ChatEntry({ agentType: 'system', type: 'md', content: lines.join('\n\n') }))
90
88
  }
91
89
  })
92
-
93
-
90
+
94
91
  return (
95
92
  <div className={listToClass(['info-bar', 'upload-bar', visible && 'visible'])}>
96
93
  <div className="aria-live"
@@ -34,8 +34,7 @@ const dictionary = {
34
34
  cantSendBecausePromptMaxLength: 'You can\'t send messages longer than $0 characters. Please, shorten your message.',
35
35
  chatAgent: 'Agents',
36
36
  uploadSuccessStatus: 'File sent successfully',
37
- chatMenu: 'Chat options menu',
38
- chatViewMenu: 'Viewing menu options',
37
+ chatViewMenu: 'Chat options menu',
39
38
  },
40
39
  pt: {
41
40
  stack: 'Selecionar stack',
@@ -70,8 +69,7 @@ const dictionary = {
70
69
  cantSendBecausePromptMaxLength: 'Você não pode enviar mensagens com mais de $0 caracteres. Por favor, reduza sua mensagem.',
71
70
  chatAgent: 'Agentes',
72
71
  uploadSuccessStatus: 'Arquivo anexado com sucesso',
73
- chatMenu: 'Menu de opções do chat',
74
- chatViewMenu: 'Visualizando as opções de menu',
72
+ chatViewMenu: 'Menu de opções do chat',
75
73
  },
76
74
  } satisfies Dictionary
77
75
 
@@ -67,14 +67,14 @@ export const MessageInputBox = styled.div<{$inputFocused?: boolean}>`
67
67
 
68
68
  > .aria-live {
69
69
  position: absolute;
70
- width: 1;
71
- height: 1;
72
- margin: -1;
73
- padding: 0;
74
- overflow: hidden;
75
- clip: rect(0 0 0 0);
76
- white-Space: nowrap;
77
- border: 0;
70
+ width: 1px;
71
+ height: 1px;
72
+ margin: -1px;
73
+ padding: 0;
74
+ overflow: hidden;
75
+ clip: rect(0 0 0 0);
76
+ white-space: nowrap;
77
+ border: 0;
78
78
  }
79
79
 
80
80
  > .space {
@@ -0,0 +1,99 @@
1
+ import { Box, Flex, IconBox, Text } from '@citric/core'
2
+ import { Agent } from '@citric/icons'
3
+ import { Avatar } from '@citric/ui'
4
+ import { agentToolsClient } from '@stack-spot/portal-network'
5
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
6
+ import { useEffect, useMemo } from 'react'
7
+ import { Accordion } from '../components/Accordion'
8
+ import { useWidget, useWidgetState } from '../context/hooks'
9
+ import { useRightPanel } from '../right-panel/hooks'
10
+ import { toolById } from '../utils/tools'
11
+ import { AgentDescription } from './Agents/AgentDescription'
12
+
13
+ export const Resources = () => {
14
+ const t = useTranslate(dictionary)
15
+ const panel = useWidgetState('panel')
16
+ const message = useWidgetState('currentMessageInPanel')
17
+ const { open } = useRightPanel()
18
+ const widget = useWidget()
19
+
20
+ useEffect(() => {
21
+ if (panel === 'resources' && message) open(
22
+ <ResourcesPanel key={message.messageId} />,
23
+ { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) },
24
+ )
25
+ }, [panel, t])
26
+
27
+ return null
28
+ }
29
+
30
+ const ResourcesPanel = () => {
31
+ const { chatId, messageId } = useWidgetState('currentMessageInPanel') ?? {}
32
+ const widget = useWidget()
33
+ const message = useMemo(() => {
34
+ const chat = widget.chatTabs.getAll().find(c => c.id === chatId)
35
+ return chat?.getMessages().find(m => m.id === messageId)?.getValue()
36
+ }, [messageId])
37
+
38
+ const [toolKits] = agentToolsClient.tools.useStatefulQuery({}, { enabled: !!message?.agent?.id })
39
+ const [agent] = agentToolsClient.agent.useStatefulQuery({ agentId: message?.agent?.id || '' },
40
+ { enabled: !!message?.agent?.id })
41
+ const tools = useMemo(() => message?.tools?.map(id => toolById(id, toolKits)), [messageId, toolKits])
42
+ const customTools = useMemo(() => message?.tools?.map(id => toolById(id, agent?.toolkits?.custom_toolkits)),
43
+ [messageId, agent?.toolkits?.custom_toolkits])
44
+
45
+ const [agentsTools] = agentToolsClient.agentsByIds.useStatefulQuery({ searchAgentsRequest:{ ids: message?.tools || [] } })
46
+ const hasAgentTool = useMemo(() => message?.tools?.some(id => agentsTools?.find((agent) => agent.id === id)), [messageId, toolKits])
47
+
48
+ const header = (image?: string, label?: string) => (
49
+ <Flex alignItems="center">
50
+ {image ? <Avatar size="xxs">
51
+ <img title={label} src={image} />
52
+ </Avatar> :
53
+ <IconBox appearance="circle" title={label} color="gray"><Agent /></IconBox>}
54
+ <Text ml={3}>{label}</Text>
55
+ </Flex>
56
+ )
57
+
58
+ return !!(tools?.length || customTools?.length) && (
59
+ <>
60
+ <>
61
+ {[...(tools || []), ...(customTools || [])].map(
62
+ (tool) =>
63
+ tool && (
64
+ <Box key={tool.id} mt={3}>
65
+ <Accordion header={header(tool?.image, tool?.name)}>
66
+ <Box sx={{ backgroundColor: 'light.400', m: '-10px', p: '16px' }}>
67
+ {tool?.description}
68
+ </Box>
69
+ </Accordion>
70
+ </Box>))}
71
+ </>
72
+ {
73
+ hasAgentTool &&
74
+ <>
75
+ {message?.tools?.map((id) => {
76
+ const agentTool = agentsTools?.find((agent) => agent.id === id)
77
+ return (
78
+ <Box key={id} mt={3}>
79
+ <Accordion header={header(agentTool?.avatar || undefined, agentTool?.name)}>
80
+ <AgentDescription agentId={id} />
81
+ </Accordion>
82
+ </Box>)},
83
+ )}
84
+ </>
85
+ }
86
+ </>
87
+ )
88
+ }
89
+
90
+ const dictionary = {
91
+ en: {
92
+ title: 'Resources',
93
+ description: 'These are the resources used to generate this answer.',
94
+ },
95
+ pt: {
96
+ title: 'Recursos',
97
+ description: 'Esses são os recursos usados pra gerar essa resposta.',
98
+ },
99
+ } satisfies Dictionary