@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
@@ -12,13 +12,13 @@ import { useCurrentChat, useWidget, useWidgetState } from '../context/hooks.js';
12
12
  import { useRightPanel } from '../right-panel/hooks.js';
13
13
  export const Workspaces = () => {
14
14
  const t = useTranslate(dictionary);
15
- const isWorkspaceSelectionOpen = useWidgetState('isWorkspaceSelectionOpen');
15
+ const panel = useWidgetState('panel');
16
16
  const { open } = useRightPanel();
17
17
  const widget = useWidget();
18
18
  useEffect(() => {
19
- if (isWorkspaceSelectionOpen)
20
- open(_jsx(RightPanelForm, { children: _jsx(WorkspacesPanel, {}) }), { title: t.title, description: t.description, onClose: () => widget.set('isWorkspaceSelectionOpen', false) });
21
- }, [isWorkspaceSelectionOpen, t]);
19
+ if (panel === 'workspace')
20
+ open(_jsx(RightPanelForm, { children: _jsx(WorkspacesPanel, {}) }), { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) });
21
+ }, [panel, t]);
22
22
  return null;
23
23
  };
24
24
  const WorkspacesPanel = () => {
@@ -34,7 +34,9 @@ const WorkspacesPanel = () => {
34
34
  chat.set('workspace', { id: value.id, label: value.name });
35
35
  close();
36
36
  }
37
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "content", children: [_jsx(IconInput, { icon: _jsx(Search, {}), value: filter, onChange: setFilter, className: "search" }), !!filtered.length && _jsx(DescribedRadioGroup, { options: filtered, keygen: w => w.id, value: value, onChange: setValue, renderLabel: w => w.name, renderDescription: w => w.description, optionClassName: w => (w === value && filter && !w.name.includes(filter)) ? 'filtered-out' : '', className: "option-list" }), !!workspaces.length && !filtered.length && _jsx(Placeholder, { title: t.noSearchResults, description: t.noSearchResultsDescription }), !workspaces.length && _jsx(Placeholder, { title: t.noData, description: t.noDataDescription })] }), _jsx(Button, { onClick: submit, disabled: !value, children: t.apply })] }));
37
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "content", children: [_jsx(IconInput, { icon: _jsx(Search, {}), value: filter, onChange: setFilter, className: "search" }), !!filtered.length && _jsx(DescribedRadioGroup, { options: filtered, keygen: w => w.id, value: value, onChange: setValue, renderLabel: w => w.name, renderDescription: w => w.description, optionClassName: w => (w === value && filter && !w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
38
+ ? 'filtered-out'
39
+ : '', className: "option-list" }), !!workspaces.length && !filtered.length && _jsx(Placeholder, { title: t.noSearchResults, description: t.noSearchResultsDescription }), !workspaces.length && _jsx(Placeholder, { title: t.noData, description: t.noDataDescription })] }), _jsx(Button, { onClick: submit, disabled: !value, children: t.apply })] }));
38
40
  };
