@stack-spot/ai-chat-widget 1.2.0 → 1.3.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.
- package/CHANGELOG.md +8 -0
- package/dist/StackspotAIWidget.d.ts +1 -6
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +6 -11
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +1 -1
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +21 -2
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/AutoFocus.d.ts.map +1 -1
- package/dist/components/AutoFocus.js +8 -1
- package/dist/components/AutoFocus.js.map +1 -1
- package/dist/components/FadingOverflow.js +2 -2
- package/dist/components/FadingOverflow.js.map +1 -1
- package/dist/components/QuickStartButton.d.ts +6 -1
- package/dist/components/QuickStartButton.d.ts.map +1 -1
- package/dist/components/QuickStartButton.js +6 -2
- package/dist/components/QuickStartButton.js.map +1 -1
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelForm.js +2 -1
- package/dist/components/RightPanelForm.js.map +1 -1
- package/dist/context/hooks.d.ts +1 -1
- package/dist/context/hooks.d.ts.map +1 -1
- package/dist/context/hooks.js +4 -5
- package/dist/context/hooks.js.map +1 -1
- package/dist/features.d.ts +16 -17
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +17 -9
- package/dist/features.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/layout.css +7 -0
- package/dist/state/ChatState.d.ts +23 -3
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js +5 -2
- package/dist/state/ChatState.js.map +1 -1
- package/dist/state/ChatTabsController.d.ts +21 -3
- package/dist/state/ChatTabsController.d.ts.map +1 -1
- package/dist/state/ChatTabsController.js +49 -11
- package/dist/state/ChatTabsController.js.map +1 -1
- package/dist/state/WidgetState.d.ts +22 -10
- package/dist/state/WidgetState.d.ts.map +1 -1
- package/dist/state/WidgetState.js +24 -9
- package/dist/state/WidgetState.js.map +1 -1
- package/dist/views/Agents/AgentDescription.d.ts +9 -0
- package/dist/views/Agents/AgentDescription.d.ts.map +1 -0
- package/dist/views/Agents/AgentDescription.js +21 -0
- package/dist/views/Agents/AgentDescription.js.map +1 -0
- package/dist/views/Agents/AgentsPanel.d.ts +5 -0
- package/dist/views/Agents/AgentsPanel.d.ts.map +1 -0
- package/dist/views/Agents/AgentsPanel.js +19 -0
- package/dist/views/Agents/AgentsPanel.js.map +1 -0
- package/dist/views/Agents/AgentsTab.d.ts +5 -0
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -0
- package/dist/views/Agents/AgentsTab.js +43 -0
- package/dist/views/Agents/AgentsTab.js.map +1 -0
- package/dist/views/Agents/dictionary.d.ts +2 -0
- package/dist/views/Agents/dictionary.d.ts.map +1 -0
- package/dist/views/Agents/dictionary.js +35 -0
- package/dist/views/Agents/dictionary.js.map +1 -0
- package/dist/views/Agents/index.d.ts +5 -0
- package/dist/views/Agents/index.d.ts.map +1 -0
- package/dist/views/Agents/index.js +21 -0
- package/dist/views/Agents/index.js.map +1 -0
- package/dist/views/Agents/styled.d.ts +3 -0
- package/dist/views/Agents/styled.d.ts.map +1 -0
- package/dist/views/Agents/styled.js +58 -0
- package/dist/views/Agents/styled.js.map +1 -0
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +1 -1
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/index.js +1 -1
- package/dist/views/Chat/index.js.map +1 -1
- package/dist/views/Chat/styled.js +1 -1
- package/dist/views/ChatTabSelection.d.ts +1 -5
- package/dist/views/ChatTabSelection.d.ts.map +1 -1
- package/dist/views/ChatTabSelection.js +6 -5
- package/dist/views/ChatTabSelection.js.map +1 -1
- package/dist/views/Editor.d.ts.map +1 -1
- package/dist/views/Editor.js +4 -1
- package/dist/views/Editor.js.map +1 -1
- package/dist/views/Home/BuiltInAgent.d.ts +6 -0
- package/dist/views/Home/BuiltInAgent.d.ts.map +1 -0
- package/dist/views/{Home.js → Home/BuiltInAgent.js} +7 -41
- package/dist/views/Home/BuiltInAgent.js.map +1 -0
- package/dist/views/Home/CustomAgent.d.ts +5 -0
- package/dist/views/Home/CustomAgent.d.ts.map +1 -0
- package/dist/views/Home/CustomAgent.js +24 -0
- package/dist/views/Home/CustomAgent.js.map +1 -0
- package/dist/views/Home/index.d.ts +8 -0
- package/dist/views/Home/index.d.ts.map +1 -0
- package/dist/views/Home/index.js +15 -0
- package/dist/views/Home/index.js.map +1 -0
- package/dist/views/Home/styled.d.ts +2 -0
- package/dist/views/Home/styled.d.ts.map +1 -0
- package/dist/views/Home/styled.js +59 -0
- package/dist/views/Home/styled.js.map +1 -0
- package/dist/views/Home/types.d.ts +7 -0
- package/dist/views/Home/types.d.ts.map +1 -0
- package/dist/views/Home/types.js +2 -0
- package/dist/views/Home/types.js.map +1 -0
- package/dist/views/KnowledgeSources.js +1 -1
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.d.ts +1 -6
- package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.js +12 -4
- package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
- package/dist/views/MessageInput/InfoBar.d.ts.map +1 -1
- package/dist/views/MessageInput/InfoBar.js +16 -6
- package/dist/views/MessageInput/InfoBar.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js +3 -3
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/index.d.ts +1 -9
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +2 -2
- package/dist/views/MessageInput/index.js.map +1 -1
- package/dist/views/MessageInput/styled.d.ts.map +1 -1
- package/dist/views/MessageInput/styled.js +6 -2
- package/dist/views/MessageInput/styled.js.map +1 -1
- package/dist/views/MinimizedHeader.d.ts.map +1 -1
- package/dist/views/MinimizedHeader.js +2 -3
- package/dist/views/MinimizedHeader.js.map +1 -1
- package/dist/views/Stacks.js +2 -1
- package/dist/views/Stacks.js.map +1 -1
- package/dist/views/Workspaces.js +2 -1
- package/dist/views/Workspaces.js.map +1 -1
- package/package.json +2 -2
- package/src/StackspotAIWidget.tsx +6 -16
- package/src/chat-interceptors/quick-commands.ts +1 -1
- package/src/chat-interceptors/send-message.ts +22 -2
- package/src/components/AutoFocus.tsx +9 -1
- package/src/components/FadingOverflow.tsx +2 -2
- package/src/components/QuickStartButton.tsx +17 -5
- package/src/components/RightPanelForm.tsx +2 -1
- package/src/context/hooks.ts +7 -8
- package/src/features.ts +27 -24
- package/src/index.ts +6 -0
- package/src/layout.css +7 -0
- package/src/state/ChatState.ts +26 -4
- package/src/state/ChatTabsController.ts +50 -11
- package/src/state/WidgetState.ts +39 -13
- package/src/views/Agents/AgentDescription.tsx +48 -0
- package/src/views/Agents/AgentsPanel.tsx +19 -0
- package/src/views/Agents/AgentsTab.tsx +80 -0
- package/src/views/Agents/dictionary.ts +36 -0
- package/src/views/Agents/index.tsx +26 -0
- package/src/views/Agents/styled.ts +59 -0
- package/src/views/Chat/ChatMessage.tsx +19 -17
- package/src/views/Chat/index.tsx +1 -1
- package/src/views/Chat/styled.ts +1 -1
- package/src/views/ChatTabSelection.tsx +7 -9
- package/src/views/Editor.tsx +4 -1
- package/src/views/{Home.tsx → Home/BuiltInAgent.tsx} +7 -48
- package/src/views/Home/CustomAgent.tsx +39 -0
- package/src/views/Home/index.tsx +20 -0
- package/src/views/Home/styled.ts +59 -0
- package/src/views/Home/types.ts +6 -0
- package/src/views/KnowledgeSources.tsx +2 -2
- package/src/views/MessageInput/ButtonGroup.tsx +15 -12
- package/src/views/MessageInput/InfoBar.tsx +25 -9
- package/src/views/MessageInput/QuickCommandSelector.tsx +3 -3
- package/src/views/MessageInput/index.tsx +1 -10
- package/src/views/MessageInput/styled.ts +6 -2
- package/src/views/MinimizedHeader.tsx +2 -3
- package/src/views/Stacks.tsx +3 -2
- package/src/views/Workspaces.tsx +3 -2
- package/dist/views/Agents.d.ts +0 -2
- package/dist/views/Agents.d.ts.map +0 -1
- package/dist/views/Agents.js +0 -146
- package/dist/views/Agents.js.map +0 -1
- package/dist/views/Home.d.ts +0 -14
- package/dist/views/Home.d.ts.map +0 -1
- package/dist/views/Home.js.map +0 -1
- package/src/views/Agents.tsx +0 -203
|
@@ -46,8 +46,12 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
46
46
|
if (agentType !== 'user') return
|
|
47
47
|
const context = buildConversationContext(chat)
|
|
48
48
|
chat.set('isLoading', true)
|
|
49
|
+
const untitled = chat.untitled
|
|
49
50
|
const isFirstMessage = chat.getMessages().length === 1
|
|
50
|
-
if (
|
|
51
|
+
if (untitled) {
|
|
52
|
+
chat.set('label', content)
|
|
53
|
+
chat.untitled = false
|
|
54
|
+
}
|
|
51
55
|
const stream = aiClient.sendChatMessage({ context, user_prompt: content.replace(/^\s*\\(\\|\/)/, '$1') })
|
|
52
56
|
signal.addEventListener('abort', () => stream.cancel())
|
|
53
57
|
const botEntry = ChatEntry.createStreamedBotEntry()
|
|
@@ -63,7 +67,23 @@ export async function sendMessageInterceptor(entry: ChatEntry, chat: ChatState,
|
|
|
63
67
|
const finalValue = await stream.getValue()
|
|
64
68
|
botEntry.setValue(createEntryValueFromChatResponse(finalValue, knowledgeSources, chat.get('agent'), true))
|
|
65
69
|
aiClient.chat.invalidate({ conversationId: chat.id })
|
|
66
|
-
if (isFirstMessage)
|
|
70
|
+
if (isFirstMessage) {
|
|
71
|
+
// if the chat has a title and this was its first message, we need to rename it according to the title, otherwise, the backend will
|
|
72
|
+
// keep the name generated by default.
|
|
73
|
+
if (!untitled) {
|
|
74
|
+
try {
|
|
75
|
+
await aiClient.renameChat.mutate({
|
|
76
|
+
conversationId: chat.id,
|
|
77
|
+
conversationUpdateTitleRequest: { title: chat.get('label') },
|
|
78
|
+
})
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.warn('Failed to rename chat:', chat.get('label'), error)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// this makes sure to update the chat history
|
|
85
|
+
aiClient.chats.invalidate()
|
|
86
|
+
}
|
|
67
87
|
} catch (error: any) {
|
|
68
88
|
botEntry.setValue({
|
|
69
89
|
...botEntry.getValue(),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/display-name */
|
|
2
2
|
import { focusFirstChild, TagPriorityElement } from '@stack-spot/portal-components'
|
|
3
3
|
import { forwardRef, RefObject, useEffect, useRef } from 'react'
|
|
4
|
+
import { styled } from 'styled-components'
|
|
4
5
|
|
|
5
6
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
7
|
/**
|
|
@@ -19,6 +20,13 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
19
20
|
ignore?: string,
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
const FlexibleDiv = styled.div`
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
flex: 1;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
`
|
|
29
|
+
|
|
22
30
|
/**
|
|
23
31
|
* Focus the first focusable child as soon as the component mounts.
|
|
24
32
|
*/
|
|
@@ -30,5 +38,5 @@ export const AutoFocus = forwardRef<HTMLDivElement, Props>(({ children, delay =
|
|
|
30
38
|
setTimeout(() => focusFirstChild(ref.current, { priority, ignore }), delay)
|
|
31
39
|
}, [])
|
|
32
40
|
|
|
33
|
-
return <
|
|
41
|
+
return <FlexibleDiv ref={ref} {...props}>{children}</FlexibleDiv>
|
|
34
42
|
})
|
|
@@ -45,8 +45,8 @@ const SCROLL_PX = 4
|
|
|
45
45
|
const masks = {
|
|
46
46
|
right: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
|
|
47
47
|
left: 'linear-gradient(to right, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
|
|
48
|
-
top: 'linear-gradient(to
|
|
49
|
-
bottom: 'linear-gradient(to
|
|
48
|
+
top: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
|
|
49
|
+
bottom: 'linear-gradient(to bottom, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgba(0, 0, 0) 100%)',
|
|
50
50
|
horizontal: 'linear-gradient(to left, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
|
|
51
51
|
vertical: 'linear-gradient(to top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) min(30%, 100px), rgb(0, 0, 0) max(70%, calc(100% - 100px)), rgba(0, 0, 0, 0) 100%)',
|
|
52
52
|
}
|
|
@@ -2,9 +2,15 @@ import { IconBox, Text } from '@citric/core'
|
|
|
2
2
|
import { theme, WithStyle } from '@stack-spot/portal-theme'
|
|
3
3
|
import { styled } from 'styled-components'
|
|
4
4
|
import { ButtonAction } from '../types'
|
|
5
|
+
import { FadingOverflow } from './FadingOverflow'
|
|
5
6
|
|
|
6
7
|
interface Props extends ButtonAction, WithStyle {
|
|
7
8
|
background?: string,
|
|
9
|
+
/**
|
|
10
|
+
* Whether or not the vertical overflow of this button should be managed by the component {@link FadingOverflow}.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
manageOverflow?: boolean,
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
/**
|
|
@@ -46,9 +52,15 @@ const QuickButton = styled.button<{ $color?: string, $bg?: string }>`
|
|
|
46
52
|
}
|
|
47
53
|
`
|
|
48
54
|
|
|
49
|
-
export const QuickStartButton = ({ label, onClick, background, className, color, icon, style }: Props) =>
|
|
50
|
-
|
|
51
|
-
<IconBox aria-hidden>{icon}</IconBox>
|
|
55
|
+
export const QuickStartButton = ({ label, onClick, background, className, color, icon, style, manageOverflow }: Props) => {
|
|
56
|
+
const content = <>
|
|
57
|
+
{icon && <IconBox aria-hidden>{icon}</IconBox>}
|
|
52
58
|
<Text>{label}</Text>
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
</>
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<QuickButton className={className} style={style} onClick={onClick} $color={color} $bg={background}>
|
|
63
|
+
{manageOverflow ? <FadingOverflow sides={['top', 'bottom']} scroll="wheel">{content}</FadingOverflow> : content}
|
|
64
|
+
</QuickButton>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Card } from '@citric/ui'
|
|
2
2
|
import { styled } from 'styled-components'
|
|
3
|
+
import { panelAnimationTime } from '../right-panel/constants'
|
|
3
4
|
import { PropsOf } from '../types'
|
|
4
5
|
import { AutoFocus } from './AutoFocus'
|
|
5
6
|
import { FallbackBoundary } from './FallbackBoundary'
|
|
@@ -46,7 +47,7 @@ const Form = styled.form`
|
|
|
46
47
|
*/
|
|
47
48
|
export const RightPanelForm = ({ children, onSubmit, ...props }: PropsOf<typeof Form>) => (
|
|
48
49
|
<FallbackBoundary>
|
|
49
|
-
<AutoFocus>
|
|
50
|
+
<AutoFocus delay={panelAnimationTime}>
|
|
50
51
|
<Form
|
|
51
52
|
{...props}
|
|
52
53
|
onSubmit={(e) => {
|
package/src/context/hooks.ts
CHANGED
|
@@ -39,11 +39,11 @@ export function useWidgetState<K extends keyof WidgetProperties>(key: K): Widget
|
|
|
39
39
|
/**
|
|
40
40
|
* Watches the tabs of a ChatState. The value is updated whenever a tab is added, removed or selected.
|
|
41
41
|
*/
|
|
42
|
-
export function useChatTabs(): { chats: ChatState[], active:
|
|
42
|
+
export function useChatTabs(): { chats: ChatState[], active: ChatState } {
|
|
43
43
|
const widget = useWidget()
|
|
44
|
-
const [tabs, setTabs] = useState<{ chats: ChatState[], active:
|
|
45
|
-
chats: widget.chatTabs.getAll(),
|
|
46
|
-
active: widget.chatTabs.
|
|
44
|
+
const [tabs, setTabs] = useState<{ chats: ChatState[], active: ChatState }>({
|
|
45
|
+
chats: widget.chatTabs.getAll(),
|
|
46
|
+
active: widget.chatTabs.getActiveChat(),
|
|
47
47
|
})
|
|
48
48
|
useEffect(() => widget.chatTabs.onChange((chats, active) => setTabs({ chats, active })), [])
|
|
49
49
|
return tabs
|
|
@@ -59,7 +59,7 @@ export function useChatTabs(): { chats: ChatState[], active: string } {
|
|
|
59
59
|
export function useChat(chatId: string): ChatState {
|
|
60
60
|
const widget = useWidget()
|
|
61
61
|
const chat = widget.chatTabs.get(chatId)
|
|
62
|
-
if (!chat) throw new Error(`No chat with id ${chatId} was found. Maybe there are no chats opened
|
|
62
|
+
if (!chat) throw new Error(`No chat with id ${chatId} was found. Maybe there are no chats opened.`)
|
|
63
63
|
return chat
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -71,8 +71,7 @@ export function useChat(chatId: string): ChatState {
|
|
|
71
71
|
* @returns the currently active chat.
|
|
72
72
|
*/
|
|
73
73
|
export function useCurrentChat(): ChatState {
|
|
74
|
-
|
|
75
|
-
return useChat(active)
|
|
74
|
+
return useChatTabs().active
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
/**
|
|
@@ -118,7 +117,7 @@ export function useChatMessages(chatId: string): ChatEntry[] {
|
|
|
118
117
|
*/
|
|
119
118
|
export function useCurrentChatMessages(): ChatEntry[] {
|
|
120
119
|
const { active } = useChatTabs()
|
|
121
|
-
return useChatMessages(active)
|
|
120
|
+
return useChatMessages(active.id)
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
/**
|
package/src/features.ts
CHANGED
|
@@ -1,50 +1,53 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface ChatFeatures {
|
|
2
2
|
/**
|
|
3
3
|
* Enables stack selection.
|
|
4
|
-
* @default true
|
|
5
4
|
*/
|
|
6
|
-
stack
|
|
5
|
+
stack: boolean,
|
|
7
6
|
/**
|
|
8
7
|
* Enables workspace selection.
|
|
9
|
-
* @default true
|
|
10
8
|
*/
|
|
11
|
-
workspace
|
|
9
|
+
workspace: boolean,
|
|
12
10
|
/**
|
|
13
11
|
* Enables knowledge source selection.
|
|
14
|
-
* @default true
|
|
15
12
|
*/
|
|
16
|
-
knowledgeSource
|
|
13
|
+
knowledgeSource: boolean,
|
|
17
14
|
/**
|
|
18
15
|
* Enables agent selection.
|
|
19
|
-
* @default true
|
|
20
16
|
*/
|
|
21
|
-
agent
|
|
17
|
+
agent: boolean,
|
|
22
18
|
/**
|
|
23
19
|
* Enables quick commands.
|
|
24
|
-
* @default true
|
|
25
20
|
*/
|
|
26
|
-
quickCommands
|
|
21
|
+
quickCommands: boolean,
|
|
27
22
|
/**
|
|
28
23
|
* Enables the editor.
|
|
29
|
-
* @default true
|
|
30
24
|
*/
|
|
31
|
-
editor
|
|
25
|
+
editor: boolean,
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
export interface
|
|
28
|
+
export interface GlobalFeatures {
|
|
35
29
|
/**
|
|
36
30
|
* Enables the chat history.
|
|
37
|
-
* @default true
|
|
38
31
|
*/
|
|
39
|
-
chatHistory
|
|
32
|
+
chatHistory: boolean,
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
export type AIWidgetFeatures = ChatFeatures & GlobalFeatures
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Receives a partial feature object and returns a full feature object, setting to true any missing property.
|
|
39
|
+
* @param features the partial features object.
|
|
40
|
+
* @returns a full features object.
|
|
41
|
+
*/
|
|
42
|
+
export function getFeaturesWithDefaults(features?: Partial<AIWidgetFeatures>): AIWidgetFeatures {
|
|
43
|
+
return {
|
|
44
|
+
agent: true,
|
|
45
|
+
editor: true,
|
|
46
|
+
knowledgeSource: true,
|
|
47
|
+
quickCommands: true,
|
|
48
|
+
stack: true,
|
|
49
|
+
workspace: true,
|
|
50
|
+
chatHistory: true,
|
|
51
|
+
...features,
|
|
52
|
+
}
|
|
50
53
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/* Attention: in order for the package "page" to work without linking the lib, we must export types separately, using the "type" keyword */
|
|
2
|
+
|
|
3
|
+
export { QuickStartButton } from './components/QuickStartButton'
|
|
1
4
|
export { AIWidgetProvider } from './context/AIWidgetProvider'
|
|
2
5
|
export * from './context/hooks'
|
|
6
|
+
export { getFeaturesWithDefaults } from './features'
|
|
7
|
+
export type { AIWidgetFeatures, ChatFeatures } from './features'
|
|
3
8
|
export { StackspotAIWidget } from './StackspotAIWidget'
|
|
4
9
|
export { ChatEntry } from './state/ChatEntry'
|
|
5
10
|
export { ChatState } from './state/ChatState'
|
|
6
11
|
export { ChatTabsController } from './state/ChatTabsController'
|
|
7
12
|
export { ObservableState } from './state/ObservableState'
|
|
13
|
+
export type { Labeled, LabeledWithImage } from './state/types'
|
|
8
14
|
export { WidgetState } from './state/WidgetState'
|
package/src/layout.css
CHANGED
package/src/state/ChatState.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { dropRight, last, pull } from 'lodash'
|
|
2
2
|
import { ulid } from 'ulid'
|
|
3
3
|
import { AbortedError } from '../AbortedError'
|
|
4
|
+
import { ChatFeatures, getFeaturesWithDefaults } from '../features'
|
|
4
5
|
import { ChatEntry } from './ChatEntry'
|
|
5
6
|
import { ObservableState } from './ObservableState'
|
|
6
7
|
import { Labeled, LabeledWithImage } from './types'
|
|
7
8
|
|
|
8
|
-
export interface
|
|
9
|
+
export interface ChatPropertiesWithOptionalFeatures {
|
|
9
10
|
/**
|
|
10
11
|
* The name of the chat.
|
|
11
12
|
*/
|
|
@@ -46,6 +47,19 @@ export interface ChatProperties {
|
|
|
46
47
|
* The current selection in the editor.
|
|
47
48
|
*/
|
|
48
49
|
codeSelection?: string,
|
|
50
|
+
/**
|
|
51
|
+
* The features enabled for this chat.
|
|
52
|
+
*
|
|
53
|
+
* If a feature is marked as false, it's disabled, otherwise it's enabled.
|
|
54
|
+
*/
|
|
55
|
+
features?: Partial<ChatFeatures>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ChatProperties extends ChatPropertiesWithOptionalFeatures {
|
|
59
|
+
/**
|
|
60
|
+
* The features enabled for this chat.
|
|
61
|
+
*/
|
|
62
|
+
features: ChatFeatures,
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
type ChatMessagesListener = (chat: ChatEntry[]) => void
|
|
@@ -62,7 +76,7 @@ interface Options {
|
|
|
62
76
|
/**
|
|
63
77
|
* The initial value for the state of this chat.
|
|
64
78
|
*/
|
|
65
|
-
initial:
|
|
79
|
+
initial: ChatPropertiesWithOptionalFeatures,
|
|
66
80
|
/**
|
|
67
81
|
* The interceptors to use for the messages pushed to this chat.
|
|
68
82
|
*
|
|
@@ -86,6 +100,12 @@ interface Options {
|
|
|
86
100
|
* The content of this chat, i.e. its messages.
|
|
87
101
|
*/
|
|
88
102
|
entries?: ChatEntry[],
|
|
103
|
+
/**
|
|
104
|
+
* Whether or not the label for this chat is real (persisted in the backend) or just a mock.
|
|
105
|
+
*
|
|
106
|
+
* This should be true if the label is just a mock.
|
|
107
|
+
*/
|
|
108
|
+
untitled?: boolean,
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
/**
|
|
@@ -104,12 +124,14 @@ export class ChatState extends ObservableState<ChatProperties> {
|
|
|
104
124
|
* Abort signals currently active.
|
|
105
125
|
*/
|
|
106
126
|
private abortions: AbortController[] = []
|
|
127
|
+
untitled: boolean
|
|
107
128
|
|
|
108
|
-
constructor({ id, initial, entries = [], interceptors = [] }: Options) {
|
|
109
|
-
super(initial)
|
|
129
|
+
constructor({ id, initial, entries = [], interceptors = [], untitled = false }: Options) {
|
|
130
|
+
super({ ...initial, features: getFeaturesWithDefaults(initial.features) })
|
|
110
131
|
this.id = id
|
|
111
132
|
this.interceptors = interceptors
|
|
112
133
|
this.entries = entries
|
|
134
|
+
this.untitled = untitled
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
private runMessagesListeners() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { last, pull } from 'lodash'
|
|
1
|
+
import { groupBy, last, pull } from 'lodash'
|
|
2
2
|
import { ChatState } from './ChatState'
|
|
3
3
|
|
|
4
|
-
type TabChangeListener = (chats: ChatState[],
|
|
4
|
+
type TabChangeListener = (chats: ChatState[], active: ChatState) => void
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Controls the chat tabs.
|
|
@@ -12,7 +12,7 @@ export class ChatTabsController {
|
|
|
12
12
|
private listeners: TabChangeListener[] = []
|
|
13
13
|
|
|
14
14
|
private runListeners() {
|
|
15
|
-
this.listeners.forEach(l => l(this.chats, this.
|
|
15
|
+
this.listeners.forEach(l => l(this.chats, this.getActiveChat()))
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -26,20 +26,50 @@ export class ChatTabsController {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* If the active chat is deleted, this selects a new chat based on the position of the chat that has been removed.
|
|
30
|
+
*
|
|
31
|
+
* @param previous the previous tabs.
|
|
32
|
+
* @param deleted the ids of the chats that have been deleted.
|
|
31
33
|
*/
|
|
32
|
-
|
|
33
|
-
if (this.
|
|
34
|
-
|
|
35
|
-
this.chats = this.chats.filter(c => !ids.includes(c.id))
|
|
36
|
-
if (ids.includes(this.activeChatId)) {
|
|
34
|
+
private reselect(previous: ChatState[], deleted: string[]) {
|
|
35
|
+
if (deleted.includes(this.activeChatId)) {
|
|
36
|
+
const currentActiveIndex = previous.findIndex(c => c.id === this.activeChatId)
|
|
37
37
|
if (currentActiveIndex === -1) this.activeChatId = this.chats[0]?.id
|
|
38
38
|
this.activeChatId = currentActiveIndex < this.chats.length ? this.chats[currentActiveIndex].id : last(this.chats)?.id ?? ''
|
|
39
39
|
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Removes chats from the tab view. This will never remove a chat if this chat is the last one.
|
|
44
|
+
*
|
|
45
|
+
* @param ids the ids of the chats to remove.
|
|
46
|
+
*/
|
|
47
|
+
remove(...ids: string[]) {
|
|
48
|
+
ids.splice(this.chats.length - 1, ids.length - this.chats.length)
|
|
49
|
+
if (!ids.length) return
|
|
50
|
+
const previous = this.chats
|
|
51
|
+
this.chats = this.chats.filter(c => !ids.includes(c.id))
|
|
52
|
+
this.reselect(previous, ids)
|
|
40
53
|
this.runListeners()
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Removes all empty chats. The last chat will be kept if it's empty, but the only chat remaining.
|
|
58
|
+
*
|
|
59
|
+
* @param exceptions ids of chats that shouldn't be removed even if empty.
|
|
60
|
+
*/
|
|
61
|
+
removeEmptyChats(...exceptions: string[]) {
|
|
62
|
+
const lastChat = last(this.chats)
|
|
63
|
+
const previous = this.chats
|
|
64
|
+
const { true: removed, false: kept = [] } = groupBy(this.chats, c => c.getMessages().length === 0 && !exceptions.includes(c.id))
|
|
65
|
+
if (removed) {
|
|
66
|
+
this.chats = kept
|
|
67
|
+
if (lastChat && this.chats.length === 0) this.chats.push(lastChat)
|
|
68
|
+
this.reselect(previous, removed.map(c => c.id))
|
|
69
|
+
this.runListeners()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
43
73
|
/**
|
|
44
74
|
* @param id the id of the chat to retrieve.
|
|
45
75
|
* @returns a ChatState corresponding to the id.
|
|
@@ -49,12 +79,21 @@ export class ChatTabsController {
|
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
/**
|
|
52
|
-
* @returns the
|
|
82
|
+
* @returns the id of the chat corresponding to the tab that is currently active.
|
|
53
83
|
*/
|
|
54
84
|
getActiveChatId() {
|
|
55
85
|
return this.activeChatId
|
|
56
86
|
}
|
|
57
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @returns the chat corresponding to the tab that is currently active.
|
|
90
|
+
*/
|
|
91
|
+
getActiveChat() {
|
|
92
|
+
const chat = this.chats.find(c => c.id === this.activeChatId)
|
|
93
|
+
if (!chat) throw new Error('Chat state error, the active chat id corresponds to none chat in the tabs.')
|
|
94
|
+
return chat
|
|
95
|
+
}
|
|
96
|
+
|
|
58
97
|
/**
|
|
59
98
|
* @returns all chats in tab view.
|
|
60
99
|
*/
|
package/src/state/WidgetState.ts
CHANGED
|
@@ -3,8 +3,9 @@ import { ulid } from 'ulid'
|
|
|
3
3
|
import { quickCommandQuestionsInterceptor } from '../chat-interceptors/quick-command-questions'
|
|
4
4
|
import { createQuickCommandInterceptor } from '../chat-interceptors/quick-commands'
|
|
5
5
|
import { sendMessageInterceptor } from '../chat-interceptors/send-message'
|
|
6
|
+
import { AIWidgetFeatures, ChatFeatures, getFeaturesWithDefaults, GlobalFeatures } from '../features'
|
|
6
7
|
import { ChatEntry } from './ChatEntry'
|
|
7
|
-
import {
|
|
8
|
+
import { ChatPropertiesWithOptionalFeatures, ChatState, MessageInterceptor } from './ChatState'
|
|
8
9
|
import { ChatTabsController } from './ChatTabsController'
|
|
9
10
|
import { ObservableState } from './ObservableState'
|
|
10
11
|
|
|
@@ -22,27 +23,39 @@ export interface WidgetProperties {
|
|
|
22
23
|
*/
|
|
23
24
|
isMinimized?: boolean,
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
+
* Global features of the chat widget. These features don't depend on the chat currently selected.
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
+
features: GlobalFeatures,
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
interface Options {
|
|
31
32
|
/**
|
|
32
33
|
* Chat interceptors allows you to intercept chat messages, interpret them, modify them and even change the chat flow. See
|
|
33
34
|
* {@link ChatState} for more details.
|
|
35
|
+
*
|
|
36
|
+
* These interceptors, in addition to the default interceptors, will be used when creating a new chat via `WidgetState#createChat`.
|
|
34
37
|
*/
|
|
35
38
|
interceptors?: MessageInterceptor[],
|
|
39
|
+
/**
|
|
40
|
+
* Features to enable in the widget. If a feature is not set explicitly to false, it's interpreted as enabled.
|
|
41
|
+
*
|
|
42
|
+
* These features will be used when creating a new chat via `WidgetState#createChat`. Updating chat features only influence new chats.
|
|
43
|
+
*/
|
|
44
|
+
features?: Partial<AIWidgetFeatures>,
|
|
36
45
|
/**
|
|
37
46
|
* The initial values for the widget state.
|
|
47
|
+
*
|
|
48
|
+
* The default value for each feature under `initial.features` is true.
|
|
38
49
|
*/
|
|
39
|
-
initial?: WidgetProperties,
|
|
50
|
+
initial?: Omit<WidgetProperties, 'features'>,
|
|
40
51
|
/**
|
|
41
52
|
* The initial tabs for the chat window.
|
|
42
53
|
*/
|
|
43
54
|
chatTabs?: ChatTabsController,
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
const untitledChatPrefix = 'Chat '
|
|
58
|
+
|
|
46
59
|
/**
|
|
47
60
|
* Holds the full state of the AI Chat Widget.
|
|
48
61
|
*
|
|
@@ -55,12 +68,14 @@ export class WidgetState extends ObservableState<WidgetProperties> {
|
|
|
55
68
|
*/
|
|
56
69
|
interceptors: MessageInterceptor[] = []
|
|
57
70
|
/**
|
|
58
|
-
*
|
|
71
|
+
* Chat features to be used by default when creating a new chat.
|
|
59
72
|
*/
|
|
60
|
-
|
|
73
|
+
chatFeatures: ChatFeatures
|
|
61
74
|
|
|
62
|
-
constructor({ chatTabs, initial
|
|
63
|
-
|
|
75
|
+
constructor({ chatTabs, initial, interceptors = [], features }: Options = {}) {
|
|
76
|
+
const featuresWithDefaults = getFeaturesWithDefaults(features)
|
|
77
|
+
super({ ...initial, features: featuresWithDefaults })
|
|
78
|
+
this.chatFeatures = featuresWithDefaults
|
|
64
79
|
this.chatTabs = chatTabs ?? new ChatTabsController()
|
|
65
80
|
this.interceptors = [
|
|
66
81
|
...interceptors,
|
|
@@ -71,21 +86,32 @@ export class WidgetState extends ObservableState<WidgetProperties> {
|
|
|
71
86
|
this.createChat()
|
|
72
87
|
}
|
|
73
88
|
|
|
89
|
+
private getNextUntitledChatIndex() {
|
|
90
|
+
let max = 0
|
|
91
|
+
for (const chat of this.chatTabs.getAll()) {
|
|
92
|
+
const [, match] = chat.get('label').match(`${untitledChatPrefix}(\\d+)`) ?? []
|
|
93
|
+
const index = parseInt(match)
|
|
94
|
+
if (index > max) max = index
|
|
95
|
+
}
|
|
96
|
+
return max + 1
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
/**
|
|
75
|
-
* Utility function for adding a new chat to the chat tabs.
|
|
100
|
+
* Utility function for adding a new chat to the chat tabs. This also selects the new tab.
|
|
76
101
|
* @param properties the chat properties (initial state).
|
|
77
102
|
* @param entries the entries for the chat to start with.
|
|
78
|
-
* @returns
|
|
103
|
+
* @returns the chat created.
|
|
79
104
|
*/
|
|
80
|
-
createChat(properties
|
|
105
|
+
createChat({ label, ...properties }: Partial<ChatPropertiesWithOptionalFeatures> = {}, entries: ChatEntry[] = []) {
|
|
81
106
|
const chat = new ChatState({
|
|
82
107
|
id: ulid(),
|
|
83
|
-
initial: { label:
|
|
108
|
+
initial: { label: label || `${untitledChatPrefix}${this.getNextUntitledChatIndex()}`, features: this.chatFeatures, ...properties },
|
|
84
109
|
entries,
|
|
85
110
|
interceptors: this.interceptors,
|
|
111
|
+
untitled: !label,
|
|
86
112
|
})
|
|
87
113
|
this.chatTabs.add(chat)
|
|
88
114
|
this.chatTabs.select(chat.id)
|
|
89
|
-
return
|
|
115
|
+
return chat
|
|
90
116
|
}
|
|
91
117
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Text } from '@citric/core'
|
|
2
|
+
import { Badge, Skeleton } from '@citric/ui'
|
|
3
|
+
import { agentClient } from '@stack-spot/portal-network'
|
|
4
|
+
import { useMemo } from 'react'
|
|
5
|
+
import { useAgentsDictionary } from './dictionary'
|
|
6
|
+
import { AgentDescriptionBox } from './styled'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
agentId?: string,
|
|
10
|
+
llm?: string,
|
|
11
|
+
description?: string,
|
|
12
|
+
numberOfKnowledgeSources: number,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const AgentDescription = ({ agentId, llm, description, numberOfKnowledgeSources }: Props) => {
|
|
16
|
+
const t = useAgentsDictionary()
|
|
17
|
+
const [agent,,, { isLoading }] = agentClient.agent.useStatefulQuery({ agentId: agentId! }, { enabled: !!agentId })
|
|
18
|
+
const knowledgeSources = useMemo(
|
|
19
|
+
() => agent?.knowledge_sources_config?.knowledge_sources_details?.map((ks, index) => (
|
|
20
|
+
<li key={index}><Badge palette="teal" appearance="square">{ks.name}</Badge></li>
|
|
21
|
+
)),
|
|
22
|
+
[agent],
|
|
23
|
+
)
|
|
24
|
+
const skeleton = useMemo(() => {
|
|
25
|
+
const loadingKS: React.ReactElement[] = []
|
|
26
|
+
for (let i = 0; i < numberOfKnowledgeSources; i++) {
|
|
27
|
+
loadingKS.push(<li key={i}><Badge palette="teal" appearance="square"><Skeleton className="ks-skeleton" /></Badge></li>)
|
|
28
|
+
}
|
|
29
|
+
return loadingKS
|
|
30
|
+
}, [numberOfKnowledgeSources])
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<AgentDescriptionBox>
|
|
34
|
+
{description && <section>
|
|
35
|
+
<Text appearance="microtext1" className="title">{t.description}</Text>
|
|
36
|
+
<Text>{description}</Text>
|
|
37
|
+
</section>}
|
|
38
|
+
{(!!numberOfKnowledgeSources || !!knowledgeSources?.length) && <section>
|
|
39
|
+
<Text appearance="microtext1" className="title">Knowledge sources</Text>
|
|
40
|
+
<ul>{isLoading ? skeleton : knowledgeSources}</ul>
|
|
41
|
+
</section>}
|
|
42
|
+
{llm && <section>
|
|
43
|
+
<Text appearance="microtext1" className="title">LLM</Text>
|
|
44
|
+
<Badge palette="orange" appearance="square">{llm}</Badge>
|
|
45
|
+
</section>}
|
|
46
|
+
</AgentDescriptionBox>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RightPanelTabs } from '../../components/RightPanelTabs'
|
|
2
|
+
import { useCurrentChat } from '../../context/hooks'
|
|
3
|
+
import { AgentsTab } from './AgentsTab'
|
|
4
|
+
import { useAgentsDictionary } from './dictionary'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders the Agent selection form in the Right Panel if this is the panel that is currently opened.
|
|
8
|
+
*/
|
|
9
|
+
export const AgentsPanel = () => {
|
|
10
|
+
const t = useAgentsDictionary()
|
|
11
|
+
const chat = useCurrentChat()
|
|
12
|
+
|
|
13
|
+
return <RightPanelTabs key={chat.id} tabs={[
|
|
14
|
+
{ title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
|
|
15
|
+
{ title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
|
|
16
|
+
{ title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" /> },
|
|
17
|
+
{ title: t.account, content: <AgentsTab key="account" visibility="ACCOUNT" /> },
|
|
18
|
+
]} />
|
|
19
|
+
}
|