@stack-spot/ai-chat-widget 0.1.0 → 0.2.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 (95) hide show
  1. package/dist/StackspotAIWidget.d.ts.map +1 -1
  2. package/dist/StackspotAIWidget.js +3 -1
  3. package/dist/StackspotAIWidget.js.map +1 -1
  4. package/dist/chat-interceptors/send-message.d.ts.map +1 -1
  5. package/dist/chat-interceptors/send-message.js +19 -7
  6. package/dist/chat-interceptors/send-message.js.map +1 -1
  7. package/dist/components/RightPanelTabs.d.ts.map +1 -1
  8. package/dist/components/RightPanelTabs.js +1 -0
  9. package/dist/components/RightPanelTabs.js.map +1 -1
  10. package/dist/components/form/styled.d.ts.map +1 -1
  11. package/dist/components/form/styled.js +2 -1
  12. package/dist/components/form/styled.js.map +1 -1
  13. package/dist/context/hooks.d.ts.map +1 -1
  14. package/dist/context/hooks.js +1 -5
  15. package/dist/context/hooks.js.map +1 -1
  16. package/dist/features.d.ts.map +1 -1
  17. package/dist/features.js +1 -0
  18. package/dist/features.js.map +1 -1
  19. package/dist/right-panel/DefaultPanel.d.ts +2 -2
  20. package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
  21. package/dist/right-panel/DefaultPanel.js +2 -1
  22. package/dist/right-panel/DefaultPanel.js.map +1 -1
  23. package/dist/right-panel/hooks.d.ts +2 -2
  24. package/dist/right-panel/hooks.d.ts.map +1 -1
  25. package/dist/state/ChatEntry.d.ts +7 -0
  26. package/dist/state/ChatEntry.d.ts.map +1 -1
  27. package/dist/state/ChatEntry.js +0 -3
  28. package/dist/state/ChatEntry.js.map +1 -1
  29. package/dist/state/ChatState.d.ts +4 -1
  30. package/dist/state/ChatState.d.ts.map +1 -1
  31. package/dist/state/ChatState.js.map +1 -1
  32. package/dist/state/WidgetState.d.ts +19 -8
  33. package/dist/state/WidgetState.d.ts.map +1 -1
  34. package/dist/state/WidgetState.js +0 -19
  35. package/dist/state/WidgetState.js.map +1 -1
  36. package/dist/utils/chat.js +1 -1
  37. package/dist/utils/chat.js.map +1 -1
  38. package/dist/utils/knowledge-source.d.ts +7 -0
  39. package/dist/utils/knowledge-source.d.ts.map +1 -0
  40. package/dist/utils/knowledge-source.js +38 -0
  41. package/dist/utils/knowledge-source.js.map +1 -0
  42. package/dist/views/Agents.d.ts.map +1 -1
  43. package/dist/views/Agents.js +129 -1
  44. package/dist/views/Agents.js.map +1 -1
  45. package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
  46. package/dist/views/Chat/ChatMessage.js +9 -4
  47. package/dist/views/Chat/ChatMessage.js.map +1 -1
  48. package/dist/views/Chat/styled.d.ts.map +1 -1
  49. package/dist/views/Chat/styled.js +24 -0
  50. package/dist/views/Chat/styled.js.map +1 -1
  51. package/dist/views/KSDocument.d.ts +2 -0
  52. package/dist/views/KSDocument.d.ts.map +1 -0
  53. package/dist/views/KSDocument.js +40 -0
  54. package/dist/views/KSDocument.js.map +1 -0
  55. package/dist/views/KnowledgeSources.js +7 -5
  56. package/dist/views/KnowledgeSources.js.map +1 -1
  57. package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
  58. package/dist/views/MessageInput/ButtonGroup.js +5 -3
  59. package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
  60. package/dist/views/MessageInput/dictionary.d.ts +1 -1
  61. package/dist/views/MessageInput/index.d.ts.map +1 -1
  62. package/dist/views/MessageInput/index.js +2 -4
  63. package/dist/views/MessageInput/index.js.map +1 -1
  64. package/dist/views/MessageInput/styled.d.ts +2 -0
  65. package/dist/views/MessageInput/styled.d.ts.map +1 -1
  66. package/dist/views/MessageInput/styled.js +11 -3
  67. package/dist/views/MessageInput/styled.js.map +1 -1
  68. package/dist/views/Stacks.js +7 -5
  69. package/dist/views/Stacks.js.map +1 -1
  70. package/dist/views/Workspaces.js +7 -5
  71. package/dist/views/Workspaces.js.map +1 -1
  72. package/package.json +2 -2
  73. package/src/StackspotAIWidget.tsx +4 -0
  74. package/src/chat-interceptors/send-message.ts +20 -8
  75. package/src/components/RightPanelTabs.tsx +1 -0
  76. package/src/components/form/styled.ts +2 -1
  77. package/src/context/hooks.ts +1 -4
  78. package/src/features.ts +1 -0
  79. package/src/right-panel/DefaultPanel.tsx +5 -4
  80. package/src/right-panel/hooks.tsx +2 -2
  81. package/src/state/ChatEntry.ts +8 -3
  82. package/src/state/ChatState.ts +5 -1
  83. package/src/state/WidgetState.ts +14 -26
  84. package/src/utils/chat.ts +1 -1
  85. package/src/utils/knowledge-source.ts +43 -0
  86. package/src/views/Agents.tsx +186 -1
  87. package/src/views/Chat/ChatMessage.tsx +18 -4
  88. package/src/views/Chat/styled.ts +24 -0
  89. package/src/views/KSDocument.tsx +58 -0
  90. package/src/views/KnowledgeSources.tsx +8 -5
  91. package/src/views/MessageInput/ButtonGroup.tsx +9 -7
  92. package/src/views/MessageInput/index.tsx +2 -5
  93. package/src/views/MessageInput/styled.ts +11 -3
  94. package/src/views/Stacks.tsx +8 -5
  95. package/src/views/Workspaces.tsx +8 -5
