@stack-spot/ai-chat-widget 0.11.0 → 1.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/CHANGELOG.md +9 -0
- package/dist/StackspotAIWidget.d.ts +20 -0
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +7 -3
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/chat-interceptors/CustomInputs.d.ts +4 -1
- package/dist/chat-interceptors/CustomInputs.d.ts.map +1 -1
- package/dist/chat-interceptors/CustomInputs.js +10 -1
- package/dist/chat-interceptors/CustomInputs.js.map +1 -1
- package/dist/chat-interceptors/quick-command-questions.d.ts +10 -0
- package/dist/chat-interceptors/quick-command-questions.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-command-questions.js +12 -2
- package/dist/chat-interceptors/quick-command-questions.js.map +1 -1
- package/dist/chat-interceptors/quick-commands.d.ts +11 -0
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +65 -25
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.d.ts +10 -0
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +32 -10
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/Accordion.d.ts +10 -0
- package/dist/components/Accordion.d.ts.map +1 -1
- package/dist/components/Accordion.js +3 -0
- package/dist/components/Accordion.js.map +1 -1
- package/dist/components/AdaptiveTextArea.d.ts +13 -1
- package/dist/components/AdaptiveTextArea.d.ts.map +1 -1
- package/dist/components/AdaptiveTextArea.js +9 -4
- package/dist/components/AdaptiveTextArea.js.map +1 -1
- package/dist/components/AutoFocus.d.ts +23 -0
- package/dist/components/AutoFocus.d.ts.map +1 -0
- package/dist/components/AutoFocus.js +16 -0
- package/dist/components/AutoFocus.js.map +1 -0
- package/dist/components/Fading.d.ts +32 -0
- package/dist/components/Fading.d.ts.map +1 -0
- package/dist/components/Fading.js +33 -0
- package/dist/components/Fading.js.map +1 -0
- package/dist/components/FadingOverflow.d.ts +25 -0
- package/dist/components/FadingOverflow.d.ts.map +1 -1
- package/dist/components/FadingOverflow.js +11 -2
- package/dist/components/FadingOverflow.js.map +1 -1
- package/dist/components/FallbackBoundary/ErrorBoundary.d.ts +3 -0
- package/dist/components/FallbackBoundary/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/FallbackBoundary/ErrorBoundary.js +18 -4
- package/dist/components/FallbackBoundary/ErrorBoundary.js.map +1 -1
- package/dist/components/FallbackBoundary/Loading.js +1 -1
- package/dist/components/FallbackBoundary/Loading.js.map +1 -1
- package/dist/components/FallbackBoundary/index.d.ts +12 -1
- package/dist/components/FallbackBoundary/index.d.ts.map +1 -1
- package/dist/components/FallbackBoundary/index.js +1 -1
- package/dist/components/FallbackBoundary/index.js.map +1 -1
- package/dist/components/HistoryList.d.ts +15 -0
- package/dist/components/HistoryList.d.ts.map +1 -1
- package/dist/components/HistoryList.js +3 -1
- package/dist/components/HistoryList.js.map +1 -1
- package/dist/components/IconInput.d.ts +3 -0
- package/dist/components/IconInput.d.ts.map +1 -1
- package/dist/components/IconInput.js +3 -0
- package/dist/components/IconInput.js.map +1 -1
- package/dist/components/OverlayMenu.d.ts +12 -1
- package/dist/components/OverlayMenu.d.ts.map +1 -1
- package/dist/components/OverlayMenu.js +31 -10
- package/dist/components/OverlayMenu.js.map +1 -1
- package/dist/components/ProgressBar.d.ts +22 -0
- package/dist/components/ProgressBar.d.ts.map +1 -1
- package/dist/components/ProgressBar.js +5 -0
- package/dist/components/ProgressBar.js.map +1 -1
- package/dist/components/QuickStartButton.d.ts.map +1 -1
- package/dist/components/QuickStartButton.js +3 -0
- package/dist/components/QuickStartButton.js.map +1 -1
- package/dist/components/RightPanelForm.d.ts +3 -0
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelForm.js +8 -4
- package/dist/components/RightPanelForm.js.map +1 -1
- package/dist/components/RightPanelTabs.d.ts +3 -0
- package/dist/components/RightPanelTabs.d.ts.map +1 -1
- package/dist/components/RightPanelTabs.js +3 -0
- package/dist/components/RightPanelTabs.js.map +1 -1
- package/dist/components/TabManager.d.ts +6 -0
- package/dist/components/TabManager.d.ts.map +1 -1
- package/dist/components/TabManager.js +8 -0
- package/dist/components/TabManager.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +26 -1
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +18 -5
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/TooltipAPI.d.ts +18 -2
- package/dist/components/Tooltip/TooltipAPI.d.ts.map +1 -1
- package/dist/components/Tooltip/TooltipAPI.js +68 -51
- package/dist/components/Tooltip/TooltipAPI.js.map +1 -1
- package/dist/components/Tooltip/types.d.ts +13 -0
- package/dist/components/Tooltip/types.d.ts.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.d.ts +4 -0
- package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.js +4 -0
- package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
- package/dist/components/form/DescribedRadioGroup.d.ts +4 -0
- package/dist/components/form/DescribedRadioGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedRadioGroup.js +4 -0
- package/dist/components/form/DescribedRadioGroup.js.map +1 -1
- package/dist/components/form/types.d.ts +34 -0
- package/dist/components/form/types.d.ts.map +1 -1
- package/dist/context/AIWidgetProvider.d.ts +19 -0
- package/dist/context/AIWidgetProvider.d.ts.map +1 -1
- package/dist/context/AIWidgetProvider.js +19 -0
- package/dist/context/AIWidgetProvider.js.map +1 -1
- package/dist/context/hooks.d.ts +56 -0
- package/dist/context/hooks.d.ts.map +1 -1
- package/dist/context/hooks.js +56 -1
- package/dist/context/hooks.js.map +1 -1
- package/dist/features.d.ts +28 -0
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +1 -0
- package/dist/features.js.map +1 -1
- package/dist/layout.css +5 -0
- package/dist/regex.d.ts +2 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +2 -0
- package/dist/regex.js.map +1 -0
- package/dist/right-panel/DefaultPanel.d.ts +3 -0
- package/dist/right-panel/DefaultPanel.d.ts.map +1 -1
- package/dist/right-panel/DefaultPanel.js +3 -0
- package/dist/right-panel/DefaultPanel.js.map +1 -1
- package/dist/right-panel/RightPanel.d.ts +3 -0
- package/dist/right-panel/RightPanel.d.ts.map +1 -1
- package/dist/right-panel/RightPanel.js +3 -0
- package/dist/right-panel/RightPanel.js.map +1 -1
- package/dist/right-panel/RightPanelProvider.d.ts +15 -0
- package/dist/right-panel/RightPanelProvider.d.ts.map +1 -1
- package/dist/right-panel/RightPanelProvider.js.map +1 -1
- package/dist/right-panel/constants.d.ts +2 -0
- package/dist/right-panel/constants.d.ts.map +1 -0
- package/dist/right-panel/constants.js +2 -0
- package/dist/right-panel/constants.js.map +1 -0
- package/dist/right-panel/hooks.d.ts +6 -0
- package/dist/right-panel/hooks.d.ts.map +1 -1
- package/dist/right-panel/hooks.js +8 -1
- package/dist/right-panel/hooks.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +58 -2
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +20 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/ChatState.d.ts +73 -8
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js +24 -7
- package/dist/state/ChatState.js.map +1 -1
- package/dist/state/ChatTabsController.d.ts +31 -0
- package/dist/state/ChatTabsController.d.ts.map +1 -1
- package/dist/state/ChatTabsController.js +31 -0
- package/dist/state/ChatTabsController.js.map +1 -1
- package/dist/state/ObservableState.d.ts +14 -0
- package/dist/state/ObservableState.d.ts.map +1 -1
- package/dist/state/ObservableState.js +14 -0
- package/dist/state/ObservableState.js.map +1 -1
- package/dist/state/WidgetState.d.ts +5 -0
- package/dist/state/WidgetState.d.ts.map +1 -1
- package/dist/state/WidgetState.js +5 -0
- package/dist/state/WidgetState.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/chat.d.ts +13 -0
- package/dist/utils/chat.d.ts.map +1 -1
- package/dist/utils/chat.js +15 -1
- package/dist/utils/chat.js.map +1 -1
- package/dist/utils/date.d.ts +25 -0
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +25 -0
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/download.d.ts +5 -0
- package/dist/utils/download.d.ts.map +1 -1
- package/dist/utils/download.js +5 -0
- package/dist/utils/download.js.map +1 -1
- package/dist/utils/knowledge-source.d.ts +10 -0
- package/dist/utils/knowledge-source.d.ts.map +1 -1
- package/dist/utils/knowledge-source.js +16 -0
- package/dist/utils/knowledge-source.js.map +1 -1
- package/dist/utils/string.d.ts +5 -0
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/string.js +5 -1
- package/dist/utils/string.js.map +1 -1
- package/dist/utils/url.d.ts +2 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +8 -0
- package/dist/utils/url.js.map +1 -0
- package/dist/views/Agents.js +3 -0
- package/dist/views/Agents.js.map +1 -1
- package/dist/views/Chat/AgentInfo.d.ts +3 -0
- package/dist/views/Chat/AgentInfo.d.ts.map +1 -1
- package/dist/views/Chat/AgentInfo.js +3 -0
- package/dist/views/Chat/AgentInfo.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts +17 -2
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +9 -13
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/ChatMessages.d.ts +3 -0
- package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessages.js +3 -0
- package/dist/views/Chat/ChatMessages.js.map +1 -1
- package/dist/views/Chat/chat-scroll.d.ts +1 -1
- package/dist/views/Chat/chat-scroll.js +1 -1
- package/dist/views/Chat/events.d.ts +22 -0
- package/dist/views/Chat/events.d.ts.map +1 -0
- package/dist/views/Chat/events.js +66 -0
- package/dist/views/Chat/events.js.map +1 -0
- package/dist/views/Chat/index.d.ts +6 -0
- package/dist/views/Chat/index.d.ts.map +1 -1
- package/dist/views/Chat/index.js +3 -0
- package/dist/views/Chat/index.js.map +1 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.d.ts +3 -0
- package/dist/views/ChatHistory/ChatHistoryPanel.d.ts.map +1 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.js +5 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.js.map +1 -1
- package/dist/views/ChatHistory/HistoryItem.d.ts +3 -0
- package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
- package/dist/views/ChatHistory/HistoryItem.js +13 -1
- package/dist/views/ChatHistory/HistoryItem.js.map +1 -1
- package/dist/views/ChatHistory/index.d.ts +10 -2
- package/dist/views/ChatHistory/index.d.ts.map +1 -1
- package/dist/views/ChatHistory/index.js +3 -0
- package/dist/views/ChatHistory/index.js.map +1 -1
- package/dist/views/ChatHistory/utils.d.ts +14 -0
- package/dist/views/ChatHistory/utils.d.ts.map +1 -1
- package/dist/views/ChatHistory/utils.js +14 -0
- package/dist/views/ChatHistory/utils.js.map +1 -1
- package/dist/views/ChatTabSelection.d.ts +3 -0
- package/dist/views/ChatTabSelection.d.ts.map +1 -1
- package/dist/views/ChatTabSelection.js +3 -0
- package/dist/views/ChatTabSelection.js.map +1 -1
- package/dist/views/Editor.d.ts +3 -0
- package/dist/views/Editor.d.ts.map +1 -1
- package/dist/views/Editor.js +4 -0
- package/dist/views/Editor.js.map +1 -1
- package/dist/views/Home.d.ts +8 -0
- package/dist/views/Home.d.ts.map +1 -1
- package/dist/views/Home.js +5 -0
- package/dist/views/Home.js.map +1 -1
- package/dist/views/KSDocument.d.ts +3 -0
- package/dist/views/KSDocument.d.ts.map +1 -1
- package/dist/views/KSDocument.js +3 -0
- package/dist/views/KSDocument.js.map +1 -1
- package/dist/views/KnowledgeSources.js +3 -0
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.d.ts +22 -0
- package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.js +4 -0
- package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
- package/dist/views/MessageInput/InfoBar.d.ts +7 -0
- package/dist/views/MessageInput/InfoBar.d.ts.map +1 -1
- package/dist/views/MessageInput/InfoBar.js +7 -0
- package/dist/views/MessageInput/InfoBar.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts +13 -0
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -0
- package/dist/views/MessageInput/QuickCommandSelector.js +141 -0
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -0
- package/dist/views/MessageInput/index.d.ts +8 -0
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +11 -4
- 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 +137 -0
- package/dist/views/MessageInput/styled.js.map +1 -1
- package/dist/views/MinimizedHeader.d.ts +4 -0
- package/dist/views/MinimizedHeader.d.ts.map +1 -1
- package/dist/views/MinimizedHeader.js +4 -0
- package/dist/views/MinimizedHeader.js.map +1 -1
- package/dist/views/Stacks.d.ts +3 -0
- package/dist/views/Stacks.d.ts.map +1 -1
- package/dist/views/Stacks.js +3 -0
- package/dist/views/Stacks.js.map +1 -1
- package/dist/views/Workspaces.d.ts +3 -0
- package/dist/views/Workspaces.d.ts.map +1 -1
- package/dist/views/Workspaces.js +3 -0
- package/dist/views/Workspaces.js.map +1 -1
- package/package.json +6 -6
- package/src/StackspotAIWidget.tsx +23 -2
- package/src/chat-interceptors/CustomInputs.ts +14 -1
- package/src/chat-interceptors/quick-command-questions.ts +12 -2
- package/src/chat-interceptors/quick-commands.ts +66 -26
- package/src/chat-interceptors/send-message.ts +41 -11
- package/src/components/Accordion.tsx +10 -0
- package/src/components/AdaptiveTextArea.tsx +21 -4
- package/src/components/AutoFocus.tsx +34 -0
- package/src/components/Fading.tsx +66 -0
- package/src/components/FadingOverflow.tsx +31 -3
- package/src/components/FallbackBoundary/ErrorBoundary.tsx +26 -3
- package/src/components/FallbackBoundary/Loading.tsx +1 -1
- package/src/components/FallbackBoundary/index.tsx +13 -2
- package/src/components/HistoryList.tsx +15 -1
- package/src/components/IconInput.tsx +3 -0
- package/src/components/OverlayMenu.tsx +76 -20
- package/src/components/ProgressBar.tsx +23 -0
- package/src/components/QuickStartButton.tsx +3 -0
- package/src/components/RightPanelForm.tsx +15 -9
- package/src/components/RightPanelTabs.tsx +3 -0
- package/src/components/TabManager.tsx +8 -0
- package/src/components/Tooltip/Tooltip.tsx +47 -5
- package/src/components/Tooltip/TooltipAPI.ts +59 -42
- package/src/components/Tooltip/types.ts +13 -0
- package/src/components/form/DescribedCheckboxGroup.tsx +4 -0
- package/src/components/form/DescribedRadioGroup.tsx +4 -0
- package/src/components/form/types.ts +34 -0
- package/src/context/AIWidgetProvider.tsx +19 -0
- package/src/context/hooks.ts +56 -1
- package/src/features.ts +29 -0
- package/src/layout.css +5 -0
- package/src/regex.ts +1 -0
- package/src/right-panel/DefaultPanel.tsx +4 -0
- package/src/right-panel/RightPanel.tsx +3 -0
- package/src/right-panel/RightPanelProvider.tsx +15 -0
- package/src/right-panel/constants.ts +1 -0
- package/src/right-panel/hooks.tsx +8 -1
- package/src/state/ChatEntry.ts +60 -2
- package/src/state/ChatState.ts +74 -9
- package/src/state/ChatTabsController.ts +31 -0
- package/src/state/ObservableState.ts +14 -0
- package/src/state/WidgetState.ts +5 -0
- package/src/types.ts +10 -0
- package/src/utils/chat.ts +15 -1
- package/src/utils/date.ts +25 -1
- package/src/utils/download.ts +5 -0
- package/src/utils/knowledge-source.ts +16 -0
- package/src/utils/string.ts +5 -1
- package/src/utils/url.ts +8 -0
- package/src/views/Agents.tsx +3 -0
- package/src/views/Chat/AgentInfo.tsx +3 -0
- package/src/views/Chat/ChatMessage.tsx +31 -18
- package/src/views/Chat/ChatMessages.tsx +3 -0
- package/src/views/Chat/chat-scroll.ts +1 -1
- package/src/views/Chat/events.ts +69 -0
- package/src/views/Chat/index.tsx +6 -0
- package/src/views/ChatHistory/ChatHistoryPanel.tsx +6 -2
- package/src/views/ChatHistory/HistoryItem.tsx +14 -2
- package/src/views/ChatHistory/index.tsx +11 -1
- package/src/views/ChatHistory/utils.ts +14 -0
- package/src/views/ChatTabSelection.tsx +3 -0
- package/src/views/Editor.tsx +4 -0
- package/src/views/Home.tsx +8 -0
- package/src/views/KSDocument.tsx +3 -0
- package/src/views/KnowledgeSources.tsx +3 -0
- package/src/views/MessageInput/ButtonGroup.tsx +22 -0
- package/src/views/MessageInput/InfoBar.tsx +7 -0
- package/src/views/MessageInput/QuickCommandSelector.tsx +217 -0
- package/src/views/MessageInput/index.tsx +16 -4
- package/src/views/MessageInput/styled.ts +137 -0
- package/src/views/MinimizedHeader.tsx +4 -0
- package/src/views/Stacks.tsx +3 -0
- package/src/views/Workspaces.tsx +3 -0
|
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Scrolls the closest chat (upwards in the tree) to its bottom.
|
|
5
|
-
* @param ref the reference element.
|
|
5
|
+
* @param ref the reference to the element contained the chat we want to scroll.
|
|
6
6
|
* @param deps when the deps changes, the chat is scrolled.
|
|
7
7
|
*/
|
|
8
8
|
export function useChatScrollToBottomEffect(ref: React.RefObject<HTMLElement>, deps: any[]) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { aiClient } from '@stack-spot/portal-network'
|
|
2
|
+
import { TextChatEntry } from '../../state/ChatEntry'
|
|
3
|
+
import { ChatState } from '../../state/ChatState'
|
|
4
|
+
import { buildConversationContext } from '../../utils/chat'
|
|
5
|
+
import { getSizeOfString } from '../../utils/string'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates the event of copying a code.
|
|
9
|
+
* @param code the code copied.
|
|
10
|
+
* @param messageId the id of the message containing the code.
|
|
11
|
+
* @param chat the chat state.
|
|
12
|
+
*/
|
|
13
|
+
export async function onCopyCode(code: string, messageId: string, chat: ChatState) {
|
|
14
|
+
try {
|
|
15
|
+
await aiClient.createEvent.mutate({
|
|
16
|
+
body: [{
|
|
17
|
+
type: 'code_copied',
|
|
18
|
+
code,
|
|
19
|
+
context: buildConversationContext(chat),
|
|
20
|
+
size: getSizeOfString(code),
|
|
21
|
+
generated_at: new Date().getTime(),
|
|
22
|
+
message_id: messageId,
|
|
23
|
+
}],
|
|
24
|
+
})
|
|
25
|
+
} catch (error) {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.warn('Failed to register event: code copied.')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates the event of copying a whole message.
|
|
33
|
+
* @param entry the message copied.
|
|
34
|
+
* @param chat the chat state.
|
|
35
|
+
*/
|
|
36
|
+
export function onCopyAll(entry: TextChatEntry, chat: ChatState) {
|
|
37
|
+
navigator.clipboard.writeText(entry.content)
|
|
38
|
+
return aiClient.createEvent.mutate({
|
|
39
|
+
body: [
|
|
40
|
+
{
|
|
41
|
+
code: entry.content,
|
|
42
|
+
size: getSizeOfString(entry.content),
|
|
43
|
+
generated_at: new Date().getTime(),
|
|
44
|
+
type: 'copied_all',
|
|
45
|
+
knowledge_sources: entry.knowledgeSources?.map(ks => ({ slug: ks.slug, type: 'knowledge_source', name: ks.name })),
|
|
46
|
+
context: buildConversationContext(chat),
|
|
47
|
+
message_id: entry.messageId,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates an event of like or dislike for a message.
|
|
55
|
+
* @param messageId the id of the message.
|
|
56
|
+
* @param like true if it's a like, false if it's a dislike.
|
|
57
|
+
*/
|
|
58
|
+
export function onLikeOrDislike(messageId: string, like: boolean) {
|
|
59
|
+
return aiClient.createEvent.mutate({
|
|
60
|
+
body: [{
|
|
61
|
+
feedback: like ? 'LIKE' : 'DISLIKE',
|
|
62
|
+
message_id: messageId,
|
|
63
|
+
type: 'user_feedback_provided',
|
|
64
|
+
code: '',
|
|
65
|
+
generated_at: Math.floor(new Date().getTime() / 1000),
|
|
66
|
+
size: 0,
|
|
67
|
+
}],
|
|
68
|
+
})
|
|
69
|
+
}
|
package/src/views/Chat/index.tsx
CHANGED
|
@@ -2,9 +2,15 @@ import { useChatTabs } from '../../context/hooks'
|
|
|
2
2
|
import { ChatMessages } from './ChatMessages'
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the user currently logged in.
|
|
7
|
+
*/
|
|
5
8
|
username: string,
|
|
6
9
|
}
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Renders the chat panel, with all of its messages.
|
|
13
|
+
*/
|
|
8
14
|
export const Chat = ({ username }: Props) => {
|
|
9
15
|
const { active } = useChatTabs()
|
|
10
16
|
return <ChatMessages key={active} chatId={active} username={username} />
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { aiClient } from '@stack-spot/portal-network'
|
|
2
2
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
|
3
|
+
import { AutoFocus } from '../../components/AutoFocus'
|
|
3
4
|
import { HistoryList } from '../../components/HistoryList'
|
|
4
5
|
import { MessageInterceptor } from '../../state/ChatState'
|
|
5
6
|
import { HistoryItem } from './HistoryItem'
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Renders the list of conversations (history).
|
|
10
|
+
*/
|
|
7
11
|
export const ChatHistoryPanel = ({ interceptors }: { interceptors: MessageInterceptor[] }) => {
|
|
8
12
|
const [chats, { fetchNextPage, hasNextPage }] = aiClient.chats.useInfiniteQuery({ size: 40 })
|
|
9
13
|
return (
|
|
10
|
-
<
|
|
14
|
+
<AutoFocus id="chatHistoryList" style={{ height: '100%', overflow: 'auto' }}>
|
|
11
15
|
<InfiniteScroll
|
|
12
16
|
scrollableTarget="chatHistoryList"
|
|
13
17
|
dataLength={chats.length}
|
|
@@ -23,6 +27,6 @@ export const ChatHistoryPanel = ({ interceptors }: { interceptors: MessageInterc
|
|
|
23
27
|
style={{ marginRight: '6px' }}
|
|
24
28
|
/>
|
|
25
29
|
</InfiniteScroll>
|
|
26
|
-
</
|
|
30
|
+
</AutoFocus>
|
|
27
31
|
)
|
|
28
32
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IconBox, Input } from '@citric/core'
|
|
2
2
|
import { Check, Download, EllipsisHorizontal, Pencil, Trash } from '@citric/icons'
|
|
3
3
|
import { IconButton, LoadingCircular } from '@citric/ui'
|
|
4
|
+
import { focusNextIgnoringChildren } from '@stack-spot/portal-components'
|
|
4
5
|
import { aiClient } from '@stack-spot/portal-network'
|
|
5
6
|
import { ConversationResponse } from '@stack-spot/portal-network/api/ai'
|
|
6
7
|
import { theme } from '@stack-spot/portal-theme'
|
|
@@ -17,6 +18,9 @@ import { useHistoryDictionary } from './dictionary'
|
|
|
17
18
|
import { HistoryItemBox } from './styled'
|
|
18
19
|
import { findStack, findWorkspace, getAllAgents } from './utils'
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Renders an item of the list of conversations (history).
|
|
23
|
+
*/
|
|
20
24
|
export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse, interceptors: MessageInterceptor[] }) => {
|
|
21
25
|
const t = useHistoryDictionary()
|
|
22
26
|
const [isLoading, setLoading] = useState(false)
|
|
@@ -26,6 +30,7 @@ export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse
|
|
|
26
30
|
const [isDeleted, setDeleted] = useState(false)
|
|
27
31
|
const renameInput = useRef<HTMLInputElement>(null)
|
|
28
32
|
const widget = useWidget()
|
|
33
|
+
const overlayRef = useRef<HTMLDivElement>(null)
|
|
29
34
|
|
|
30
35
|
useEffect(() => {
|
|
31
36
|
if (isRenaming) renameInput.current?.focus()
|
|
@@ -52,6 +57,7 @@ export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
const onDownload = useCallback(async () => {
|
|
60
|
+
setTimeout(() => focusNextIgnoringChildren(overlayRef.current), 10)
|
|
55
61
|
setLoading(true)
|
|
56
62
|
try {
|
|
57
63
|
const content = await aiClient.downloadChat.mutate({ conversationId: item.id })
|
|
@@ -65,6 +71,7 @@ export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse
|
|
|
65
71
|
|
|
66
72
|
const onDelete = useCallback(async () => {
|
|
67
73
|
setDeleted(true)
|
|
74
|
+
setTimeout(() => overlayRef.current?.focus(), 10)
|
|
68
75
|
try {
|
|
69
76
|
await aiClient.deleteChat.mutate({ conversationId: item.id })
|
|
70
77
|
aiClient.chats.invalidate()
|
|
@@ -124,14 +131,19 @@ export const HistoryItem = ({ item, interceptors }: { item: ConversationResponse
|
|
|
124
131
|
ref={renameInput}
|
|
125
132
|
value={renamed}
|
|
126
133
|
onChange={e => setRenamed(e.target.value)}
|
|
127
|
-
onKeyDown={e =>
|
|
134
|
+
onKeyDown={(e) => {
|
|
135
|
+
if (['Enter', 'Escape'].includes(e.key)) {
|
|
136
|
+
e.key === 'Enter' ? onSubmitRename() : setRenaming(false)
|
|
137
|
+
setTimeout(() => overlayRef.current?.focus(), 10)
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
128
140
|
/>
|
|
129
141
|
<IconButton onClick={onSubmitRename}><Check /></IconButton>
|
|
130
142
|
</>
|
|
131
143
|
) : (
|
|
132
144
|
<>
|
|
133
145
|
<button className="label" onClick={onSelect} disabled={isLoading}>{title}</button>
|
|
134
|
-
{isLoading ? <LoadingCircular size="xs" /> : <OverlayMenu actions={actions} position="left">
|
|
146
|
+
{isLoading ? <LoadingCircular size="xs" /> : <OverlayMenu actions={actions} position="left" ref={overlayRef}>
|
|
135
147
|
<IconBox><EllipsisHorizontal /></IconBox>
|
|
136
148
|
</OverlayMenu>}
|
|
137
149
|
</>
|
|
@@ -6,7 +6,17 @@ import { MessageInterceptor } from '../../state/ChatState'
|
|
|
6
6
|
import { ChatHistoryPanel } from './ChatHistoryPanel'
|
|
7
7
|
import { useHistoryDictionary } from './dictionary'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface Props {
|
|
10
|
+
/**
|
|
11
|
+
* The chat interceptors to use when recreating a ChatState from the history.
|
|
12
|
+
*/
|
|
13
|
+
interceptors: MessageInterceptor[],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders the Chat History in the Right Panel if this is the panel that is currently opened.
|
|
18
|
+
*/
|
|
19
|
+
export const ChatHistory = ({ interceptors }: Props) => {
|
|
10
20
|
const t = useHistoryDictionary()
|
|
11
21
|
const panel = useWidgetState('panel')
|
|
12
22
|
const { open } = useRightPanel()
|
|
@@ -2,6 +2,11 @@ import { agentClient, aiClient, workspaceClient } from '@stack-spot/portal-netwo
|
|
|
2
2
|
import { ChatProperties } from '../../state/ChatState'
|
|
3
3
|
import { LabeledWithImage } from '../../state/types'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Finds a stack by its id.
|
|
7
|
+
* @param id the id of the stack.
|
|
8
|
+
* @returns the stack or undefined.
|
|
9
|
+
*/
|
|
5
10
|
export async function findStack(id: string | null): Promise<ChatProperties['stack'] | undefined> {
|
|
6
11
|
if (!id) return
|
|
7
12
|
try {
|
|
@@ -14,6 +19,11 @@ export async function findStack(id: string | null): Promise<ChatProperties['stac
|
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Finds a workspace by its id.
|
|
24
|
+
* @param id the id of the workspace.
|
|
25
|
+
* @returns the workspace or undefined.
|
|
26
|
+
*/
|
|
17
27
|
export async function findWorkspace(id: string | null): Promise<ChatProperties['workspace'] | undefined> {
|
|
18
28
|
if (!id) return
|
|
19
29
|
try {
|
|
@@ -26,6 +36,10 @@ export async function findWorkspace(id: string | null): Promise<ChatProperties['
|
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Finds all the agents, including common agents and public agents.
|
|
41
|
+
* @returns an array with every agent.
|
|
42
|
+
*/
|
|
29
43
|
export async function getAllAgents(): Promise<LabeledWithImage[]> {
|
|
30
44
|
try {
|
|
31
45
|
const [agents, publicAgents] = await Promise.all([agentClient.agents.query({}), agentClient.publicAgents.query({})])
|
|
@@ -17,6 +17,9 @@ const TabLabel = ({ id }: { id: string }) => {
|
|
|
17
17
|
return <div title={label}>{label}</div>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* This renders the top-most part of the layout, which includes the chat selection through tabs.
|
|
22
|
+
*/
|
|
20
23
|
export const ChatTabSelection = ({ history, interceptors }: Props) => {
|
|
21
24
|
const t = useTranslate(dictionary)
|
|
22
25
|
const widget = useWidget()
|
package/src/views/Editor.tsx
CHANGED
|
@@ -42,6 +42,9 @@ const TitleBox = styled.div`
|
|
|
42
42
|
}
|
|
43
43
|
`
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Renders the Editor in the Right Panel if this is the panel that is currently opened.
|
|
47
|
+
*/
|
|
45
48
|
export const Editor = () => {
|
|
46
49
|
const t = useTranslate(dictionary)
|
|
47
50
|
const panel = useWidgetState('panel')
|
|
@@ -94,6 +97,7 @@ const EditorPanel = () => {
|
|
|
94
97
|
const selectedText = editor.getModel()?.getValueInRange(e.selection)
|
|
95
98
|
chat.set('codeSelection', selectedText?.trim() ? selectedText : undefined)
|
|
96
99
|
}, MIN_SELECTION_UPDATE_MS))
|
|
100
|
+
editor.focus()
|
|
97
101
|
}, [])
|
|
98
102
|
|
|
99
103
|
useEffect(() => () => {
|
package/src/views/Home.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import { useCurrentChat } from '../context/hooks'
|
|
|
8
8
|
import { ChatEntry } from '../state/ChatEntry'
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
|
+
/**
|
|
12
|
+
* The name of the user currently logged in.
|
|
13
|
+
*/
|
|
11
14
|
username: string,
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -44,6 +47,11 @@ const HomeBox = styled.div`
|
|
|
44
47
|
}
|
|
45
48
|
`
|
|
46
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Renders the default home page for the chat. This shows up when no message has been sent yet.
|
|
52
|
+
*
|
|
53
|
+
* The home page can be replaced by providing children to the component `StackspotAIWidget`.
|
|
54
|
+
*/
|
|
47
55
|
export const Home = ({ username }: Props) => {
|
|
48
56
|
const t = useTranslate(dictionary)
|
|
49
57
|
const chat = useCurrentChat()
|
package/src/views/KSDocument.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import { useWidget, useWidgetState } from '../context/hooks'
|
|
|
8
8
|
import { useRightPanel } from '../right-panel/hooks'
|
|
9
9
|
import { extractCodeFromKSDocument } from '../utils/knowledge-source'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Renders the KS document in the Right Panel if this is the panel that is currently opened.
|
|
13
|
+
*/
|
|
11
14
|
export const KSDocument = () => {
|
|
12
15
|
const t = useTranslate(dictionary)
|
|
13
16
|
const panel = useWidgetState('panel')
|
|
@@ -35,6 +35,9 @@ export const KnowledgeSources = () => {
|
|
|
35
35
|
return null
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Renders the KS selection form in the Right Panel if this is the panel that is currently opened.
|
|
40
|
+
*/
|
|
38
41
|
const KnowledgeSourcesPanel = () => {
|
|
39
42
|
const t = useTranslate(dictionary)
|
|
40
43
|
const chat = useCurrentChat()
|
|
@@ -8,14 +8,36 @@ import { MessageInputFeatures } from '../../features'
|
|
|
8
8
|
import { useMessageInputDictionary } from './dictionary'
|
|
9
9
|
|
|
10
10
|
interface ButtonGroupProps {
|
|
11
|
+
/**
|
|
12
|
+
* The features enabled and accessible through the message input.
|
|
13
|
+
*/
|
|
11
14
|
features: MessageInputFeatures,
|
|
15
|
+
/**
|
|
16
|
+
* Whether or not the button group is expanded.
|
|
17
|
+
*/
|
|
12
18
|
expanded: boolean,
|
|
19
|
+
/**
|
|
20
|
+
* A function to set the button group as expanded or collapsed.
|
|
21
|
+
*/
|
|
13
22
|
setExpanded: React.Dispatch<React.SetStateAction<boolean>>,
|
|
23
|
+
/**
|
|
24
|
+
* Whether or not the message is currently being sent. This is used to decide which button to show: send or cancel.
|
|
25
|
+
*/
|
|
14
26
|
isLoading: boolean,
|
|
27
|
+
/**
|
|
28
|
+
* A function to run when the send button is clicked.
|
|
29
|
+
*/
|
|
15
30
|
onSend: () => void,
|
|
31
|
+
/**
|
|
32
|
+
* A function to run when the cancel button is clicked.
|
|
33
|
+
*/
|
|
16
34
|
onCancel: () => void,
|
|
17
35
|
}
|
|
18
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Renders the button group at right bottom side of the message input. This includes the send button as well as the buttons to open the
|
|
39
|
+
* editor, change the agent, the stack, etc.
|
|
40
|
+
*/
|
|
19
41
|
export const ButtonGroup = ({ features, onSend, onCancel, expanded, setExpanded, isLoading }: ButtonGroupProps) => {
|
|
20
42
|
const t = useMessageInputDictionary()
|
|
21
43
|
const widget = useWidget()
|
|
@@ -21,6 +21,13 @@ const InfoBadge = ({ label, color, dismiss, onDismiss }: InfoBadgeProps) => (
|
|
|
21
21
|
</Badge>
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* This renders the bar on top of the textarea to send the message. This tells:
|
|
26
|
+
* - if there's any code selected in the editor;
|
|
27
|
+
* - which workspace is being used;
|
|
28
|
+
* - which stack is being used;
|
|
29
|
+
* - which knowledge sources are being used.
|
|
30
|
+
*/
|
|
24
31
|
export const InfoBar = () => {
|
|
25
32
|
const t = useMessageInputDictionary()
|
|
26
33
|
const chat = useCurrentChat()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { IconBox, Text } from '@citric/core'
|
|
2
|
+
import { ExternalLink, QuickCommand } from '@citric/icons'
|
|
3
|
+
import { IconButton } from '@citric/ui'
|
|
4
|
+
import { useKeyboardControls } from '@stack-spot/portal-components'
|
|
5
|
+
import { aiClient } from '@stack-spot/portal-network'
|
|
6
|
+
import { QuickCommandListResponse, VisibilityLevelEnum } from '@stack-spot/portal-network/api/ai'
|
|
7
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
8
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
9
|
+
import { Fading } from '../../components/Fading'
|
|
10
|
+
import { FallbackBoundary } from '../../components/FallbackBoundary'
|
|
11
|
+
import { useCurrentChat, useCurrentChatState } from '../../context/hooks'
|
|
12
|
+
import { quickCommandRegex } from '../../regex'
|
|
13
|
+
import { getUrlToStackSpotAI } from '../../utils/url'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/**
|
|
17
|
+
* A reference to the input this quick commands panel is attached to.
|
|
18
|
+
*/
|
|
19
|
+
inputRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ContentProps extends Props {
|
|
23
|
+
filter?: string,
|
|
24
|
+
onClose: () => void,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ListProps {
|
|
28
|
+
filter?: string,
|
|
29
|
+
visibility?: VisibilityLevelEnum,
|
|
30
|
+
onSelect: (slug: string) => void,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ItemProps {
|
|
34
|
+
qc: QuickCommandListResponse,
|
|
35
|
+
onSelect: (slug: string) => void,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sections = [undefined, 'personal', 'workspace', 'account', 'shared'] as const
|
|
39
|
+
|
|
40
|
+
const CommandListItem = ({ qc, onSelect }: ItemProps) => {
|
|
41
|
+
const t = useTranslate(dictionary)
|
|
42
|
+
return (
|
|
43
|
+
<li>
|
|
44
|
+
<button
|
|
45
|
+
className="qc"
|
|
46
|
+
onClick={() => onSelect(qc.slug)}
|
|
47
|
+
// the following line prevents a new line character in the message when the user presses enter to select a qc.
|
|
48
|
+
onKeyDown={e => e.key === 'Enter' && e.preventDefault()}
|
|
49
|
+
onFocus={e => e.target.closest('li')?.classList.add('focus')}
|
|
50
|
+
onBlur={e => e.target.closest('li')?.classList.remove('focus')}
|
|
51
|
+
>
|
|
52
|
+
<p className="qc-title">/{qc.slug}</p>
|
|
53
|
+
<p className="qc-description">{qc.description}</p>
|
|
54
|
+
</button>
|
|
55
|
+
<IconButton as="a" title={t.openQC} aria-label={t.openQC} href={`${getUrlToStackSpotAI()}/quick-command/${qc.slug}`} target="_blank">
|
|
56
|
+
<ExternalLink />
|
|
57
|
+
</IconButton>
|
|
58
|
+
</li>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CommandList = ({ filter, visibility, onSelect }: ListProps) => {
|
|
63
|
+
const t = useTranslate(dictionary)
|
|
64
|
+
const quickCommands = aiClient.quickCommands.useQuery({ order: 'a-to-z' })
|
|
65
|
+
let filtered = quickCommands
|
|
66
|
+
|
|
67
|
+
if (visibility || filter) {
|
|
68
|
+
const lowerFilter = filter?.toLocaleLowerCase()
|
|
69
|
+
filtered = quickCommands.filter(
|
|
70
|
+
qc => (!lowerFilter || qc.slug.toLocaleLowerCase().startsWith(lowerFilter)) && (!visibility || qc.visibility_level === visibility),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
if (!quickCommands.length) return <Text className="empty" colorScheme="light.700">{t.noData}</Text>
|
|
74
|
+
if (!filtered.length) return <Text className="empty" colorScheme="light.700">{t.noResults}</Text>
|
|
75
|
+
return (
|
|
76
|
+
<ul className="command-list">
|
|
77
|
+
{filtered.map(qc => <CommandListItem key={qc.id} qc={qc} onSelect={onSelect} />)}
|
|
78
|
+
</ul>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SelectorContent = ({ filter, onClose, inputRef }: ContentProps) => {
|
|
83
|
+
const t = useTranslate(dictionary)
|
|
84
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
85
|
+
const chat = useCurrentChat()
|
|
86
|
+
const [visibility, setVisibility] = useState<VisibilityLevelEnum | undefined>()
|
|
87
|
+
|
|
88
|
+
const onSelectQC = useCallback((slug: string) => {
|
|
89
|
+
const newValue = `/${slug}`
|
|
90
|
+
chat.set('nextMessage', newValue)
|
|
91
|
+
onClose()
|
|
92
|
+
if (!inputRef.current) return
|
|
93
|
+
// the following line prevents bugs by setting the text area value before react gets the chance to.
|
|
94
|
+
inputRef.current.value = newValue
|
|
95
|
+
inputRef.current.focus()
|
|
96
|
+
}, [])
|
|
97
|
+
|
|
98
|
+
useKeyboardControls({
|
|
99
|
+
querySelectors: '.tabs button, button.qc',
|
|
100
|
+
disableTabBehavior: true,
|
|
101
|
+
onPressEscape: onClose,
|
|
102
|
+
onPressArrowLeft: () => (ref.current?.querySelector('.tabs button.active') as HTMLElement)?.focus(),
|
|
103
|
+
onPressArrowRight: () => (ref.current?.querySelector('button.qc') as HTMLElement)?.focus(),
|
|
104
|
+
ref,
|
|
105
|
+
}, [])
|
|
106
|
+
|
|
107
|
+
function createSectionItem(action: VisibilityLevelEnum | undefined) {
|
|
108
|
+
return (
|
|
109
|
+
<li key={action ?? 'all'}>
|
|
110
|
+
<button className={visibility === action ? 'active' : ''} onFocus={() => setVisibility(action)}>
|
|
111
|
+
{t[action || 'all']}
|
|
112
|
+
</button>
|
|
113
|
+
</li>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div ref={ref}>
|
|
119
|
+
<header>
|
|
120
|
+
<IconBox><QuickCommand /></IconBox>
|
|
121
|
+
<Text as="h3">QUICK COMMANDS</Text>
|
|
122
|
+
</header>
|
|
123
|
+
<div className="body">
|
|
124
|
+
<ul className="tabs">{sections.map(createSectionItem)}</ul>
|
|
125
|
+
<FallbackBoundary message={t.error} mini>
|
|
126
|
+
<CommandList onSelect={onSelectQC} filter={filter} visibility={visibility} />
|
|
127
|
+
</FallbackBoundary>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* This renders the floating Quick Commands panel that allows the user to select a quick command. This appears whenever the user types "/"
|
|
135
|
+
* in the textarea.
|
|
136
|
+
*/
|
|
137
|
+
export const QuickCommandSelector = ({ inputRef }: Props) => {
|
|
138
|
+
const value = useCurrentChatState('nextMessage') ?? ''
|
|
139
|
+
const filter = useMemo(() => value === '/' || quickCommandRegex.test(value) ? value.substring(1) : undefined, [value])
|
|
140
|
+
const [isClosed, setClosed] = useState(false)
|
|
141
|
+
const selectorRef = useRef<HTMLDivElement>(null)
|
|
142
|
+
const shouldRender = filter !== undefined && !isClosed
|
|
143
|
+
|
|
144
|
+
// Resets the closed state whenever the message input is cleared
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!value) setClosed(false)
|
|
147
|
+
}, [value])
|
|
148
|
+
|
|
149
|
+
// Creates the following behavior while the user types in the message input:
|
|
150
|
+
// auto-complete on tab; move focus to the qc panel on press up or down; and close the qc panel on esc.
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
function getFirst() {
|
|
153
|
+
return selectorRef.current?.querySelector('.qc') as HTMLElement | null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function onKeyDown(event: Event) {
|
|
157
|
+
const key = (event as KeyboardEvent).key
|
|
158
|
+
if (!selectorRef.current) return
|
|
159
|
+
if (key === 'Tab') {
|
|
160
|
+
getFirst()?.click()
|
|
161
|
+
event.preventDefault()
|
|
162
|
+
}
|
|
163
|
+
else if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
164
|
+
getFirst()?.focus()
|
|
165
|
+
event.preventDefault()
|
|
166
|
+
}
|
|
167
|
+
if (key === 'Escape') {
|
|
168
|
+
setClosed(true)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
inputRef.current?.addEventListener('keydown', onKeyDown)
|
|
173
|
+
return () => inputRef.current?.removeEventListener('keydown', onKeyDown)
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
// Closes the panel when the user clicks outside the qc panel or the message input.
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!shouldRender) return
|
|
179
|
+
function onClickOut(e: Event) {
|
|
180
|
+
const target = e.target as HTMLElement | null
|
|
181
|
+
if (!selectorRef.current?.contains(target) && !inputRef.current?.contains(target)) setClosed(true)
|
|
182
|
+
}
|
|
183
|
+
document.addEventListener('click', onClickOut)
|
|
184
|
+
return () => document.removeEventListener('click', onClickOut)
|
|
185
|
+
}, [shouldRender])
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<Fading visible={shouldRender} ref={selectorRef} className="quick-command-selector">
|
|
189
|
+
<SelectorContent filter={filter} onClose={() => setClosed(true)} inputRef={inputRef} />
|
|
190
|
+
</Fading>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const dictionary = {
|
|
195
|
+
en: {
|
|
196
|
+
all: 'All',
|
|
197
|
+
personal: 'Personal',
|
|
198
|
+
account: 'Account',
|
|
199
|
+
shared: 'Shared',
|
|
200
|
+
workspace: 'Workspace',
|
|
201
|
+
error: 'Could not load the quick commands.',
|
|
202
|
+
noData: 'You don\'t have any quick command yet.',
|
|
203
|
+
noResults: 'There are no quick commands to show here.',
|
|
204
|
+
openQC: 'Open this quick command\'s settings in a new tab.',
|
|
205
|
+
},
|
|
206
|
+
pt: {
|
|
207
|
+
all: 'Todos',
|
|
208
|
+
personal: 'Pessoal',
|
|
209
|
+
account: 'Conta',
|
|
210
|
+
shared: 'Compartilhado',
|
|
211
|
+
workspace: 'Workspace',
|
|
212
|
+
error: 'Não foi possível carregar os quick commands.',
|
|
213
|
+
noData: 'Você ainda não possui quick commands.',
|
|
214
|
+
noResults: 'Não há quick commands para mostrar aqui.',
|
|
215
|
+
openQC: 'Abra as configurações deste quick command em uma nova aba.',
|
|
216
|
+
},
|
|
217
|
+
} satisfies Dictionary
|
|
@@ -4,16 +4,26 @@ import { AdaptiveTextArea } from '../../components/AdaptiveTextArea'
|
|
|
4
4
|
import { ProgressBar } from '../../components/ProgressBar'
|
|
5
5
|
import { useCurrentChat, useCurrentChatState, useWidgetState } from '../../context/hooks'
|
|
6
6
|
import { MessageInputFeatures } from '../../features'
|
|
7
|
+
import { quickCommandRegex } from '../../regex'
|
|
7
8
|
import { ChatEntry } from '../../state/ChatEntry'
|
|
8
9
|
import { ButtonGroup } from './ButtonGroup'
|
|
9
10
|
import { useMessageInputDictionary } from './dictionary'
|
|
10
11
|
import { InfoBar } from './InfoBar'
|
|
12
|
+
import { QuickCommandSelector } from './QuickCommandSelector'
|
|
11
13
|
import { MAX_INPUT_HEIGHT, MessageInputBox, MIN_INPUT_HEIGHT } from './styled'
|
|
12
14
|
|
|
13
15
|
interface Props {
|
|
16
|
+
/**
|
|
17
|
+
* The features enabled and accessible through the message input.
|
|
18
|
+
*/
|
|
14
19
|
features: MessageInputFeatures,
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* This renders the MessageInput part of the layout which includes the progress bar, the actual textarea, the badges telling what is
|
|
24
|
+
* going to be used for the question and the buttons to send, cancel, set the workspace, among others. This also includes the Quick
|
|
25
|
+
* Commands panel for auto completing.
|
|
26
|
+
*/
|
|
17
27
|
export const MessageInput = ({ features }: Props) => {
|
|
18
28
|
const t = useMessageInputDictionary()
|
|
19
29
|
const [focused, setFocused] = useState(false)
|
|
@@ -23,14 +33,14 @@ export const MessageInput = ({ features }: Props) => {
|
|
|
23
33
|
const isLoading = useCurrentChatState('isLoading') ?? false
|
|
24
34
|
const value = useCurrentChatState('nextMessage') ?? ''
|
|
25
35
|
const isMinimized = useWidgetState('isMinimized')
|
|
26
|
-
const
|
|
36
|
+
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
|
27
37
|
|
|
28
38
|
const onSend = useCallback(async () => {
|
|
29
39
|
const message = chat.get('nextMessage')
|
|
30
40
|
if (!message) return
|
|
31
41
|
const code = chat.get('codeSelection')
|
|
32
42
|
const language = chat.get('codeLanguage')
|
|
33
|
-
const prompt = code && !
|
|
43
|
+
const prompt = code && !quickCommandRegex.test(message) ? `${message}\n\`\`\`${language}\n${code}\n\`\`\`` : message
|
|
34
44
|
chat.pushMessage(ChatEntry.createUserEntry(prompt, true))
|
|
35
45
|
chat.set('nextMessage', '')
|
|
36
46
|
setFocused(false)
|
|
@@ -44,15 +54,17 @@ export const MessageInput = ({ features }: Props) => {
|
|
|
44
54
|
}, [onSend])
|
|
45
55
|
|
|
46
56
|
useEffect(() => {
|
|
47
|
-
if (!isLoading)
|
|
57
|
+
if (!isLoading) textAreaRef.current?.focus()
|
|
48
58
|
}, [isLoading])
|
|
49
59
|
|
|
50
60
|
return (
|
|
51
|
-
<MessageInputBox
|
|
61
|
+
<MessageInputBox aria-busy={isLoading} className="message-input">
|
|
52
62
|
<ProgressBar visible={isLoading} shimmer />
|
|
53
63
|
<InfoBar />
|
|
54
64
|
<div className={listToClass(['action-box', focused && 'focused', isLoading && 'disabled'])}>
|
|
65
|
+
<QuickCommandSelector inputRef={textAreaRef} />
|
|
55
66
|
<AdaptiveTextArea
|
|
67
|
+
ref={textAreaRef}
|
|
56
68
|
disabled={isLoading}
|
|
57
69
|
placeholder={t.placeholder}
|
|
58
70
|
onChange={e => chat.set('nextMessage', e.target.value)}
|