@stack-spot/ai-chat-widget 0.1.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/dist/StackspotAIWidget.d.ts +13 -0
- package/dist/StackspotAIWidget.d.ts.map +1 -0
- package/dist/StackspotAIWidget.js +32 -0
- package/dist/StackspotAIWidget.js.map +1 -0
- package/dist/chat-interceptors/quick-commands.d.ts +4 -0
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -0
- package/dist/chat-interceptors/quick-commands.js +11 -0
- package/dist/chat-interceptors/quick-commands.js.map +1 -0
- package/dist/chat-interceptors/send-message.d.ts +4 -0
- package/dist/chat-interceptors/send-message.d.ts.map +1 -0
- package/dist/chat-interceptors/send-message.js +36 -0
- package/dist/chat-interceptors/send-message.js.map +1 -0
- package/dist/components/Accordion.d.ts +10 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +46 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/AdaptiveTextArea.d.ts +10 -0
- package/dist/components/AdaptiveTextArea.d.ts.map +1 -0
- package/dist/components/AdaptiveTextArea.js +28 -0
- package/dist/components/AdaptiveTextArea.js.map +1 -0
- package/dist/components/Code.d.ts +19 -0
- package/dist/components/Code.d.ts.map +1 -0
- package/dist/components/Code.js +112 -0
- package/dist/components/Code.js.map +1 -0
- package/dist/components/Editor.d.ts +9 -0
- package/dist/components/Editor.d.ts.map +1 -0
- package/dist/components/Editor.js +2 -0
- package/dist/components/Editor.js.map +1 -0
- package/dist/components/FadingOverflow.d.ts +12 -0
- package/dist/components/FadingOverflow.d.ts.map +1 -0
- package/dist/components/FadingOverflow.js +216 -0
- package/dist/components/FadingOverflow.js.map +1 -0
- package/dist/components/FallbackBoundary/ErrorBoundary.d.ts +30 -0
- package/dist/components/FallbackBoundary/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/FallbackBoundary/ErrorBoundary.js +38 -0
- package/dist/components/FallbackBoundary/ErrorBoundary.js.map +1 -0
- package/dist/components/FallbackBoundary/Loading.d.ts +2 -0
- package/dist/components/FallbackBoundary/Loading.d.ts.map +1 -0
- package/dist/components/FallbackBoundary/Loading.js +12 -0
- package/dist/components/FallbackBoundary/Loading.js.map +1 -0
- package/dist/components/FallbackBoundary/index.d.ts +6 -0
- package/dist/components/FallbackBoundary/index.d.ts.map +1 -0
- package/dist/components/FallbackBoundary/index.js +9 -0
- package/dist/components/FallbackBoundary/index.js.map +1 -0
- package/dist/components/HistoryList.d.ts +13 -0
- package/dist/components/HistoryList.d.ts.map +1 -0
- package/dist/components/HistoryList.js +4 -0
- package/dist/components/HistoryList.js.map +1 -0
- package/dist/components/IconInput.d.ts +7 -0
- package/dist/components/IconInput.d.ts.map +1 -0
- package/dist/components/IconInput.js +58 -0
- package/dist/components/IconInput.js.map +1 -0
- package/dist/components/Markdown.d.ts +9 -0
- package/dist/components/Markdown.d.ts.map +1 -0
- package/dist/components/Markdown.js +17 -0
- package/dist/components/Markdown.js.map +1 -0
- package/dist/components/OverlayMenu.d.ts +9 -0
- package/dist/components/OverlayMenu.d.ts.map +1 -0
- package/dist/components/OverlayMenu.js +2 -0
- package/dist/components/OverlayMenu.js.map +1 -0
- package/dist/components/ProgressBar.d.ts +11 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +126 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/QuickStartButton.d.ts +8 -0
- package/dist/components/QuickStartButton.d.ts.map +1 -0
- package/dist/components/QuickStartButton.js +40 -0
- package/dist/components/QuickStartButton.js.map +1 -0
- package/dist/components/RightPanelForm.d.ts +5 -0
- package/dist/components/RightPanelForm.d.ts.map +1 -0
- package/dist/components/RightPanelForm.js +45 -0
- package/dist/components/RightPanelForm.js.map +1 -0
- package/dist/components/RightPanelTabs.d.ts +10 -0
- package/dist/components/RightPanelTabs.d.ts.map +1 -0
- package/dist/components/RightPanelTabs.js +20 -0
- package/dist/components/RightPanelTabs.js.map +1 -0
- package/dist/components/TabManager.d.ts +34 -0
- package/dist/components/TabManager.d.ts.map +1 -0
- package/dist/components/TabManager.js +158 -0
- package/dist/components/TabManager.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.d.ts +11 -0
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.js +9 -0
- package/dist/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/components/Tooltip/TooltipAPI.d.ts +10 -0
- package/dist/components/Tooltip/TooltipAPI.d.ts.map +1 -0
- package/dist/components/Tooltip/TooltipAPI.js +48 -0
- package/dist/components/Tooltip/TooltipAPI.js.map +1 -0
- package/dist/components/Tooltip/context.d.ts +5 -0
- package/dist/components/Tooltip/context.d.ts.map +1 -0
- package/dist/components/Tooltip/context.js +18 -0
- package/dist/components/Tooltip/context.js.map +1 -0
- package/dist/components/Tooltip/index.d.ts +3 -0
- package/dist/components/Tooltip/index.d.ts.map +1 -0
- package/dist/components/Tooltip/index.js +3 -0
- package/dist/components/Tooltip/index.js.map +1 -0
- package/dist/components/Tooltip/style.d.ts +4 -0
- package/dist/components/Tooltip/style.d.ts.map +1 -0
- package/dist/components/Tooltip/style.js +23 -0
- package/dist/components/Tooltip/style.js.map +1 -0
- package/dist/components/Tooltip/types.d.ts +8 -0
- package/dist/components/Tooltip/types.d.ts.map +1 -0
- package/dist/components/Tooltip/types.js +2 -0
- package/dist/components/Tooltip/types.js.map +1 -0
- package/dist/components/form/DescribedCheckboxGroup.d.ts +3 -0
- package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -0
- package/dist/components/form/DescribedCheckboxGroup.js +23 -0
- package/dist/components/form/DescribedCheckboxGroup.js.map +1 -0
- package/dist/components/form/DescribedRadioGroup.d.ts +3 -0
- package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -0
- package/dist/components/form/DescribedRadioGroup.js +18 -0
- package/dist/components/form/DescribedRadioGroup.js.map +1 -0
- package/dist/components/form/styled.d.ts +2 -0
- package/dist/components/form/styled.d.ts.map +1 -0
- package/dist/components/form/styled.js +41 -0
- package/dist/components/form/styled.js.map +1 -0
- package/dist/components/form/types.d.ts +19 -0
- package/dist/components/form/types.d.ts.map +1 -0
- package/dist/components/form/types.js +2 -0
- package/dist/components/form/types.js.map +1 -0
- package/dist/context/AIWidgetProvider.d.ts +4 -0
- package/dist/context/AIWidgetProvider.d.ts.map +1 -0
- package/dist/context/AIWidgetProvider.js +4 -0
- package/dist/context/AIWidgetProvider.js.map +1 -0
- package/dist/context/hooks.d.ts +18 -0
- package/dist/context/hooks.d.ts.map +1 -0
- package/dist/context/hooks.js +82 -0
- package/dist/context/hooks.js.map +1 -0
- package/dist/features.d.ts +13 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/features.js +6 -0
- package/dist/features.js.map +1 -0
- package/dist/hooks/chat-scroll.d.ts +7 -0
- package/dist/hooks/chat-scroll.d.ts.map +1 -0
- package/dist/hooks/chat-scroll.js +15 -0
- package/dist/hooks/chat-scroll.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.css +119 -0
- package/dist/right-panel/DefaultPanel.d.ts +9 -0
- package/dist/right-panel/DefaultPanel.d.ts.map +1 -0
- package/dist/right-panel/DefaultPanel.js +46 -0
- package/dist/right-panel/DefaultPanel.js.map +1 -0
- package/dist/right-panel/RightPanel.d.ts +2 -0
- package/dist/right-panel/RightPanel.d.ts.map +1 -0
- package/dist/right-panel/RightPanel.js +6 -0
- package/dist/right-panel/RightPanel.js.map +1 -0
- package/dist/right-panel/RightPanelProvider.d.ts +13 -0
- package/dist/right-panel/RightPanelProvider.d.ts.map +1 -0
- package/dist/right-panel/RightPanelProvider.js +10 -0
- package/dist/right-panel/RightPanelProvider.js.map +1 -0
- package/dist/right-panel/hooks.d.ts +13 -0
- package/dist/right-panel/hooks.d.ts.map +1 -0
- package/dist/right-panel/hooks.js +39 -0
- package/dist/right-panel/hooks.js.map +1 -0
- package/dist/state/ChatEntry.d.ts +48 -0
- package/dist/state/ChatEntry.d.ts.map +1 -0
- package/dist/state/ChatEntry.js +55 -0
- package/dist/state/ChatEntry.js.map +1 -0
- package/dist/state/ChatState.d.ts +46 -0
- package/dist/state/ChatState.d.ts.map +1 -0
- package/dist/state/ChatState.js +53 -0
- package/dist/state/ChatState.js.map +1 -0
- package/dist/state/ChatTabsController.d.ts +17 -0
- package/dist/state/ChatTabsController.d.ts.map +1 -0
- package/dist/state/ChatTabsController.js +47 -0
- package/dist/state/ChatTabsController.js.map +1 -0
- package/dist/state/ObservableState.d.ts +10 -0
- package/dist/state/ObservableState.d.ts.map +1 -0
- package/dist/state/ObservableState.js +25 -0
- package/dist/state/ObservableState.js.map +1 -0
- package/dist/state/WidgetState.d.ts +19 -0
- package/dist/state/WidgetState.d.ts.map +1 -0
- package/dist/state/WidgetState.js +29 -0
- package/dist/state/WidgetState.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/chat.d.ts +6 -0
- package/dist/utils/chat.d.ts.map +1 -0
- package/dist/utils/chat.js +26 -0
- package/dist/utils/chat.js.map +1 -0
- package/dist/utils/date.d.ts +6 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +38 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/views/Agents.d.ts +2 -0
- package/dist/views/Agents.d.ts.map +1 -0
- package/dist/views/Agents.js +2 -0
- package/dist/views/Agents.js.map +1 -0
- package/dist/views/Chat/AgentInfo.d.ts +6 -0
- package/dist/views/Chat/AgentInfo.d.ts.map +1 -0
- package/dist/views/Chat/AgentInfo.js +7 -0
- package/dist/views/Chat/AgentInfo.js.map +1 -0
- package/dist/views/Chat/ChatMessage.d.ts +6 -0
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -0
- package/dist/views/Chat/ChatMessage.js +61 -0
- package/dist/views/Chat/ChatMessage.js.map +1 -0
- package/dist/views/Chat/ChatMessages.d.ts +7 -0
- package/dist/views/Chat/ChatMessages.d.ts.map +1 -0
- package/dist/views/Chat/ChatMessages.js +12 -0
- package/dist/views/Chat/ChatMessages.js.map +1 -0
- package/dist/views/Chat/index.d.ts +6 -0
- package/dist/views/Chat/index.d.ts.map +1 -0
- package/dist/views/Chat/index.js +8 -0
- package/dist/views/Chat/index.js.map +1 -0
- package/dist/views/Chat/styled.d.ts +2 -0
- package/dist/views/Chat/styled.d.ts.map +1 -0
- package/dist/views/Chat/styled.js +116 -0
- package/dist/views/Chat/styled.js.map +1 -0
- package/dist/views/ChatTabSelection.d.ts +8 -0
- package/dist/views/ChatTabSelection.d.ts.map +1 -0
- package/dist/views/ChatTabSelection.js +45 -0
- package/dist/views/ChatTabSelection.js.map +1 -0
- package/dist/views/Editor.d.ts +2 -0
- package/dist/views/Editor.d.ts.map +1 -0
- package/dist/views/Editor.js +2 -0
- package/dist/views/Editor.js.map +1 -0
- package/dist/views/Home.d.ts +6 -0
- package/dist/views/Home.d.ts.map +1 -0
- package/dist/views/Home.js +68 -0
- package/dist/views/Home.js.map +1 -0
- package/dist/views/KnowledgeSources.d.ts +2 -0
- package/dist/views/KnowledgeSources.d.ts.map +1 -0
- package/dist/views/KnowledgeSources.js +82 -0
- package/dist/views/KnowledgeSources.js.map +1 -0
- package/dist/views/MessageInput/ButtonGroup.d.ts +12 -0
- package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -0
- package/dist/views/MessageInput/ButtonGroup.js +22 -0
- package/dist/views/MessageInput/ButtonGroup.js.map +1 -0
- package/dist/views/MessageInput/InfoBar.d.ts +2 -0
- package/dist/views/MessageInput/InfoBar.d.ts.map +1 -0
- package/dist/views/MessageInput/InfoBar.js +28 -0
- package/dist/views/MessageInput/InfoBar.js.map +1 -0
- package/dist/views/MessageInput/dictionary.d.ts +2 -0
- package/dist/views/MessageInput/dictionary.d.ts.map +1 -0
- package/dist/views/MessageInput/dictionary.js +35 -0
- package/dist/views/MessageInput/dictionary.js.map +1 -0
- package/dist/views/MessageInput/index.d.ts +7 -0
- package/dist/views/MessageInput/index.d.ts.map +1 -0
- package/dist/views/MessageInput/index.js +43 -0
- package/dist/views/MessageInput/index.js.map +1 -0
- package/dist/views/MessageInput/styled.d.ts +2 -0
- package/dist/views/MessageInput/styled.d.ts.map +1 -0
- package/dist/views/MessageInput/styled.js +195 -0
- package/dist/views/MessageInput/styled.js.map +1 -0
- package/dist/views/MinimizedHeader.d.ts +3 -0
- package/dist/views/MinimizedHeader.d.ts.map +1 -0
- package/dist/views/MinimizedHeader.js +76 -0
- package/dist/views/MinimizedHeader.js.map +1 -0
- package/dist/views/Stacks.d.ts +2 -0
- package/dist/views/Stacks.d.ts.map +1 -0
- package/dist/views/Stacks.js +73 -0
- package/dist/views/Stacks.js.map +1 -0
- package/dist/views/Workspaces.d.ts +2 -0
- package/dist/views/Workspaces.d.ts.map +1 -0
- package/dist/views/Workspaces.js +59 -0
- package/dist/views/Workspaces.js.map +1 -0
- package/package.json +52 -0
- package/src/StackspotAIWidget.tsx +65 -0
- package/src/chat-interceptors/quick-commands.ts +12 -0
- package/src/chat-interceptors/send-message.ts +35 -0
- package/src/components/Accordion.tsx +64 -0
- package/src/components/AdaptiveTextArea.tsx +34 -0
- package/src/components/Code.tsx +201 -0
- package/src/components/Editor.tsx +12 -0
- package/src/components/FadingOverflow.tsx +234 -0
- package/src/components/FallbackBoundary/ErrorBoundary.tsx +48 -0
- package/src/components/FallbackBoundary/Loading.tsx +14 -0
- package/src/components/FallbackBoundary/index.tsx +15 -0
- package/src/components/HistoryList.tsx +16 -0
- package/src/components/IconInput.tsx +70 -0
- package/src/components/Markdown.tsx +53 -0
- package/src/components/OverlayMenu.tsx +10 -0
- package/src/components/ProgressBar.tsx +153 -0
- package/src/components/QuickStartButton.tsx +51 -0
- package/src/components/RightPanelForm.tsx +55 -0
- package/src/components/RightPanelTabs.tsx +39 -0
- package/src/components/TabManager.tsx +223 -0
- package/src/components/Tooltip/Tooltip.tsx +30 -0
- package/src/components/Tooltip/TooltipAPI.ts +46 -0
- package/src/components/Tooltip/context.tsx +24 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/Tooltip/style.tsx +25 -0
- package/src/components/Tooltip/types.ts +8 -0
- package/src/components/form/DescribedCheckboxGroup.tsx +39 -0
- package/src/components/form/DescribedRadioGroup.tsx +33 -0
- package/src/components/form/styled.ts +41 -0
- package/src/components/form/types.ts +21 -0
- package/src/context/AIWidgetProvider.tsx +6 -0
- package/src/context/hooks.ts +93 -0
- package/src/features.ts +18 -0
- package/src/hooks/chat-scroll.ts +14 -0
- package/src/index.ts +8 -0
- package/src/layout.css +119 -0
- package/src/right-panel/DefaultPanel.tsx +67 -0
- package/src/right-panel/RightPanel.tsx +6 -0
- package/src/right-panel/RightPanelProvider.tsx +20 -0
- package/src/right-panel/hooks.tsx +47 -0
- package/src/state/ChatEntry.ts +95 -0
- package/src/state/ChatState.ts +85 -0
- package/src/state/ChatTabsController.ts +55 -0
- package/src/state/ObservableState.ts +35 -0
- package/src/state/WidgetState.ts +42 -0
- package/src/types.ts +20 -0
- package/src/utils/chat.ts +30 -0
- package/src/utils/date.ts +40 -0
- package/src/views/Agents.tsx +1 -0
- package/src/views/Chat/AgentInfo.tsx +17 -0
- package/src/views/Chat/ChatMessage.tsx +89 -0
- package/src/views/Chat/ChatMessages.tsx +16 -0
- package/src/views/Chat/index.tsx +11 -0
- package/src/views/Chat/styled.ts +116 -0
- package/src/views/ChatTabSelection.tsx +65 -0
- package/src/views/Editor.tsx +1 -0
- package/src/views/Home.tsx +109 -0
- package/src/views/KnowledgeSources.tsx +115 -0
- package/src/views/MessageInput/ButtonGroup.tsx +84 -0
- package/src/views/MessageInput/InfoBar.tsx +69 -0
- package/src/views/MessageInput/dictionary.ts +36 -0
- package/src/views/MessageInput/index.tsx +79 -0
- package/src/views/MessageInput/styled.ts +196 -0
- package/src/views/MinimizedHeader.tsx +94 -0
- package/src/views/Stacks.tsx +104 -0
- package/src/views/Workspaces.tsx +88 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import { useContext, useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import { ChatEntry } from '../state/ChatEntry'
|
|
4
|
+
import { ChatProperties, ChatState, MessageInterceptor } from '../state/ChatState'
|
|
5
|
+
import { ObservableState } from '../state/ObservableState'
|
|
6
|
+
import { WidgetProperties, WidgetState } from '../state/WidgetState'
|
|
7
|
+
import { createNewChat } from '../utils/chat'
|
|
8
|
+
import { AIWidgetContext } from './AIWidgetProvider'
|
|
9
|
+
|
|
10
|
+
let globalWidgetState: WidgetState | undefined
|
|
11
|
+
|
|
12
|
+
export function useWidget(): WidgetState {
|
|
13
|
+
const fromContext = useContext(AIWidgetContext)
|
|
14
|
+
if (fromContext) return fromContext
|
|
15
|
+
globalWidgetState ??= new WidgetState()
|
|
16
|
+
return globalWidgetState
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function useObservableState<T, K extends keyof T>(state: ObservableState<T>, key: K): T[K] {
|
|
20
|
+
const [value, setValue] = useState(state.get(key))
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setValue(state.get(key))
|
|
23
|
+
return state.onChange(key, setValue)
|
|
24
|
+
}, [state])
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useWidgetState<K extends keyof WidgetProperties>(key: K): WidgetProperties[K] {
|
|
29
|
+
const widget = useWidget()
|
|
30
|
+
return useObservableState(widget, key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useChatTabs(): { chats: ChatState[], active: string } {
|
|
34
|
+
const widget = useWidget()
|
|
35
|
+
const [tabs, setTabs] = useState<{ chats: ChatState[], active: string }>({
|
|
36
|
+
chats: widget.chatTabs.getAll(),
|
|
37
|
+
active: widget.chatTabs.getActiveChatId(),
|
|
38
|
+
})
|
|
39
|
+
useEffect(() => widget.chatTabs.onChange((chats, active) => setTabs({ chats, active })), [])
|
|
40
|
+
return tabs
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useChat(chatId: string): ChatState {
|
|
44
|
+
const widget = useWidget()
|
|
45
|
+
const chat = widget.chatTabs.get(chatId)
|
|
46
|
+
if (!chat) throw new Error(`No chat with id ${chatId} was found. Maybe there are no chats opened, try to call useFirstChat first.`)
|
|
47
|
+
return chat
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useCurrentChat(): ChatState {
|
|
51
|
+
const { active } = useChatTabs()
|
|
52
|
+
return useChat(active)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useChatState<K extends keyof ChatProperties>(chatId: string, key: K): ChatProperties[K] {
|
|
56
|
+
const chat = useChat(chatId)
|
|
57
|
+
return useObservableState(chat, key)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useCurrentChatState<K extends keyof ChatProperties>(key: K): ChatProperties[K] {
|
|
61
|
+
const chat = useCurrentChat()
|
|
62
|
+
return useObservableState(chat, key)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useChatMessages(chatId: string): ChatEntry[] {
|
|
66
|
+
const chat = useChat(chatId)
|
|
67
|
+
const [entries, setEntries] = useState(chat.getMessages())
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setEntries(chat.getMessages())
|
|
70
|
+
return chat.onChangeMessages(setEntries)
|
|
71
|
+
}, [chat])
|
|
72
|
+
return entries
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function useCurrentChatMessages(): ChatEntry[] {
|
|
76
|
+
const { active } = useChatTabs()
|
|
77
|
+
return useChatMessages(active)
|
|
78
|
+
}
|
|
79
|
+
|
|
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
|
+
const [content, setContent] = useState(entry.getValue())
|
|
85
|
+
useEffect(() => entry.onChange(setContent), [])
|
|
86
|
+
return content
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useFirstChat(interceptors: MessageInterceptor[]) {
|
|
90
|
+
const widget = useWidget()
|
|
91
|
+
const tabs = widget.chatTabs
|
|
92
|
+
if (!tabs.getAll().length) createNewChat(widget, interceptors)
|
|
93
|
+
}
|
package/src/features.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MessageInputFeatures {
|
|
2
|
+
stack?: boolean,
|
|
3
|
+
workspace?: boolean,
|
|
4
|
+
knowledgeSource?: boolean,
|
|
5
|
+
agent?: boolean,
|
|
6
|
+
quickCommands?: boolean,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AIWidgetFeatures extends MessageInputFeatures {
|
|
10
|
+
editor?: boolean,
|
|
11
|
+
chatHistory?: boolean,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const defaultFeatures: AIWidgetFeatures = {
|
|
15
|
+
stack: true,
|
|
16
|
+
workspace: true,
|
|
17
|
+
knowledgeSource: true,
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scrolls the closest chat (upwards in the tree) to its bottom.
|
|
5
|
+
* @param ref the reference element.
|
|
6
|
+
* @param deps when the deps changes, the chat is scrolled.
|
|
7
|
+
*/
|
|
8
|
+
export function useChatScrollToBottomEffect(ref: React.RefObject<HTMLElement>, deps: any[]) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const chat = ref.current?.closest('.chat-content')
|
|
11
|
+
if (!chat) return
|
|
12
|
+
chat.scrollTop = chat.scrollHeight
|
|
13
|
+
}, deps)
|
|
14
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AIWidgetProvider } from './context/AIWidgetProvider'
|
|
2
|
+
export * from './context/hooks'
|
|
3
|
+
export { StackspotAIWidget } from './StackspotAIWidget'
|
|
4
|
+
export { ChatEntry } from './state/ChatEntry'
|
|
5
|
+
export { ChatState } from './state/ChatState'
|
|
6
|
+
export { ChatTabsController } from './state/ChatTabsController'
|
|
7
|
+
export { ObservableState } from './state/ObservableState'
|
|
8
|
+
export { WidgetState } from './state/WidgetState'
|
package/src/layout.css
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
@font-face {
|
|
2
|
+
font-family: "San Francisco";
|
|
3
|
+
font-weight: 400;
|
|
4
|
+
src: url("https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-regular-webfont.woff");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Scroll bars */
|
|
8
|
+
|
|
9
|
+
::-webkit-scrollbar-track {
|
|
10
|
+
background-color: transparent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
::-webkit-scrollbar {
|
|
14
|
+
width: 0.25rem;
|
|
15
|
+
height: 0.5rem;
|
|
16
|
+
background-color: transparent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
::-webkit-scrollbar-thumb {
|
|
20
|
+
background-color: var(--light-600);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
::-webkit-scrollbar-corner {
|
|
24
|
+
background-color: transparent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Classes */
|
|
28
|
+
|
|
29
|
+
.ai-chat-widget {
|
|
30
|
+
border: 1px solid var(--light-600);
|
|
31
|
+
background-color: var(--light-400);
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
position: relative;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 100%;
|
|
37
|
+
|
|
38
|
+
&.minimized {
|
|
39
|
+
.chat-container {
|
|
40
|
+
padding: 10px;
|
|
41
|
+
width: calc(100% - 20px);
|
|
42
|
+
}
|
|
43
|
+
.minimized-header {
|
|
44
|
+
padding: 0 10px;
|
|
45
|
+
width: calc(100% - 20px);
|
|
46
|
+
}
|
|
47
|
+
.home-page {
|
|
48
|
+
.title {
|
|
49
|
+
margin-top: 40px;
|
|
50
|
+
font-size: 18px;
|
|
51
|
+
}
|
|
52
|
+
.subtitle {
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
}
|
|
55
|
+
.title, .subtitle {
|
|
56
|
+
display: block;
|
|
57
|
+
text-align: center;
|
|
58
|
+
margin-left: 20px;
|
|
59
|
+
margin-right: 20px;
|
|
60
|
+
}
|
|
61
|
+
.shortcuts {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
.message-input .action-box{
|
|
66
|
+
.feature-buttons, .expand {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.chat-window {
|
|
74
|
+
transition: width 0.3s;
|
|
75
|
+
width: 100%;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
align-items: center;
|
|
79
|
+
height: 100%;
|
|
80
|
+
|
|
81
|
+
&.narrow {
|
|
82
|
+
width: 50%;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.chat-container {
|
|
87
|
+
padding: 20px;
|
|
88
|
+
max-width: 1200px;
|
|
89
|
+
width: calc(100% - 40px);
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
flex: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.chat-content {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
flex: 1;
|
|
99
|
+
flex-basis: 0;
|
|
100
|
+
overflow: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.chat-right-panel {
|
|
104
|
+
position: absolute;
|
|
105
|
+
width: 50%;
|
|
106
|
+
left: 100%;
|
|
107
|
+
top: 0;
|
|
108
|
+
bottom: 0;
|
|
109
|
+
transition: left 0.3s ease-in-out;
|
|
110
|
+
background-color: var(--light-300);
|
|
111
|
+
border-radius: 4px;
|
|
112
|
+
border-left: 2px solid var(--light-600);
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
|
|
116
|
+
&.visible {
|
|
117
|
+
left: 50%;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Text } from '@citric/core'
|
|
2
|
+
import { Times } from '@citric/icons'
|
|
3
|
+
import { IconButton } from '@citric/ui'
|
|
4
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
5
|
+
import { styled } from 'styled-components'
|
|
6
|
+
import { WithChildren } from '../types'
|
|
7
|
+
|
|
8
|
+
interface Props extends WithChildren {
|
|
9
|
+
title: string,
|
|
10
|
+
description: string,
|
|
11
|
+
onClose: () => void,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PanelBox = styled.div`
|
|
15
|
+
padding: 25px;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 25px;
|
|
19
|
+
flex: 1;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
|
|
22
|
+
header {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
|
|
26
|
+
.title {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: 10px;
|
|
30
|
+
flex: 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
article {
|
|
35
|
+
flex: 1;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
overflow: auto;
|
|
39
|
+
}
|
|
40
|
+
`
|
|
41
|
+
|
|
42
|
+
export const DefaultPanel = ({ description, onClose, title, children }: Props) => {
|
|
43
|
+
const t = useTranslate(dictionary)
|
|
44
|
+
return (
|
|
45
|
+
<PanelBox>
|
|
46
|
+
<header>
|
|
47
|
+
<div className="title">
|
|
48
|
+
<Text appearance="h5">{title}</Text>
|
|
49
|
+
<Text colorScheme="light.700">{description}</Text>
|
|
50
|
+
</div>
|
|
51
|
+
<IconButton title={t.close} aria-label={t.close} onClick={onClose}>
|
|
52
|
+
<Times />
|
|
53
|
+
</IconButton>
|
|
54
|
+
</header>
|
|
55
|
+
<article>{children}</article>
|
|
56
|
+
</PanelBox>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dictionary = {
|
|
61
|
+
en: {
|
|
62
|
+
close: 'Close',
|
|
63
|
+
},
|
|
64
|
+
pt: {
|
|
65
|
+
close: 'Fechar',
|
|
66
|
+
},
|
|
67
|
+
} satisfies Dictionary
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useMemo, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface ContextValue {
|
|
4
|
+
content?: React.ReactNode,
|
|
5
|
+
setContent?: (content: React.ReactNode) => void,
|
|
6
|
+
panel?: React.RefObject<HTMLDivElement>,
|
|
7
|
+
chatWindow?: React.RefObject<HTMLDivElement>,
|
|
8
|
+
onCloseNext: React.MutableRefObject<(() => void) | undefined>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const RightPanelContext = createContext<ContextValue>({ onCloseNext: { current: undefined } })
|
|
12
|
+
|
|
13
|
+
export const RightPanelProvider = (
|
|
14
|
+
{ panel, chatWindow, children }: Pick<ContextValue, 'panel' | 'chatWindow'> & { children: React.ReactNode },
|
|
15
|
+
) => {
|
|
16
|
+
const [content, setContent] = useState<React.ReactNode>()
|
|
17
|
+
const onCloseNext = useRef<(() => void) | undefined>()
|
|
18
|
+
const value = useMemo<ContextValue>(() => ({ content, setContent, panel, chatWindow, onCloseNext }), [content, panel, chatWindow])
|
|
19
|
+
return <RightPanelContext.Provider value={value}>{children}</RightPanelContext.Provider>
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react'
|
|
2
|
+
import { DefaultPanel } from './DefaultPanel'
|
|
3
|
+
import { RightPanelContext } from './RightPanelProvider'
|
|
4
|
+
|
|
5
|
+
interface RightPanelOptions {
|
|
6
|
+
title: string,
|
|
7
|
+
description: string,
|
|
8
|
+
onClose?: () => void,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useRightPanelContent() {
|
|
12
|
+
const { content } = useContext(RightPanelContext)
|
|
13
|
+
return content
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useRightPanel() {
|
|
17
|
+
const ctx = useContext(RightPanelContext)
|
|
18
|
+
const { panel, chatWindow, setContent } = ctx
|
|
19
|
+
|
|
20
|
+
return useMemo(() => {
|
|
21
|
+
if (!setContent) throw new Error('No RightPanelProvider found.')
|
|
22
|
+
|
|
23
|
+
function open(content: React.ReactNode, options?: RightPanelOptions) {
|
|
24
|
+
if (!panel?.current || !chatWindow?.current || !setContent) return
|
|
25
|
+
setContent(options ? <DefaultPanel {...options} onClose={close}>{content}</DefaultPanel> : content)
|
|
26
|
+
panel.current.classList.add('visible')
|
|
27
|
+
chatWindow.current.classList.add('narrow')
|
|
28
|
+
ctx.onCloseNext.current = options?.onClose
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function close() {
|
|
32
|
+
if (!panel?.current || !chatWindow?.current || !setContent) return
|
|
33
|
+
panel.current.classList.remove('visible')
|
|
34
|
+
chatWindow.current.classList.remove('narrow')
|
|
35
|
+
ctx.onCloseNext.current?.()
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
setContent(null)
|
|
38
|
+
}, 300)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isOpen() {
|
|
42
|
+
return !!panel?.current?.classList.contains('visible')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { open, close, isOpen }
|
|
46
|
+
}, [])
|
|
47
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { pull } from 'lodash'
|
|
2
|
+
|
|
3
|
+
export interface SerializableAction {
|
|
4
|
+
title: string,
|
|
5
|
+
type: 'link' | 'command',
|
|
6
|
+
/**
|
|
7
|
+
* The URL if the action is a link or a chat command otherwise (/command).
|
|
8
|
+
*/
|
|
9
|
+
exec: string,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ChatAction extends SerializableAction {
|
|
13
|
+
/**
|
|
14
|
+
* @default primary
|
|
15
|
+
*/
|
|
16
|
+
appearance?: 'primary' | 'secondary',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TextChatEntry {
|
|
20
|
+
type: 'text' | 'md',
|
|
21
|
+
agent: 'bot' | 'user' | 'system',
|
|
22
|
+
// image?: string,
|
|
23
|
+
actions?: ChatAction[],
|
|
24
|
+
subtitle?: string,
|
|
25
|
+
content: string,
|
|
26
|
+
// knowledgeSources?: KnowledgeSource[],
|
|
27
|
+
updated?: string,
|
|
28
|
+
agentId?: string,
|
|
29
|
+
messageId?: string,
|
|
30
|
+
error?: string,
|
|
31
|
+
// customInput?: CustomInputResponse,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type ChatEntryListener = (value: TextChatEntry) => void
|
|
35
|
+
|
|
36
|
+
let nextId = 0
|
|
37
|
+
|
|
38
|
+
export class ChatEntry {
|
|
39
|
+
readonly id: number
|
|
40
|
+
private value: TextChatEntry
|
|
41
|
+
private streamFinished: boolean
|
|
42
|
+
private listeners: ChatEntryListener[] = []
|
|
43
|
+
abort: () => void
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param value the value of the entry.
|
|
47
|
+
* @param isStreamed whether or not this entry is streamed. Defaults to false.
|
|
48
|
+
* @param abort an abort function to cancel the transmission of this chat entry. Specially useful for canceling streamings.
|
|
49
|
+
*/
|
|
50
|
+
constructor(value: TextChatEntry, isStreamed = false, abort = () => {}) {
|
|
51
|
+
this.id = nextId++
|
|
52
|
+
this.value = value
|
|
53
|
+
this.streamFinished = !isStreamed
|
|
54
|
+
this.abort = abort
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static createUserEntry(content: string) {
|
|
58
|
+
return new ChatEntry({
|
|
59
|
+
agent: 'user',
|
|
60
|
+
type: 'text',
|
|
61
|
+
content,
|
|
62
|
+
updated: new Date().toISOString(),
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static createStreamedBotEntry(abort: () => void) {
|
|
67
|
+
return new ChatEntry({ agent: 'bot', type: 'md', content: '' }, true, abort)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setValue(value: TextChatEntry) {
|
|
71
|
+
if (this.streamFinished) return
|
|
72
|
+
this.value = value
|
|
73
|
+
this.listeners.forEach(l => l(this.value))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getValue() {
|
|
77
|
+
return this.value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
finish() {
|
|
81
|
+
this.streamFinished = true
|
|
82
|
+
this.listeners = []
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hasFinished() {
|
|
86
|
+
return this.streamFinished
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onChange(listener: ChatEntryListener) {
|
|
90
|
+
if (!this.streamFinished) this.listeners.push(listener)
|
|
91
|
+
return () => {
|
|
92
|
+
pull(this.listeners, listener)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { dropRight, last, pull } from 'lodash'
|
|
2
|
+
import { ChatEntry } from './ChatEntry'
|
|
3
|
+
import { ObservableState } from './ObservableState'
|
|
4
|
+
|
|
5
|
+
interface Labeled {
|
|
6
|
+
id: string,
|
|
7
|
+
label: string,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChatProperties {
|
|
11
|
+
label: string,
|
|
12
|
+
agent?: Labeled,
|
|
13
|
+
workspace?: Labeled,
|
|
14
|
+
stack?: Labeled,
|
|
15
|
+
knowledgeSources?: Labeled[],
|
|
16
|
+
isLoading?: boolean,
|
|
17
|
+
nextMessage?: string,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ChatMessagesListener = (chat: ChatEntry[]) => void
|
|
21
|
+
|
|
22
|
+
export type MessageInterceptor = (entry: ChatEntry, chat: ChatState) => boolean | undefined | void | Promise<boolean | undefined | void>
|
|
23
|
+
|
|
24
|
+
interface Options {
|
|
25
|
+
id: string,
|
|
26
|
+
initial: ChatProperties,
|
|
27
|
+
interceptors?: MessageInterceptor[],
|
|
28
|
+
entries?: ChatEntry[],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ChatState extends ObservableState<ChatProperties> {
|
|
32
|
+
readonly id: string
|
|
33
|
+
private entries: ChatEntry[]
|
|
34
|
+
private messagesListeners: ChatMessagesListener[] = []
|
|
35
|
+
private readonly interceptors: MessageInterceptor[]
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param id the id of the chat.
|
|
39
|
+
* @param initial the initial state.
|
|
40
|
+
* @param interceptors a list of interceptors to run whenever a new message (entry) is added to the chat. If an interception function
|
|
41
|
+
* returns false, the next interceptors are not run. Attention: when multiple messages are added at once, only the last goes through the
|
|
42
|
+
* interceptors. Furthermore, messages created by the constructor don't go through the interceptors, only messages added via `pushMessage`
|
|
43
|
+
* do.
|
|
44
|
+
*/
|
|
45
|
+
constructor({ id, initial, entries = [], interceptors = [] }: Options) {
|
|
46
|
+
super(initial)
|
|
47
|
+
this.id = id
|
|
48
|
+
this.interceptors = interceptors
|
|
49
|
+
this.entries = entries
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private runMessagesListeners() {
|
|
53
|
+
this.messagesListeners.forEach(l => l(this.entries))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async runInterceptors(entry: ChatEntry) {
|
|
57
|
+
for (const interceptor of this.interceptors) {
|
|
58
|
+
const result = await interceptor(entry, this)
|
|
59
|
+
if (result === false) break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pushMessage(...entries: ChatEntry[]) {
|
|
64
|
+
if (!entries.length) return
|
|
65
|
+
this.entries = [...this.entries, ...entries]
|
|
66
|
+
this.runMessagesListeners()
|
|
67
|
+
this.runInterceptors(last(entries)!)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
popMessage(quantity = 1) {
|
|
71
|
+
this.entries = dropRight(this.entries, quantity)
|
|
72
|
+
this.runMessagesListeners()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getMessages() {
|
|
76
|
+
return this.entries
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onChangeMessages(listener: ChatMessagesListener) {
|
|
80
|
+
this.messagesListeners.push(listener)
|
|
81
|
+
return () => {
|
|
82
|
+
pull(this.messagesListeners, listener)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { last, pull } from 'lodash'
|
|
2
|
+
import { ChatState } from './ChatState'
|
|
3
|
+
|
|
4
|
+
type TabChangeListener = (chats: ChatState[], activeId: string) => void
|
|
5
|
+
|
|
6
|
+
export class ChatTabsController {
|
|
7
|
+
private chats: ChatState[] = []
|
|
8
|
+
private activeChatId = ''
|
|
9
|
+
private listeners: TabChangeListener[] = []
|
|
10
|
+
|
|
11
|
+
private runListeners() {
|
|
12
|
+
this.listeners.forEach(l => l(this.chats, this.activeChatId))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
add(...chats: ChatState[]) {
|
|
16
|
+
if (!chats.length) return
|
|
17
|
+
this.chats = [...this.chats, ...chats]
|
|
18
|
+
this.runListeners()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove(...ids: string[]) {
|
|
22
|
+
if (this.chats.length <= 1 || !ids.length) return
|
|
23
|
+
const currentActiveIndex = this.chats.findIndex(c => c.id === this.activeChatId)
|
|
24
|
+
this.chats = this.chats.filter(c => !ids.includes(c.id))
|
|
25
|
+
if (ids.includes(this.activeChatId)) {
|
|
26
|
+
if (currentActiveIndex === -1) this.activeChatId = this.chats[0]?.id
|
|
27
|
+
this.activeChatId = currentActiveIndex < this.chats.length ? this.chats[currentActiveIndex].id : last(this.chats)?.id ?? ''
|
|
28
|
+
}
|
|
29
|
+
this.runListeners()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get(id: string) {
|
|
33
|
+
return this.chats.find(c => c.id === id)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getActiveChatId() {
|
|
37
|
+
return this.activeChatId
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getAll() {
|
|
41
|
+
return this.chats
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onChange(listener: TabChangeListener) {
|
|
45
|
+
this.listeners.push(listener)
|
|
46
|
+
return () => {
|
|
47
|
+
pull(this.listeners, listener)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
select(id: string) {
|
|
52
|
+
this.activeChatId = id
|
|
53
|
+
this.runListeners()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { pull } from 'lodash'
|
|
2
|
+
|
|
3
|
+
export type ObservableStateListener<T, K extends keyof T> = (value: T[K]) => void
|
|
4
|
+
|
|
5
|
+
type Listeners<T> = {
|
|
6
|
+
[K in keyof T]?: ((value: T[K]) => void)[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ObservableState<T> {
|
|
10
|
+
private state: T
|
|
11
|
+
private listeners: Listeners<T> = {}
|
|
12
|
+
|
|
13
|
+
constructor(initial: T) {
|
|
14
|
+
this.state = { ...initial }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set<K extends keyof T>(key: K, value: T[K]) {
|
|
18
|
+
if (this.state[key] !== value) {
|
|
19
|
+
this.state[key] = value
|
|
20
|
+
this.listeners[key]?.forEach(l => l(value))
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get<K extends keyof T>(key: K): T[K] {
|
|
25
|
+
return this.state[key]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onChange<K extends keyof T>(key: K, listener: (value: T[K]) => void) {
|
|
29
|
+
this.listeners[key] ??= []
|
|
30
|
+
this.listeners[key]!.push(listener)
|
|
31
|
+
return () => {
|
|
32
|
+
pull(this.listeners[key]!, listener)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|