@@ -0,0 +1,58 @@
1
+ import { Flex, IconBox, Text } from '@citric/core'
2
+ import { Score } from '@citric/icons'
3
+ import { aiClient } from '@stack-spot/portal-network'
4
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
5
+ import { useEffect } from 'react'
6
+ import { Code } from '../components/Code'
7
+ import { useWidget, useWidgetState } from '../context/hooks'
8
+ import { useRightPanel } from '../right-panel/hooks'
9
+ import { extractCodeFromKSDocument } from '../utils/knowledge-source'
10
+
11
+ export const KSDocument = () => {
12
+ const t = useTranslate(dictionary)
13
+ const panel = useWidgetState('panel')
14
+ const ks = useWidgetState('currentKSInPanel')
15
+ const { open } = useRightPanel()
16
+ const widget = useWidget()
17
+
18
+ useEffect(() => {
19
+ if (panel === 'ks-details' && ks) open(
20
+ <KSDocumentPanel documentId={ks.documentId} slug={ks.slug} />,
21
+ {
22
+ title: (
23
+ <Flex flexDirection="row" alignItems="center" flex="1">
24
+ <Text appearance="h5" style={{ flex: 1 }}>{ks.name}</Text>
25
+ <Flex flexDirection="row" alignItems="center" style={{ gap: '5px' }} title="Score" aria-label="Score">
26
+ <IconBox><Score /></IconBox>
27
+ <Text>{ks.score.toFixed(2)}</Text>
28
+ </Flex>
29
+ </Flex>
30
+ ),
31
+ description: t.description,
32
+ onClose: () => widget.set('panel', undefined),
33
+ },
34
+ )
35
+ }, [panel, ks, t])
36
+
37
+ return null
38
+ }
39
+
40
+ const KSDocumentPanel = ({ slug, documentId }: { slug: string, documentId: string }) => {
41
+ const document = aiClient.knowledgeSourceDocument.useQuery({ slug, customId: documentId })
42
+ const { snippet, language, text } = extractCodeFromKSDocument(document)
43
+ return (
44
+ <>
45
+ {text && <Text>{text}</Text>}
46
+ <Code language={language}>{snippet}</Code>
47
+ </>
48
+ )
49
+ }
50
+
51
+ const dictionary = {
52
+ en: {
53
+ description: 'Knowledge Source details',
54
+ },
55
+ pt: {
56
+ description: 'Detalhes do Knowledge Source',
57
+ },
58
+ } satisfies Dictionary
@@ -13,16 +13,16 @@ import { useRightPanel } from '../right-panel/hooks'
13
13
 