39
41
  const dictionary = {
40
42
  en: {
@@ -1 +1 @@
1
- {"version":3,"file":"Workspaces.js","sourceRoot":"","sources":["../../src/views/Workspaces.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAAc,YAAY,EAAE,MAAM,8BAA8B,CAAA;AACvE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,EAAE;IAC7B,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,wBAAwB,GAAG,cAAc,CAAC,0BAA0B,CAAC,CAAA;IAC3E,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,wBAAwB;YAAE,IAAI,CAChC,KAAC,cAAc,cAAC,KAAC,eAAe,KAAG,GAAiB,EACpD,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,EAAE,KAAK,CAAC,EAAE,CAC7G,CAAA;IACH,CAAC,EAAE,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC,CAAA;IAEjC,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,MAAM,eAAe,GAAG,GAAG,EAAE;IAC3B,MAAM,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,EAAE,CAAA;IACjC,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;IAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACxC,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1E,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAoC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAC/H,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAClI,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAC5B,CAAA;IAED,SAAS,MAAM;QACb,IAAI,KAAK;YAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QACrE,KAAK,EAAE,CAAA;IACT,CAAC;IAED,OAAO,CACL,8BACE,eAAK,SAAS,EAAC,SAAS,aACtB,KAAC,SAAS,IAAC,IAAI,EAAE,KAAC,MAAM,KAAG,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAC,QAAQ,GAAG,EACrF,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,KAAC,mBAAmB,IACxC,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EACxB,iBAAiB,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EACrC,eAAe,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAC/F,SAAS,EAAC,aAAa,GACvB,EACD,CAAC,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,KAAC,WAAW,IAAC,KAAK,EAAE,CAAC,CAAC,eAAe,EAAE,WAAW,EAAE,CAAC,CAAC,0BAA0B,GAAI,EAC/H,CAAC,UAAU,CAAC,MAAM,IAAI,KAAC,WAAW,IAAC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,iBAAiB,GAAI,IACrF,EACN,KAAC,MAAM,IAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,YAAG,CAAC,CAAC,KAAK,GAAU,IAC5D,CACJ,CAAA;AACH,CAAC,CAAA;AAED,MAAM,UAAU,GAAG;IACjB,EAAE,EAAE;QACF,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,kGAAkG;QAC/G,KAAK,EAAE,OAAO;QACd,eAAe,EAAE,mCAAmC;QACpD,0BAA0B,EAAE,kCAAkC;QAC9D,MAAM,EAAE,8BAA8B;QACtC,iBAAiB,EAAE,6CAA6C;KACjE;IACD,EAAE,EAAE;QACF,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,qGAAqG;QAClH,KAAK,EAAE,SAAS;QAChB,eAAe,EAAE,mCAAmC;QACpD,0BAA0B,EAAE,+BAA+B;QAC3D,MAAM,EAAE,yBAAyB;QACjC,iBAAiB,EAAE,8CAA8C;KAClE;CACmB,CAAA"}
1
+ {"version":3,"file":"Workspaces.js","sourceRoot":"","sources":["../../src/views/Workspaces.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/ai-chat-widget",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "@citric/icons": "^5.4.0 || ^6.0.0",
10
10
  "@citric/ui": "^5.4.0 || ^6.0.0",
11
11
  "@stack-spot/portal-components": "^2.6.1",
12
- "@stack-spot/portal-network": "^0.40.0",
12
+ "@stack-spot/portal-network": "^0.41.0",
13
13
  "@stack-spot/portal-theme": "^1.0.0",
14
14
  "@stack-spot/portal-translate": "^1.1.0",
15
15
  "lodash": "^4.17.0",
@@ -10,10 +10,12 @@ import { RightPanel } from './right-panel/RightPanel'
10
10
  import { RightPanelProvider } from './right-panel/RightPanelProvider'
11
11
  import { MessageInterceptor } from './state/ChatState'
12
12
  import { MinimizedActions } from './types'
13
+ import { Agents } from './views/Agents'
13
14
  import { Chat } from './views/Chat'
14
15
  import { ChatTabSelection } from './views/ChatTabSelection'
15
16
  import { Home } from './views/Home'
16
17
  import { KnowledgeSources } from './views/KnowledgeSources'
18
+ import { KSDocument } from './views/KSDocument'
17
19
  import { MessageInput } from './views/MessageInput'
18
20
  import { MinimizedHeader } from './views/MinimizedHeader'
19
21
  import { Stacks } from './views/Stacks'
@@ -57,6 +59,8 @@ export const StackspotAIWidget = (
57
59
  <Stacks />
58
60
  <Workspaces />
59
61
  <KnowledgeSources />
62
+ <KSDocument />
63
+ <Agents />
60
64
  {!isMinimized && <div className="chat-right-panel" ref={rightPanelRef}><RightPanel /></div>}
61
65
  </div>
62
66
  </RightPanelProvider>
@@ -1,5 +1,5 @@
1
1
  import { aiClient, StackspotAPIError } from '@stack-spot/portal-network'
2
- import { ChatEntry } from '../state/ChatEntry'
2
+ import { ChatEntry, KnowledgeSource } from '../state/ChatEntry'
3
3
  import { ChatState } from '../state/ChatState'
4
4
  import { buildConversationContext } from '../utils/chat'
5
5
 
@@ -15,13 +15,25 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState)
15
15
  const stream = aiClient.sendChatMessage({ context, user_prompt: content })
16
16
  const botEntry = ChatEntry.createStreamedBotEntry(() => stream.cancel())
17
17
  chat.pushMessage(botEntry)
18
- stream.onChange(value => botEntry.setValue({
19
- agent: 'bot',
20
- type: 'md',
21
- content: value.answer ?? '',
22
- messageId: value.message_id ?? undefined,
23
- updated: new Date().toISOString(),
24
- }))
18
+ let knowledgeSources: KnowledgeSource[] | undefined
19
+ stream.onChange(value => {
20
+ if (value.sources?.length !== knowledgeSources?.length) {
21
+ knowledgeSources = value.sources?.filter(s => s.type === 'knowledge_source').map(ks => ({
22
+ documentId: ks.document_id,
23
+ documentScore: ks.document_score,
24
+ name: ks.name,
25
+ slug: ks.slug,
26
+ }))
27
+ }
28
+ botEntry.setValue({
29
+ agent: 'bot',
30
+ type: 'md',
31
+ content: value.answer ?? '',
32
+ messageId: value.message_id ?? undefined,
33
+ knowledgeSources,
34
+ updated: new Date().toISOString(),
35
+ })
36
+ })
25
37
  try {
26
38
  await stream.getValue()
27
39
  } catch (error: any) {
@@ -15,6 +15,7 @@ interface Props {
15
15
  const StyledTabs = styled(Tabs)`
16
16
  > button {
17
17
  flex: 1;
18
+ padding: 8px 0;
18
19
  }
19
20
  `
20
21
 
@@ -9,7 +9,7 @@ export const RadioCheckBox = styled.ul`
9
9
  flex-direction: column;
10
10
  gap: 6px;
11
11
 
12
- li {
12
+ > li {
13
13
  display: flex;
14
14
  flex-direction: column;
15
15
  gap: 8px;
@@ -26,6 +26,7 @@ export const RadioCheckBox = styled.ul`
26
26
  input[type="radio"], input[type="checkbox"] {
27
27
  border-color: ${theme.color.light[700]};
28
28
  background-color: transparent;
29
+ flex-shrink: 0;
29
30
  }
30
31
 
31
32
  input[type="checkbox"]:checked {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
- import { useContext, useEffect, useMemo, useState } from 'react'
2
+ import { useContext, useEffect, useState } from 'react'
3
3
  import { ChatEntry } from '../state/ChatEntry'
4
4
  import { ChatProperties, ChatState, MessageInterceptor } from '../state/ChatState'
5
5
  import { ObservableState } from '../state/ObservableState'
@@ -78,9 +78,6 @@ export function useCurrentChatMessages(): ChatEntry[] {
78
78
  }
79
79
 
80
80
  export function useChatEntry(entry: ChatEntry) {
81
- const immutable = useMemo(() => entry.hasFinished(), [])
82
- // the following condition is not a problem for react hooks, it will always be true or always false.
83
- if (immutable) return entry.getValue()
84
81
  const [content, setContent] = useState(entry.getValue())
85
82
  useEffect(() => entry.onChange(setContent), [])
86
83
  return content
package/src/features.ts CHANGED
@@ -15,4 +15,5 @@ export const defaultFeatures: AIWidgetFeatures = {
15
15
  stack: true,
16
16
  workspace: true,
17
17
  knowledgeSource: true,
18
+ agent: true,
18
19
  }
@@ -6,8 +6,8 @@ import { styled } from 'styled-components'
6
6
  import { WithChildren } from '../types'
7
7
 
8
8
  interface Props extends WithChildren {
9
- title: string,
10
- description: string,
9
+ title: React.ReactNode,
10
+ description: React.ReactNode,
11
11
  onClose: () => void,
12
12
  }
13
13
 
@@ -22,6 +22,7 @@ const PanelBox = styled.div`
22
22
  header {
23
23
  display: flex;
24
24
  flex-direction: row;
25
+ gap: 10px;
25
26
 
26
27
  .title {
27
28
  display: flex;
@@ -45,8 +46,8 @@ export const DefaultPanel = ({ description, onClose, title, children }: Props) =
45
46
  <PanelBox>
46
47
  <header>
47
48
  <div className="title">
48
- <Text appearance="h5">{title}</Text>
49
- <Text colorScheme="light.700">{description}</Text>
49
+ {typeof title === 'string' ? <Text appearance="h5">{title}</Text> : title}
50
+ {typeof description === 'string' ? <Text colorScheme="light.700">{description}</Text> : description}
50
51
  </div>
51
52
  <IconButton title={t.close} aria-label={t.close} onClick={onClose}>
52
53
  <Times />
@@ -3,8 +3,8 @@ import { DefaultPanel } from './DefaultPanel'
3
3
  import { RightPanelContext } from './RightPanelProvider'
4
4
 
5
5
  interface RightPanelOptions {
6
- title: string,
7
- description: string,
6
+ title: React.ReactNode,
7
+ description: React.ReactNode,
8
8
  onClose?: () => void,
9
9
  }
10
10
 
@@ -16,6 +16,13 @@ export interface ChatAction extends SerializableAction {
16
16
  appearance?: 'primary' | 'secondary',
17
17
  }
18
18
 
19
+ export interface KnowledgeSource {
20
+ name: string,
21
+ slug: string,
22
+ documentScore: number,
23
+ documentId: string,
24
+ }
25
+
19
26
  export interface TextChatEntry {
20
27
  type: 'text' | 'md',
21
28
  agent: 'bot' | 'user' | 'system',
@@ -23,7 +30,7 @@ export interface TextChatEntry {
23
30
  actions?: ChatAction[],
24
31
  subtitle?: string,
25
32
  content: string,
26
- // knowledgeSources?: KnowledgeSource[],
33
+ knowledgeSources?: KnowledgeSource[],
27
34
  updated?: string,
28
35
  agentId?: string,
29
36
  messageId?: string,
@@ -68,7 +75,6 @@ export class ChatEntry {
68
75
  }
69
76
 
70
77
  setValue(value: TextChatEntry) {
71
- if (this.streamFinished) return
72
78
  this.value = value
73
79
  this.listeners.forEach(l => l(this.value))
74
80
  }
@@ -79,7 +85,6 @@ export class ChatEntry {
79
85
 
80
86
  finish() {
81
87
  this.streamFinished = true
82
- this.listeners = []
83
88
  }
84
89
 
85
90
  hasFinished() {
@@ -7,9 +7,13 @@ interface Labeled {
7
7
  label: string,
8
8
  }
9
9
 
10
+ interface LabeledWithImage extends Labeled {
11
+ image?: string,
12
+ }
13
+
10
14
  export interface ChatProperties {
11
15
  label: string,
12
- agent?: Labeled,
16
+ agent?: LabeledWithImage,
13
17
  workspace?: Labeled,
14
18
  stack?: Labeled,
15
19
  knowledgeSources?: Labeled[],
@@ -2,41 +2,29 @@ import { ChatTabsController } from './ChatTabsController'
2
2
  import { ObservableState } from './ObservableState'
3
3
 
4
4
  export interface WidgetProperties {
5
- isStackSelectionOpen?: boolean,
6
- isWorkspaceSelectionOpen?: boolean,
7
- isAgentSelectionOpen?: boolean,
8
- isKnowledgeSourceSelectionOpen?: boolean,
9
- isCodeEditorOpen?: boolean,
10
- isChatHistoryOpen?: boolean,
5
+ /**
6
+ * Current content of the right panel. Undefined for closed right panel.
7
+ */
8
+ panel?: 'stack' | 'workspace' | 'agent' | 'ks' | 'editor' | 'history' | 'ks-details',
9
+ /**
10
+ * KS to use when the right panel "ks-details" is open.
11
+ */
12
+ currentKSInPanel?: { name: string, slug: string, score: number, documentId: string },
13
+ /**
14
+ * Whether or not the widget is in its minimized version.
15
+ */
11
16
  isMinimized?: boolean,
17
+ /**
18
+ * The current code in the editor.
19
+ */
12
20
  code?: string,
13
21
  }
14
22
 
15
- const panelKeys: (keyof WidgetProperties)[] = [
16
- 'isAgentSelectionOpen', 'isChatHistoryOpen', 'isCodeEditorOpen', 'isKnowledgeSourceSelectionOpen', 'isStackSelectionOpen',
17
- 'isWorkspaceSelectionOpen',
18
- ]
19
-
20
23
  export class WidgetState extends ObservableState<WidgetProperties> {
21
24
  readonly chatTabs: ChatTabsController
22
25
 
23
26
  constructor(initial: WidgetProperties = {}, chatTabs?: ChatTabsController) {
24
27
  super(initial)
25
28
  this.chatTabs = chatTabs ?? new ChatTabsController()
26
- this.createCloseOthersBehavior()
27
- }
28
-
29
- private createCloseOthersBehavior() {
30
- panelKeys.forEach((key) => {
31
- this.onChange(key, (value) => {
32
- if (value) this.closeOtherPanels(key)
33
- })
34
- })
35
- }
36
-
37
- private closeOtherPanels(leaveOpen: keyof WidgetProperties) {
38
- panelKeys.forEach((key) => {
39
- if (key !== leaveOpen) this.set(key, false)
40
- })
41
29
  }
42
30
  }
package/src/utils/chat.ts CHANGED
@@ -21,7 +21,7 @@ export function buildConversationContext(state: ChatState): FixedChatRequest['co
21
21
  language: getLanguage(),
22
22
  knowledge_sources: state.get('knowledgeSources')?.map(ks => ks.id),
23
23
  agent_id: state.get('agent')?.id,
24
- agent_built_in: !!state.get('agent'),
24
+ agent_built_in: !state.get('agent'),
25
25
  os: navigator.userAgent,
26
26
  platform: 'web-widget',
27
27
  platform_version: navigator.userAgent,
@@ -0,0 +1,43 @@
1
+ import { DocumentResponse } from '@stack-spot/portal-network/api/ai'
2
+
3
+ function attemptToParseMalFormedJson(str: string) {
4
+ try {
5
+ return JSON.parse(str)
6
+ } catch {
7
+ try {
8
+ const fixed = str
9
+ .replace(/"/g, '-_-')
10
+ .replace(/'/g, '"')
11
+ .replace(/-_-/g, "'")
12
+ .replace(
13
+ /": (\w+)/g,
14
+ (_, value) => (['null', 'true', 'false'].includes(value) || value.match(/\d+/)) ? `": ${value}` : `": "${value}"`,
15
+ )
16
+ return JSON.parse(fixed)
17
+ } catch {
18
+ return undefined
19
+ }
20
+ }
21
+ }
22
+
23
+ export function extractCodeFromKSDocument(document: DocumentResponse): { language?: string, snippet: string, text?: string } {
24
+ const language = (document?.metadata as any)?.language
25
+ if (language) {
26
+ const pageContent: string = document?.page_content ?? ''
27
+ let text: string | undefined = undefined
28
+ let snippet = pageContent
29
+ const match = pageContent.match(/^use case: .*\n/i)
30
+ if (match) {
31
+ text = match[0]
32
+ snippet = pageContent.replace(text, '')
33
+ }
34
+ return { language, snippet, text }
35
+ }
36
+
37
+ if (document?.page_content) {
38
+ const parsed = attemptToParseMalFormedJson(document.page_content)
39
+ return parsed ? { language: 'json', snippet: JSON.stringify(parsed, null, 2) } : { snippet: document.page_content }
40
+ }
41
+
42
+ return typeof document === 'object' ? { language: 'json', snippet: JSON.stringify(document, null, 2) } : { snippet: String(document) }
43
+ }
@@ -1 +1,186 @@
1
- export const Agents = () => null
1
+ import { Button, Text } from '@citric/core'
2
+ import { Search } from '@citric/icons'
3
+ import { Badge } from '@citric/ui'
4
+ import { Placeholder } from '@stack-spot/portal-components/Placeholder'
5
+ import { agentClient } from '@stack-spot/portal-network'
6
+ import { AgentResponse, VisibilityLevel } from '@stack-spot/portal-network/api/agent'
7
+ import { theme } from '@stack-spot/portal-theme'
8
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
9
+ import { useEffect, useMemo, useState } from 'react'
10
+ import { styled } from 'styled-components'
11
+ import { DescribedRadioGroup } from '../components/form/DescribedRadioGroup'
12
+ import { IconInput } from '../components/IconInput'
13
+ import { RightPanelTabs } from '../components/RightPanelTabs'
14
+ import { useCurrentChat, useWidget, useWidgetState } from '../context/hooks'
15
+ import { useRightPanel } from '../right-panel/hooks'
16
+
17
+ const AgentLabel = styled.div`
18
+ display: flex;
19
+ flex-direction: row;
20
+ align-items: center;
21
+ gap: 6px;
22
+
23
+ img {
24
+ width: 20px;
25
+ height: 20px;
26
+ border-radius: 50%;
27
+ overflow: hidden;
28
+ }
29
+ `
30
+
31
+ const AgentDescription = styled.div`
32
+ color: ${theme.color.light[700]};
33
+ line-height: 18px;
34
+
35
+ section {
36
+ border-bottom: 1px solid ${theme.color.light[600]};
37
+ padding-bottom: 10px;
38
+ margin-bottom: 10px;
39
+
40
+ &:last-child {
41
+ padding-bottom: 0;
42
+ margin-bottom: 0;
43
+ border-bottom: none;
44
+ }
45
+
46
+ .title {
47
+ display: block;
48
+ margin-bottom: 6px;
49
+ }
50
+ }
51
+
52
+ ul {
53
+ padding: 0;
54
+ margin: 0;
55
+ list-style: none;
56
+ display: flex;
57
+ flex-direction: row;
58
+ flex-wrap: wrap;
59
+ white-space: nowrap;
60
+ gap: 6px;
61
+
62
+ li {
63
+ margin: 3px 0;
64
+ }
65
+ }
66
+ `
67
+
68
+ export const Agents = () => {
69
+ const t = useTranslate(dictionary)
70
+ const panel = useWidgetState('panel')
71
+ const { open } = useRightPanel()
72
+ const widget = useWidget()
73
+
74
+ useEffect(() => {
75
+ if (panel === 'agent') open(
76
+ <AgentsPanel />,
77
+ { title: t.title, description: t.description, onClose: () => widget.set('panel', undefined) },
78
+ )
79
+ }, [panel, t])
80
+
81
+ return null
82
+ }
83
+
84
+ const AgentsPanel = () => {
85
+ const t = useTranslate(dictionary)
86
+
87
+ return <RightPanelTabs tabs={[
88
+ { title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
89
+ { title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
90
+ { title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" /> },
91
+ { title: t.account, content: <AgentsTab key="account" visibility="ACCOUNT" /> },
92
+ ]} />
93
+ }
94
+
95
+ const AgentsTab = ({ visibility }: { visibility: VisibilityLevel | 'BUILT-IN' }) => {
96
+ const t = useTranslate(dictionary)
97
+ const { close } = useRightPanel()
98
+ const chat = useCurrentChat()
99
+ const [filter, setFilter] = useState('')
100
+ const agents = visibility === 'BUILT-IN' ? agentClient.publicAgents.useQuery({}) : agentClient.agents.useQuery({ visibility })
101
+ const [value, setValue] = useState<AgentResponse | undefined>(agents.find(a => a.id === chat.get('agent')?.id))
102
+ const filtered = useMemo(
103
+ () => filter ? agents.filter(a => a === value || a.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : agents,
104
+ [agents, filter, value],
105
+ )
106
+
107
+ function submit() {
108
+ if (value) chat.set('agent', { id: value.id, label: value.name, image: value.avatar })
109
+ close()
110
+ }
111
+
112
+ return (
113
+ <>
114
+ <div className="content">
115
+ <IconInput icon={<Search />} value={filter} onChange={setFilter} className="search" />
116
+ {!!filtered.length && <DescribedRadioGroup
117
+ options={filtered}
118
+ keygen={a => a.id}
119
+ value={value}
120
+ onChange={setValue}
121
+ renderLabel={({ name, avatar }) => (
122
+ <AgentLabel>
123
+ {avatar && <img src={avatar} />}
124
+ <Text>{name}</Text>
125
+ </AgentLabel>
126
+ )}
127
+ renderDescription={({ description, llm_config: llmc, knowledge_sources_config: ksc }) => (
128
+ <AgentDescription>
129
+ {description && <section>
130
+ <Text appearance="microtext1" className="title">Description</Text>
131
+ <Text>{description}</Text>
132
+ </section>}
133
+ {!!ksc?.knowledge_sources?.length && <section>
134
+ <Text appearance="microtext1" className="title">Knowledge sources</Text>
135
+ <ul>
136
+ {ksc.knowledge_sources.map((ks, index) => <li key={index}><Badge palette="teal" appearance="square">{ks}</Badge></li>)}
137
+ </ul>
138
+ </section>}
139
+ {llmc?.model_slug && <section>
140
+ <Text appearance="microtext1" className="title">LLM</Text>
141
+ <Badge palette="orange" appearance="square">{llmc.model_slug}</Badge>
142
+ </section>}
143
+ </AgentDescription>
144
+ )}
145
+ optionClassName={a => (a === value && filter && !a.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
146
+ ? 'filtered-out'
147
+ : ''
148
+ }
149
+ className="option-list"
150
+ />}
151
+ {!!agents.length && !filtered.length && <Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} />}
152
+ {!agents.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
153
+ </div>
154
+ <Button onClick={submit} disabled={!value}>{t.apply}</Button>
155
+ </>
156
+ )
157
+ }
158
+
159
+ const dictionary = {
160
+ en: {
161
+ title: 'Agents',
162
+ description: 'By selecting an Agent, it will be consulted to generate the answers.',
163
+ personal: 'Personal',
164
+ builtin: 'Built-in',
165
+ shared: 'Shared',
166
+ account: 'Account',
167
+ apply: 'Apply',
168
+ noSearchResults: "Your search didn't yield results.",
169
+ noSearchResultsDescription: 'Please, try another search term.',
170
+ noData: 'There are no agents in this category yet.',
171
+ noDataDescription: 'Use the tabs above to try other categories or use the AI portal to create new agents.',
172
+ },
173
+ pt: {
174
+ title: 'Agentes',
175
+ description: 'Ao selecionar um Agente, ele será consultado para gerar as respostas.',
176
+ personal: 'Pessoal',
177
+ builtin: 'Embutido',
178
+ shared: 'Compartilhado',
179
+ account: 'Conta',
180
+ apply: 'Aplicar',
181
+ noSearchResults: 'Sua busca não produziu resultados.',
182
+ noSearchResultsDescription: 'Por favor, tente outra busca.',
183
+ noData: 'Ainda não há agentes nesta categoria.',
184
+ noDataDescription: 'Use as abas acima para tentar outras categorias ou use o Portal AI para criar novos agentes.',
185
+ },
186
+ } satisfies Dictionary
@@ -1,13 +1,13 @@
1
- import { IconBox, Text } from '@citric/core'
1
+ import { Button, IconBox, Text } from '@citric/core'
2
2
  import { Dislike, DislikeFill, Like, LikeFill, TimesCircle } from '@citric/icons'
3
3
  import { Avatar, IconButton } from '@citric/ui'
4
4
  import { aiClient } from '@stack-spot/portal-network'
5
5
  import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
6
- import { useMemo, useRef, useState } from 'react'
6
+ import { useCallback, useMemo, useRef, useState } from 'react'
7
7
  import { Markdown } from '../../components/Markdown'
8
- import { useChatEntry } from '../../context/hooks'
8
+ import { useChatEntry, useWidget } from '../../context/hooks'
9
9
  import { useChatScrollToBottomEffect } from '../../hooks/chat-scroll'
10
- import { ChatEntry } from '../../state/ChatEntry'
10
+ import { ChatEntry, TextChatEntry } from '../../state/ChatEntry'
11
11
  import { useDateFormatter } from '../../utils/date'
12
12
  import { AgentInfo } from './AgentInfo'
13
13
 
@@ -20,8 +20,14 @@ export const ChatMessage = ({ message, username }: { message: ChatEntry, usernam
20
20
  const date = new Date(entry.updated ?? '')
21
21
  const shouldShowDate = entry.updated && !isNaN(date.getTime())
22
22
  const ref = useRef<HTMLLIElement>(null)
23
+ const widget = useWidget()
23
24
  useChatScrollToBottomEffect(ref, [entry])
24
25
 
26
+ const detailKS = useCallback(({ name, slug, documentScore, documentId }: Required<TextChatEntry>['knowledgeSources'][number]) => {
27
+ widget.set('currentKSInPanel', { name, slug, score: documentScore, documentId })
28
+ widget.set('panel', 'ks-details')
29
+ }, [])
30
+
25
31
  const { like, dislike } = useMemo(() => {
26
32
  async function feedback(like: boolean) {
27
33
  if (!entry.messageId || like === liked) return
@@ -62,6 +68,14 @@ export const ChatMessage = ({ message, username }: { message: ChatEntry, usernam
62
68
  <Text appearance="microtext1">{entry.error}</Text>
63
69
  </div>
64
70
  )}
71
+ {!!entry.knowledgeSources?.length && <div className="ks-box">
72
+ <Text appearance="microtext1" colorScheme="light.700">Knowledge Sources:</Text>
73
+ <ul>{entry.knowledgeSources.map(ks => (
74
+ <li key={ks.slug}>
75
+ <Button size="sm" colorScheme="light" onClick={() => detailKS(ks)}>{ks.name}</Button>
76
+ </li>
77
+ ))}</ul>
78
+ </div>}
65
79
  <div className="message-footer">
66
80
  {entry.agent === 'bot' && entry.messageId && !entry.error && <div className="message-actions">
67
81
  <IconButton title={t.like} aria-label={t.like} onClick={like}>
@@ -113,4 +113,28 @@ export const ChatList = styled.ul`
113
113
  .plain-text {
114
114
  margin: 0
115
115
  }
116
+
117
+ .ks-box {
118
+ border-radius: 8px;
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 10px;
122
+ padding: 8px;
123
+ border: 1px solid ${theme.color.light[500]};
124
+
125
+ > ul {
126
+ display: flex;
127
+ flex-direction: row;
128
+ flex-wrap: wrap;
129
+ white-space: nowrap;
130
+ margin: 0;
131
+ padding: 0;
132
+ list-style: none;
133
+ gap: 6px;
134
+
135
+ > li button {
136
+ margin: 0;
137
+ }
138
+ }
139
+ }
116
140
  `