14
14
  export const KnowledgeSources = () => {
15
15
  const t = useTranslate(dictionary)
16
- const isKnowledgeSourceSelectionOpen = useWidgetState('isKnowledgeSourceSelectionOpen')
16
+ const panel = useWidgetState('panel')
17
17
  const { open } = useRightPanel()
18
18
  const widget = useWidget()
19
19
 
20
20
  useEffect(() => {
21
- if (isKnowledgeSourceSelectionOpen) open(
21
+ if (panel === 'ks') open(
22
22
  <KnowledgeSourcesPanel />,
23
- { title: t.title, description: t.description, onClose: () => widget.set('isKnowledgeSourceSelectionOpen', false) },
23
+ { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) },
24
24
  )
25
- }, [isKnowledgeSourceSelectionOpen, t])
25
+ }, [panel, t])
26
26
 
27
27
  return null
28
28
  }
@@ -74,7 +74,10 @@ const KnowledgeSourcesTab = ({ visibility }: { visibility: VisibilityLevelEnum }
74
74
  }}
75
75
  renderLabel={ks => ks.name}
76
76
  renderDescription={ks => ks.description}
77
- optionClassName={ks => (filter && !ks.name.includes(filter) && value.includes(ks)) ? 'filtered-out' : ''}
77
+ optionClassName={ks => (filter && !ks.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()) && value.includes(ks))
78
+ ? 'filtered-out'
79
+ : ''
80
+ }
78
81
  className="option-list"
79
82
  />}
80
83
  {!!knowledgeSources.length && !filtered.length && (
@@ -1,8 +1,9 @@
1
- import { ChevronRight, FaceSmile, KnowledgeSource, Send, Stack, Times, Workspace } from '@citric/icons'
1
+ import { ChevronRight, KnowledgeSource, Send, Stack, Times, Workspace } from '@citric/icons'
2
2
  import { IconButton } from '@citric/ui'
3
+ import { MiniLogo } from '@stack-spot/portal-components/svg'
3
4
  import { listToClass } from '@stack-spot/portal-theme'
4
5
  import { useEffect, useRef } from 'react'
5
- import { useWidget } from '../../context/hooks'
6
+ import { useCurrentChatState, useWidget } from '../../context/hooks'
6
7
  import { MessageInputFeatures } from '../../features'
7
8
  import { useMessageInputDictionary } from './dictionary'
8
9
 
@@ -20,6 +21,7 @@ export const ButtonGroup = ({ features, onSend, onCancel, expanded, setExpanded,
20
21
  const widget = useWidget()
21
22
  const featureButtonsWidth = useRef<number | undefined>()
22
23
  const featureButtons = useRef<HTMLDivElement>(null)
24
+ const agent = useCurrentChatState('agent')
23
25
  const hasFeatureButtons = features.agent || features.workspace || features.knowledgeSource || features.stack
24
26
 
25
27
  useEffect(() => {
@@ -37,12 +39,12 @@ export const ButtonGroup = ({ features, onSend, onCancel, expanded, setExpanded,
37
39
  style={{ width: expanded ? featureButtonsWidth.current : 0 }}
38
40
  >
39
41
  {features.agent && (
40
- <IconButton aria-label={t.agent} title={t.agent} onClick={() => widget.set('isAgentSelectionOpen', true)}>
41
- <FaceSmile />
42
+ <IconButton aria-label={t.agent} title={t.agent} className="agent" onClick={() => widget.set('panel', 'agent')}>
43
+ {agent?.image ? <img src={agent.image} /> : <MiniLogo />}
42
44
  </IconButton>
43
45
  )}
44
46
  {features.workspace && (
45
- <IconButton aria-label={t.workspace} title={t.workspace} onClick={() => widget.set('isWorkspaceSelectionOpen', true)}>
47
+ <IconButton aria-label={t.workspace} title={t.workspace} onClick={() => widget.set('panel', 'workspace')}>
46
48
  <Workspace />
47
49
  </IconButton>
48
50
  )}
@@ -50,13 +52,13 @@ export const ButtonGroup = ({ features, onSend, onCancel, expanded, setExpanded,
50
52
  <IconButton
51
53
  aria-label={t.knowledgeSource}
52
54
  title={t.knowledgeSource}
53
- onClick={() => widget.set('isKnowledgeSourceSelectionOpen', true)}
55
+ onClick={() => widget.set('panel', 'ks')}
54
56
  >
55
57
  <KnowledgeSource />
56
58
  </IconButton>
57
59
  )}
58
60
  {features.stack && (
59
- <IconButton aria-label={t.stack} title={t.stack} onClick={() => widget.set('isStackSelectionOpen', true)}>
61
+ <IconButton aria-label={t.stack} title={t.stack} onClick={() => widget.set('panel', 'stack')}>
60
62
  <Stack />
61
63
  </IconButton>
62
64
  )}
@@ -9,15 +9,12 @@ import { ChatEntry } from '../../state/ChatEntry'
9
9
  import { ButtonGroup } from './ButtonGroup'
10
10
  import { useMessageInputDictionary } from './dictionary'
11
11
  import { InfoBar } from './InfoBar'
12
- import { MessageInputBox } from './styled'
12
+ import { MAX_INPUT_HEIGHT, MessageInputBox, MIN_INPUT_HEIGHT } from './styled'
13
13
 
14
14
  interface Props {
15
15
  features: MessageInputFeatures,
16
16
  }
17
17
 
18
- const MAX_INPUT_HEIGHT = 300
19
- const MAX_INPUT_HEIGHT_MINIMIZED = 30
20
-
21
18
  export const MessageInput = ({ features }: Props) => {
22
19
  const t = useMessageInputDictionary()
23
20
  const [focused, setFocused] = useState(false)
@@ -58,7 +55,7 @@ export const MessageInput = ({ features }: Props) => {
58
55
  onKeyDown={onKeyDown}
59
56
  onIncreaseSize={() => setExpanded(false)}
60
57
  onResetSize={() => !expansionLocked.current && setExpanded(true)}
61
- maxHeight={isMinimized ? MAX_INPUT_HEIGHT_MINIMIZED : MAX_INPUT_HEIGHT}
58
+ maxHeight={isMinimized ? MIN_INPUT_HEIGHT : MAX_INPUT_HEIGHT}
62
59
  />
63
60
  <ButtonGroup
64
61
  features={features}
@@ -4,6 +4,8 @@ import { styled } from 'styled-components'
4
4
 
5
5
  const INFO_BAR_HEIGHT = 42
6
6
  const INFO_BAR_DISPLACEMENT = 4
7
+ export const MAX_INPUT_HEIGHT = 300
8
+ export const MIN_INPUT_HEIGHT = 24
7
9
 
8
10
  export const MessageInputBox = styled.div`
9
11
  display: flex;
@@ -103,7 +105,7 @@ export const MessageInputBox = styled.div`
103
105
  flex-direction: row;
104
106
  align-items: center;
105
107
  gap: 4px;
106
- margin-bottom: 3px;
108
+ margin-bottom: 1px;
107
109
 
108
110
  button {
109
111
  border: none;
@@ -144,6 +146,12 @@ export const MessageInputBox = styled.div`
144
146
  transform: rotate(180deg);
145
147
  }
146
148
  }
149
+
150
+ &.agent img {
151
+ width: 80%;
152
+ height: 80%;
153
+ border-radius: 50%;
154
+ }
147
155
  }
148
156
 
149
157
  .feature-buttons {
@@ -185,8 +193,8 @@ export const MessageInputBox = styled.div`
185
193
  border: none;
186
194
  flex: 1;
187
195
  padding: 0;
188
- height: 30px;
189
- padding: 2px 0;
196
+ height: ${MIN_INPUT_HEIGHT}px;
197
+ padding: 0;
190
198
  transition: height 0.3s;
191
199
  background-color: transparent;
192
200
  &:focus {
@@ -13,16 +13,16 @@ import { useRightPanel } from '../right-panel/hooks'
13
13
 
14
14
  export const Stacks = () => {
15
15
  const t = useTranslate(dictionary)
16
- const isStackSelectionOpen = useWidgetState('isStackSelectionOpen')
16
+ const panel = useWidgetState('panel')
17
17
  const { open } = useRightPanel()
18
18
  const widget = useWidget()
19
19
 
20
20
  useEffect(() => {
21
- if (isStackSelectionOpen) open(
21
+ if (panel === 'stack') open(
22
22
  <StacksPanel />,
23
- { title: t.title, description: t.description, onClose: () => widget.set('isStackSelectionOpen', false) },
23
+ { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) },
24
24
  )
25
- }, [isStackSelectionOpen, t])
25
+ }, [panel, t])
26
26
 
27
27
  return null
28
28
  }
@@ -65,7 +65,10 @@ const StacksTab = ({ visibility }: { visibility: VisibilityLevelEnum }) => {
65
65
  onChange={setValue}
66
66
  renderLabel={s => s.name}
67
67
  renderDescription={s => s.use_case}
68
- optionClassName={s => (s === value && filter && !s.name.includes(filter)) ? 'filtered-out' : ''}
68
+ optionClassName={s => (s === value && filter && !s.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
69
+ ? 'filtered-out'
70
+ : ''
71
+ }
69
72
  className="option-list"
70
73
  />}
71
74
  {!!stacks.length && !filtered.length && <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}
@@ -13,16 +13,16 @@ import { useRightPanel } from '../right-panel/hooks'
13
13
 
14
14
  export const Workspaces = () => {
15
15
  const t = useTranslate(dictionary)
16
- const isWorkspaceSelectionOpen = useWidgetState('isWorkspaceSelectionOpen')
16
+ const panel = useWidgetState('panel')
17
17
  const { open } = useRightPanel()
18
18
  const widget = useWidget()
19
19
 
20
20
  useEffect(() => {
21
- if (isWorkspaceSelectionOpen) open(
21
+ if (panel === 'workspace') open(
22
22
  <RightPanelForm><WorkspacesPanel /></RightPanelForm>,
23
- { title: t.title, description: t.description, onClose: () => widget.set('isWorkspaceSelectionOpen', false) },
23
+ { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) },
24
24
  )
25
- }, [isWorkspaceSelectionOpen, t])
25
+ }, [panel, t])
26
26
 
27
27
  return null
28
28
  }
@@ -55,7 +55,10 @@ const WorkspacesPanel = () => {
55
55
  onChange={setValue}
56
56
  renderLabel={w => w.name}
57
57
  renderDescription={w => w.description}
58
- optionClassName={w => (w === value && filter && !w.name.includes(filter)) ? 'filtered-out' : ''}
58
+ optionClassName={w => (w === value && filter && !w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
59
+ ? 'filtered-out'
60
+ : ''
61
+ }
59
62
  className="option-list"
60
63
  />}
61
64
  {!!workspaces.length && !filtered.length && <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}