@stack-spot/ai-chat-widget 0.10.0 → 1.0.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/AbortedError.d.ts +5 -0
- package/dist/AbortedError.d.ts.map +1 -0
- package/dist/AbortedError.js +7 -0
- package/dist/AbortedError.js.map +1 -0
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +11 -3
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/chat-interceptors/CustomInputs.d.ts +19 -0
- package/dist/chat-interceptors/CustomInputs.d.ts.map +1 -0
- package/dist/chat-interceptors/CustomInputs.js +62 -0
- package/dist/chat-interceptors/CustomInputs.js.map +1 -0
- package/dist/chat-interceptors/quick-command-questions.d.ts +4 -0
- package/dist/chat-interceptors/quick-command-questions.d.ts.map +1 -0
- package/dist/chat-interceptors/quick-command-questions.js +18 -0
- package/dist/chat-interceptors/quick-command-questions.js.map +1 -0
- package/dist/chat-interceptors/quick-commands.d.ts +3 -1
- package/dist/chat-interceptors/quick-commands.d.ts.map +1 -1
- package/dist/chat-interceptors/quick-commands.js +250 -8
- package/dist/chat-interceptors/quick-commands.js.map +1 -1
- package/dist/chat-interceptors/send-message.d.ts +1 -1
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +18 -14
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/AdaptiveTextArea.d.ts +1 -1
- package/dist/components/AdaptiveTextArea.d.ts.map +1 -1
- package/dist/components/AdaptiveTextArea.js +6 -4
- package/dist/components/AdaptiveTextArea.js.map +1 -1
- package/dist/components/AutoFocus.d.ts +6 -0
- package/dist/components/AutoFocus.d.ts.map +1 -0
- package/dist/components/AutoFocus.js +15 -0
- package/dist/components/AutoFocus.js.map +1 -0
- package/dist/components/Fading.d.ts +15 -0
- package/dist/components/Fading.d.ts.map +1 -0
- package/dist/components/Fading.js +31 -0
- package/dist/components/Fading.js.map +1 -0
- 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 +6 -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/OverlayMenu.d.ts +1 -1
- package/dist/components/OverlayMenu.d.ts.map +1 -1
- package/dist/components/OverlayMenu.js +26 -9
- package/dist/components/OverlayMenu.js.map +1 -1
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelForm.js +5 -4
- package/dist/components/RightPanelForm.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +3 -1
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +14 -5
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/TooltipAPI.d.ts +2 -2
- package/dist/components/Tooltip/TooltipAPI.d.ts.map +1 -1
- package/dist/components/Tooltip/TooltipAPI.js +51 -51
- package/dist/components/Tooltip/TooltipAPI.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.map +1 -1
- package/dist/right-panel/DefaultPanel.js +3 -1
- package/dist/right-panel/DefaultPanel.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.map +1 -1
- package/dist/right-panel/hooks.js +2 -1
- package/dist/right-panel/hooks.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +8 -8
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js +4 -16
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/state/ChatState.d.ts +13 -1
- package/dist/state/ChatState.d.ts.map +1 -1
- package/dist/state/ChatState.js +38 -3
- package/dist/state/ChatState.js.map +1 -1
- package/dist/state/ObservableState.d.ts +1 -1
- package/dist/state/ObservableState.d.ts.map +1 -1
- package/dist/state/ObservableState.js.map +1 -1
- package/dist/utils/chat.d.ts.map +1 -1
- package/dist/utils/chat.js +3 -2
- package/dist/utils/chat.js.map +1 -1
- package/dist/utils/knowledge-source.d.ts +2 -2
- package/dist/utils/knowledge-source.d.ts.map +1 -1
- package/dist/utils/knowledge-source.js +4 -6
- package/dist/utils/knowledge-source.js.map +1 -1
- package/dist/utils/programming-languages.d.ts +1 -0
- package/dist/utils/programming-languages.d.ts.map +1 -1
- package/dist/utils/programming-languages.js +1 -0
- package/dist/utils/programming-languages.js.map +1 -1
- package/dist/utils/string.d.ts +2 -0
- package/dist/utils/string.d.ts.map +1 -0
- package/dist/utils/string.js +7 -0
- package/dist/utils/string.js.map +1 -0
- 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/Chat/ChatMessage.d.ts +2 -1
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +38 -5
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessages.js +1 -1
- package/dist/views/Chat/ChatMessages.js.map +1 -1
- package/dist/views/Chat/styled.d.ts.map +1 -1
- package/dist/views/Chat/styled.js +31 -0
- package/dist/views/Chat/styled.js.map +1 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.d.ts.map +1 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.js +2 -1
- package/dist/views/ChatHistory/ChatHistoryPanel.js.map +1 -1
- package/dist/views/ChatHistory/HistoryItem.d.ts.map +1 -1
- package/dist/views/ChatHistory/HistoryItem.js +10 -1
- package/dist/views/ChatHistory/HistoryItem.js.map +1 -1
- package/dist/views/Editor.d.ts.map +1 -1
- package/dist/views/Editor.js +3 -4
- package/dist/views/Editor.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.d.ts +1 -1
- package/dist/views/MessageInput/ButtonGroup.d.ts.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts +6 -0
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -0
- package/dist/views/MessageInput/QuickCommandSelector.js +137 -0
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -0
- package/dist/views/MessageInput/index.d.ts.map +1 -1
- package/dist/views/MessageInput/index.js +10 -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/package.json +3 -3
- package/src/AbortedError.ts +7 -0
- package/src/StackspotAIWidget.tsx +13 -3
- package/src/chat-interceptors/CustomInputs.ts +70 -0
- package/src/chat-interceptors/quick-command-questions.ts +15 -0
- package/src/chat-interceptors/quick-commands.ts +270 -7
- package/src/chat-interceptors/send-message.ts +27 -15
- package/src/components/AdaptiveTextArea.tsx +9 -4
- package/src/components/AutoFocus.tsx +20 -0
- package/src/components/Fading.tsx +46 -0
- package/src/components/FallbackBoundary/ErrorBoundary.tsx +26 -3
- package/src/components/FallbackBoundary/Loading.tsx +1 -1
- package/src/components/FallbackBoundary/index.tsx +7 -2
- package/src/components/OverlayMenu.tsx +59 -19
- package/src/components/RightPanelForm.tsx +12 -9
- package/src/components/Tooltip/Tooltip.tsx +24 -5
- package/src/components/Tooltip/TooltipAPI.ts +42 -42
- package/src/layout.css +5 -0
- package/src/regex.ts +1 -0
- package/src/right-panel/DefaultPanel.tsx +14 -9
- package/src/right-panel/constants.ts +1 -0
- package/src/right-panel/hooks.tsx +2 -1
- package/src/state/ChatEntry.ts +7 -20
- package/src/state/ChatState.ts +41 -3
- package/src/state/ObservableState.ts +1 -1
- package/src/utils/chat.ts +3 -2
- package/src/utils/knowledge-source.ts +6 -8
- package/src/utils/programming-languages.ts +2 -0
- package/src/utils/string.ts +6 -0
- package/src/utils/url.ts +8 -0
- package/src/views/Chat/ChatMessage.tsx +67 -13
- package/src/views/Chat/ChatMessages.tsx +4 -1
- package/src/views/Chat/styled.ts +31 -0
- package/src/views/ChatHistory/ChatHistoryPanel.tsx +3 -2
- package/src/views/ChatHistory/HistoryItem.tsx +11 -2
- package/src/views/Editor.tsx +3 -4
- package/src/views/MessageInput/ButtonGroup.tsx +1 -1
- package/src/views/MessageInput/QuickCommandSelector.tsx +210 -0
- package/src/views/MessageInput/index.tsx +12 -4
- package/src/views/MessageInput/styled.ts +137 -0
- package/dist/components/Editor.d.ts +0 -9
- package/dist/components/Editor.d.ts.map +0 -1
- package/dist/components/Editor.js +0 -2
- package/dist/components/Editor.js.map +0 -1
- package/src/components/Editor.tsx +0 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
1
2
|
import { Textarea } from '@citric/core'
|
|
2
|
-
import { useEffect, useRef } from 'react'
|
|
3
|
+
import { forwardRef, useEffect, useRef } from 'react'
|
|
3
4
|
import { PropsOf } from '../types'
|
|
4
5
|
|
|
5
6
|
interface Props extends PropsOf<typeof Textarea> {
|
|
@@ -8,8 +9,12 @@ interface Props extends PropsOf<typeof Textarea> {
|
|
|
8
9
|
maxHeight?: number,
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export const AdaptiveTextArea =
|
|
12
|
-
|
|
12
|
+
export const AdaptiveTextArea = forwardRef<HTMLTextAreaElement, Props>((
|
|
13
|
+
{ value, onIncreaseSize, onResetSize, maxHeight, style, ...props },
|
|
14
|
+
externalRef,
|
|
15
|
+
) => {
|
|
16
|
+
const localRef = useRef<HTMLTextAreaElement>(null)
|
|
17
|
+
const ref = externalRef as React.RefObject<HTMLTextAreaElement> ?? localRef
|
|
13
18
|
|
|
14
19
|
useEffect(() => {
|
|
15
20
|
if (!ref.current) return
|
|
@@ -31,4 +36,4 @@ export const AdaptiveTextArea = ({ value, onIncreaseSize, onResetSize, maxHeight
|
|
|
31
36
|
}, [value, maxHeight])
|
|
32
37
|
|
|
33
38
|
return <Textarea {...props} ref={ref} value={value} style={{ ...style, maxHeight }} />
|
|
34
|
-
}
|
|
39
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
2
|
+
import { forwardRef, RefObject, useEffect, useRef } from 'react'
|
|
3
|
+
|
|
4
|
+
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
delay?: number,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const AutoFocus = forwardRef<HTMLDivElement, Props>(({ children, delay = 0, ...props }, externalRef) => {
|
|
9
|
+
const localRef = useRef<HTMLDivElement>(null)
|
|
10
|
+
const ref = externalRef as RefObject<HTMLDivElement> ?? localRef
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
// fixme: we should actually call `focusFirstChild` from the component lib, but, it's bugged
|
|
15
|
+
(ref.current?.querySelector('a, button, input, select, textarea') as HTMLElement)?.focus()
|
|
16
|
+
}, delay)
|
|
17
|
+
}, [])
|
|
18
|
+
|
|
19
|
+
return <div ref={ref} {...props}>{children}</div>
|
|
20
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
2
|
+
import { WithStyle } from '@stack-spot/portal-theme'
|
|
3
|
+
import { forwardRef, useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { WithChildren } from '../types'
|
|
5
|
+
|
|
6
|
+
interface Props extends WithChildren, WithStyle {
|
|
7
|
+
visible: boolean,
|
|
8
|
+
/**
|
|
9
|
+
* Duration of the animation in ms.
|
|
10
|
+
* @default 300
|
|
11
|
+
*/
|
|
12
|
+
duration?: number,
|
|
13
|
+
onFadeIn?: () => void,
|
|
14
|
+
onFadeOut?: () => void,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Fading = forwardRef<HTMLDivElement, Props>((
|
|
18
|
+
{ visible, children, duration = 300, onFadeIn, onFadeOut, className, style },
|
|
19
|
+
ref,
|
|
20
|
+
) => {
|
|
21
|
+
const [isOpaque, setOpaque] = useState(visible)
|
|
22
|
+
const [isRendered, setRendered] = useState(visible)
|
|
23
|
+
const previous = useRef(visible)
|
|
24
|
+
const timeout = useRef<number[]>([])
|
|
25
|
+
const opacity: React.CSSProperties = { transition: `opacity ${duration / 1000}s`, opacity: isOpaque ? 1 : 0 }
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (previous.current === visible) return
|
|
29
|
+
timeout.current.forEach(window.clearTimeout)
|
|
30
|
+
timeout.current = []
|
|
31
|
+
if (visible) {
|
|
32
|
+
setRendered(true)
|
|
33
|
+
timeout.current.push(window.setTimeout(() => setOpaque(true), 0))
|
|
34
|
+
if (onFadeIn) timeout.current.push(window.setTimeout(onFadeIn, duration))
|
|
35
|
+
} else {
|
|
36
|
+
setOpaque(false)
|
|
37
|
+
timeout.current.push(window.setTimeout(() => {
|
|
38
|
+
setRendered(false)
|
|
39
|
+
onFadeOut?.()
|
|
40
|
+
}, duration))
|
|
41
|
+
}
|
|
42
|
+
previous.current = visible
|
|
43
|
+
}, [visible])
|
|
44
|
+
|
|
45
|
+
return isRendered ? <div ref={ref} className={className} style={{ ...style, ...opacity }}>{children}</div> : null
|
|
46
|
+
})
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
+
import { IconBox, Text } from '@citric/core'
|
|
2
|
+
import { TimesCircle } from '@citric/icons'
|
|
1
3
|
import { ErrorDescription, ErrorFeedback } from '@stack-spot/portal-components/error'
|
|
2
4
|
import { StackspotAPIError } from '@stack-spot/portal-network'
|
|
3
5
|
import { Component } from 'react'
|
|
6
|
+
import { styled } from 'styled-components'
|
|
4
7
|
|
|
5
8
|
interface State extends ErrorDescription {
|
|
6
9
|
hasError: boolean,
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
interface Props {
|
|
13
|
+
mini?: boolean,
|
|
14
|
+
message?: string,
|
|
10
15
|
children: React.ReactNode,
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
const ErrorBox = styled.div`
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 100%;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
`
|
|
26
|
+
|
|
13
27
|
/**
|
|
14
28
|
* An Error Boundary that renders an ErrorFeedback instead of its content if any of its children throws.
|
|
15
29
|
*
|
|
@@ -40,9 +54,18 @@ export class ErrorBoundary extends Component<Props, State> {
|
|
|
40
54
|
if (this.props.children !== prevProps.children) this.setState({ hasError: false })
|
|
41
55
|
}
|
|
42
56
|
|
|
57
|
+
private renderError() {
|
|
58
|
+
return this.props.mini
|
|
59
|
+
? (
|
|
60
|
+
<ErrorBox className="error">
|
|
61
|
+
<IconBox size="lg" colorIcon="danger.500"><TimesCircle /></IconBox>
|
|
62
|
+
<Text colorScheme="light.700">{this.props.message || this.state.message}</Text>
|
|
63
|
+
</ErrorBox>
|
|
64
|
+
)
|
|
65
|
+
: <ErrorFeedback className="error" code={this.state.code} message={this.props.message || this.state.message} />
|
|
66
|
+
}
|
|
67
|
+
|
|
43
68
|
render() {
|
|
44
|
-
return this.state.hasError
|
|
45
|
-
? <ErrorFeedback code={this.state.code} message={this.state.message} />
|
|
46
|
-
: this.props.children
|
|
69
|
+
return this.state.hasError ? this.renderError() : this.props.children
|
|
47
70
|
}
|
|
48
71
|
}
|
|
@@ -3,11 +3,16 @@ import { WithChildren } from '../../types'
|
|
|
3
3
|
import { ErrorBoundary } from './ErrorBoundary'
|
|
4
4
|
import { Loading } from './Loading'
|
|
5
5
|
|
|
6
|
+
interface Props extends WithChildren {
|
|
7
|
+
mini?: boolean,
|
|
8
|
+
message?: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Fallbacks for errors and loadings (suspense).
|
|
8
13
|
*/
|
|
9
|
-
export const FallbackBoundary = ({ children }:
|
|
10
|
-
<ErrorBoundary>
|
|
14
|
+
export const FallbackBoundary = ({ children, mini, message }: Props) => (
|
|
15
|
+
<ErrorBoundary mini={mini} message={message}>
|
|
11
16
|
<Suspense fallback={<Loading />}>
|
|
12
17
|
{children}
|
|
13
18
|
</Suspense>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
1
2
|
/* eslint-disable no-empty-pattern */
|
|
2
3
|
import { IconBox, Text } from '@citric/core'
|
|
4
|
+
import { useKeyboardControls } from '@stack-spot/portal-components'
|
|
3
5
|
import { theme, WithStyle } from '@stack-spot/portal-theme'
|
|
4
|
-
import {
|
|
6
|
+
import { forwardRef, RefObject, useCallback, useRef } from 'react'
|
|
5
7
|
import { styled } from 'styled-components'
|
|
6
8
|
import { ButtonAction, WithChildren } from '../types'
|
|
7
9
|
import { Tooltip } from './Tooltip'
|
|
@@ -13,6 +15,11 @@ interface Props extends WithStyle, WithChildren {
|
|
|
13
15
|
actions: ButtonAction[],
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
interface MenuProps {
|
|
19
|
+
actions: ButtonAction[],
|
|
20
|
+
trigger: RefObject<HTMLDivElement>,
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
const MenuList = styled.ul`
|
|
17
24
|
margin: 0;
|
|
18
25
|
padding: 0;
|
|
@@ -37,8 +44,9 @@ const MenuList = styled.ul`
|
|
|
37
44
|
gap: 8px;
|
|
38
45
|
background-color: transparent;
|
|
39
46
|
border: none;
|
|
47
|
+
outline: none;
|
|
40
48
|
|
|
41
|
-
&:hover {
|
|
49
|
+
&:hover, &:focus {
|
|
42
50
|
background-color: ${theme.color.light[500]};
|
|
43
51
|
}
|
|
44
52
|
}
|
|
@@ -53,25 +61,57 @@ const StyledButton = styled.button<{ $color: string | undefined }>`
|
|
|
53
61
|
}
|
|
54
62
|
`
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
const Menu = ({ actions, trigger }: MenuProps) => {
|
|
57
65
|
const tooltip = useTooltip()
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
))
|
|
70
|
-
return <MenuList>{items}</MenuList>
|
|
71
|
-
}, [actions])
|
|
66
|
+
const ref = useRef<HTMLUListElement>(null)
|
|
67
|
+
|
|
68
|
+
useKeyboardControls({
|
|
69
|
+
querySelectors: 'button',
|
|
70
|
+
onPressEscape: () => {
|
|
71
|
+
tooltip.hide()
|
|
72
|
+
trigger.current?.focus()
|
|
73
|
+
},
|
|
74
|
+
ref,
|
|
75
|
+
}, [])
|
|
76
|
+
|
|
72
77
|
return (
|
|
73
|
-
<
|
|
78
|
+
<MenuList ref={ref}>
|
|
79
|
+
{actions.map(({ label, onClick, className, color, icon, style }) => (
|
|
80
|
+
<li key={label} className={className} style={style}>
|
|
81
|
+
<StyledButton $color={color} onClick={() => {
|
|
82
|
+
onClick()
|
|
83
|
+
tooltip.hide()
|
|
84
|
+
}}>
|
|
85
|
+
<IconBox>{icon}</IconBox>
|
|
86
|
+
<Text>{label}</Text>
|
|
87
|
+
</StyledButton>
|
|
88
|
+
</li>
|
|
89
|
+
))}
|
|
90
|
+
</MenuList>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const OverlayMenu = forwardRef<HTMLDivElement, Props>(({ actions, children, className, position, style }, externalRef) => {
|
|
95
|
+
const localRef = useRef<HTMLDivElement>(null)
|
|
96
|
+
const ref = externalRef as RefObject<HTMLDivElement> ?? localRef
|
|
97
|
+
const tooltip = useTooltip()
|
|
98
|
+
|
|
99
|
+
const onShow = useCallback(() => {
|
|
100
|
+
tooltip.tooltipRef.current?.querySelector('button')?.focus()
|
|
101
|
+
}, [])
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Tooltip
|
|
105
|
+
content={<Menu actions={actions} trigger={ref} />}
|
|
106
|
+
custom
|
|
107
|
+
position={position}
|
|
108
|
+
className={className}
|
|
109
|
+
style={style}
|
|
110
|
+
triggeredBy="click"
|
|
111
|
+
onShow={onShow}
|
|
112
|
+
ref={ref}
|
|
113
|
+
>
|
|
74
114
|
{children}
|
|
75
115
|
</Tooltip>
|
|
76
116
|
)
|
|
77
|
-
}
|
|
117
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Card } from '@citric/ui'
|
|
2
2
|
import { styled } from 'styled-components'
|
|
3
3
|
import { PropsOf } from '../types'
|
|
4
|
+
import { AutoFocus } from './AutoFocus'
|
|
4
5
|
import { FallbackBoundary } from './FallbackBoundary'
|
|
5
6
|
|
|
6
7
|
const Form = styled.form`
|
|
@@ -42,14 +43,16 @@ const Form = styled.form`
|
|
|
42
43
|
|
|
43
44
|
export const RightPanelForm = ({ children, onSubmit, ...props }: PropsOf<typeof Form>) => (
|
|
44
45
|
<FallbackBoundary>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
e
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
<AutoFocus>
|
|
47
|
+
<Form
|
|
48
|
+
{...props}
|
|
49
|
+
onSubmit={(e) => {
|
|
50
|
+
e.preventDefault()
|
|
51
|
+
onSubmit?.(e)
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</Form>
|
|
56
|
+
</AutoFocus>
|
|
54
57
|
</FallbackBoundary>
|
|
55
58
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
1
2
|
import { Text } from '@citric/core'
|
|
2
3
|
import { WithStyle } from '@stack-spot/portal-theme'
|
|
4
|
+
import { forwardRef } from 'react'
|
|
3
5
|
import { WithChildren } from '../../types'
|
|
4
6
|
import { useTooltip } from './context'
|
|
5
7
|
import { DefaultTooltip } from './style'
|
|
@@ -10,27 +12,44 @@ interface Props extends WithChildren, WithStyle {
|
|
|
10
12
|
position?: TooltipPosition,
|
|
11
13
|
triggeredBy?: 'click' | 'hover',
|
|
12
14
|
custom?: boolean,
|
|
15
|
+
onShow?: () => void,
|
|
16
|
+
onHide?: () => void,
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export const Tooltip =
|
|
19
|
+
export const Tooltip = forwardRef<HTMLDivElement, Props>((
|
|
20
|
+
{ content, custom, position, triggeredBy = 'hover', onHide, onShow, children, className, style },
|
|
21
|
+
ref,
|
|
22
|
+
) => {
|
|
16
23
|
const api = useTooltip()
|
|
17
24
|
|
|
18
|
-
function show(e: React.
|
|
19
|
-
api.show({
|
|
25
|
+
async function show(e: React.UIEvent, hideOnClickOutside?: boolean) {
|
|
26
|
+
await api.show({
|
|
20
27
|
content: custom ? content : <DefaultTooltip><Text appearance="microtext1">{content}</Text></DefaultTooltip>,
|
|
21
28
|
anchor: e.target as HTMLElement,
|
|
22
29
|
position,
|
|
23
30
|
hideOnClickOutside,
|
|
24
31
|
})
|
|
32
|
+
onShow?.()
|
|
25
33
|
}
|
|
34
|
+
|
|
35
|
+
function hide() {
|
|
36
|
+
api.hide()
|
|
37
|
+
onHide?.()
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
return (
|
|
27
41
|
<div
|
|
28
|
-
{...(triggeredBy === 'hover'
|
|
42
|
+
{...(triggeredBy === 'hover'
|
|
43
|
+
? { onMouseEnter: show, onMouseLeave: hide }
|
|
44
|
+
: { onClick: (e) => show(e, true), onKeyDown: (e) => e.key === 'Enter' && show(e, true) }
|
|
45
|
+
)}
|
|
29
46
|
className={className}
|
|
30
47
|
style={style}
|
|
31
48
|
tabIndex={triggeredBy === 'click' ? 0 : undefined}
|
|
49
|
+
role={triggeredBy === 'click' ? 'button' : undefined}
|
|
50
|
+
ref={ref}
|
|
32
51
|
>
|
|
33
52
|
{children}
|
|
34
53
|
</div>
|
|
35
54
|
)
|
|
36
|
-
}
|
|
55
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { delay } from '@stack-spot/portal-components'
|
|
1
2
|
import { animationTimeMS } from './style'
|
|
2
3
|
import { ShowOptions } from './types'
|
|
3
4
|
|
|
@@ -8,7 +9,7 @@ function isRelative(element: HTMLElement) {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export class TooltipAPI {
|
|
11
|
-
|
|
12
|
+
tooltipRef: React.RefObject<HTMLDivElement>
|
|
12
13
|
private setContent: React.Dispatch<React.SetStateAction<React.ReactNode>>
|
|
13
14
|
private hideTimeoutId: number | undefined
|
|
14
15
|
private clickListener: ((e: MouseEvent) => void) | undefined
|
|
@@ -27,52 +28,51 @@ export class TooltipAPI {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
show({ content, anchor, position = 'bottom', hideOnClickOutside }: ShowOptions)
|
|
31
|
+
async show({ content, anchor, position = 'bottom', hideOnClickOutside }: ShowOptions) {
|
|
31
32
|
window.clearTimeout(this.hideTimeoutId)
|
|
32
33
|
this.hideTimeoutId = undefined
|
|
33
34
|
if (this.clickListener) document.removeEventListener('click', this.clickListener)
|
|
34
35
|
this.setContent(content)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
document.addEventListener('click', this.clickListener)
|
|
36
|
+
await delay(10)
|
|
37
|
+
if (!this.tooltipRef.current) return
|
|
38
|
+
const anchorRect = anchor.getClientRects()[0]
|
|
39
|
+
this.tooltipRef.current.classList.add('visible')
|
|
40
|
+
const tooltipWidth = this.tooltipRef.current.clientWidth
|
|
41
|
+
const tooltipHeight = this.tooltipRef.current.clientHeight
|
|
42
|
+
let top = 0
|
|
43
|
+
let left = 0
|
|
44
|
+
if (position === 'left' || position === 'right') {
|
|
45
|
+
top = anchorRect.top + anchorRect.height / 2 - tooltipHeight / 2
|
|
46
|
+
if (position === 'left') left = anchorRect.left - tooltipWidth
|
|
47
|
+
else left = anchorRect.left + anchorRect.width
|
|
48
|
+
} else {
|
|
49
|
+
left = anchorRect.left + anchorRect.width / 2 - tooltipWidth / 2
|
|
50
|
+
if (position === 'top') top = anchorRect.top - tooltipHeight
|
|
51
|
+
else top = anchorRect.top + anchorRect.height
|
|
52
|
+
}
|
|
53
|
+
// takes the parent the tooltip is positioned relative to into consideration
|
|
54
|
+
this.computeRelativeTo()
|
|
55
|
+
const relativeRect = this.relativeTo?.getClientRects()[0] ?? { top: 0, left: 0 }
|
|
56
|
+
top -= relativeRect.top
|
|
57
|
+
left -= relativeRect.left
|
|
58
|
+
// adjusts positions in order to avoid overflowing the window and leaving a margin to the corners
|
|
59
|
+
if (top <= 0) top += MARGIN_TO_CORNERS_PX
|
|
60
|
+
else if (top + tooltipHeight >= document.body.clientHeight - MARGIN_TO_CORNERS_PX) {
|
|
61
|
+
top = document.body.clientHeight - MARGIN_TO_CORNERS_PX + tooltipHeight
|
|
62
|
+
}
|
|
63
|
+
if (left <= 0) left += MARGIN_TO_CORNERS_PX
|
|
64
|
+
else if (left + tooltipWidth >= document.body.clientWidth - MARGIN_TO_CORNERS_PX) {
|
|
65
|
+
left = document.body.clientWidth - MARGIN_TO_CORNERS_PX - tooltipWidth
|
|
66
|
+
}
|
|
67
|
+
this.tooltipRef.current.style.top = `${top}px`
|
|
68
|
+
this.tooltipRef.current.style.left = `${left}px`
|
|
69
|
+
if (hideOnClickOutside) {
|
|
70
|
+
this.clickListener = (e: MouseEvent) => {
|
|
71
|
+
if (this.tooltipRef.current?.contains(e.target as HTMLElement)) return
|
|
72
|
+
this.hide()
|
|
74
73
|
}
|
|
75
|
-
|
|
74
|
+
document.addEventListener('click', this.clickListener)
|
|
75
|
+
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
hide(): void {
|
package/src/layout.css
CHANGED
package/src/regex.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const quickCommandRegex = /^\/[\w\d-_]+$/
|
|
@@ -3,7 +3,9 @@ import { Times } from '@citric/icons'
|
|
|
3
3
|
import { IconButton } from '@citric/ui'
|
|
4
4
|
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
5
5
|
import { styled } from 'styled-components'
|
|
6
|
+
import { AutoFocus } from '../components/AutoFocus'
|
|
6
7
|
import { WithChildren } from '../types'
|
|
8
|
+
import { panelAnimationTime } from './constants'
|
|
7
9
|
|
|
8
10
|
interface Props extends WithChildren {
|
|
9
11
|
title: React.ReactNode,
|
|
@@ -42,17 +44,20 @@ const PanelBox = styled.div`
|
|
|
42
44
|
|
|
43
45
|
export const DefaultPanel = ({ description, onClose, title, children }: Props) => {
|
|
44
46
|
const t = useTranslate(dictionary)
|
|
47
|
+
|
|
45
48
|
return (
|
|
46
49
|
<PanelBox>
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
<AutoFocus delay={panelAnimationTime}>
|
|
51
|
+
<header>
|
|
52
|
+
<div className="title">
|
|
53
|
+
{typeof title === 'string' ? <Text appearance="h5">{title}</Text> : title}
|
|
54
|
+
{typeof description === 'string' ? <Text colorScheme="light.700">{description}</Text> : description}
|
|
55
|
+
</div>
|
|
56
|
+
<IconButton title={t.close} aria-label={t.close} onClick={onClose}>
|
|
57
|
+
<Times />
|
|
58
|
+
</IconButton>
|
|
59
|
+
</header>
|
|
60
|
+
</AutoFocus>
|
|
56
61
|
<article>{children}</article>
|
|
57
62
|
</PanelBox>
|
|
58
63
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const panelAnimationTime = 300
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'react'
|
|
2
2
|
import { DefaultPanel } from './DefaultPanel'
|
|
3
3
|
import { RightPanelContext } from './RightPanelProvider'
|
|
4
|
+
import { panelAnimationTime } from './constants'
|
|
4
5
|
|
|
5
6
|
interface RightPanelOptions {
|
|
6
7
|
title: React.ReactNode,
|
|
@@ -35,7 +36,7 @@ export function useRightPanel() {
|
|
|
35
36
|
ctx.onCloseNext.current?.()
|
|
36
37
|
setTimeout(() => {
|
|
37
38
|
setContent(null)
|
|
38
|
-
},
|
|
39
|
+
}, panelAnimationTime)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
function isOpen() {
|
package/src/state/ChatEntry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ColorPaletteName } from '@stack-spot/portal-theme'
|
|
1
2
|
import { pull } from 'lodash'
|
|
2
3
|
import { LabeledWithImage } from './types'
|
|
3
4
|
|
|
@@ -27,16 +28,15 @@ export interface KnowledgeSource {
|
|
|
27
28
|
export interface TextChatEntry {
|
|
28
29
|
type: 'text' | 'md',
|
|
29
30
|
agentType: 'bot' | 'user' | 'system',
|
|
30
|
-
// image?: string,
|
|
31
31
|
actions?: ChatAction[],
|
|
32
|
-
subtitle?: string,
|
|
33
32
|
content: string,
|
|
34
33
|
knowledgeSources?: KnowledgeSource[],
|
|
35
34
|
updated?: string,
|
|
36
35
|
agent?: LabeledWithImage,
|
|
37
36
|
messageId?: string,
|
|
38
37
|
error?: string,
|
|
39
|
-
|
|
38
|
+
badges?: { color?: ColorPaletteName, label: string }[],
|
|
39
|
+
card?: boolean,
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
type ChatEntryListener = (value: TextChatEntry) => void
|
|
@@ -46,20 +46,15 @@ let nextId = 0
|
|
|
46
46
|
export class ChatEntry {
|
|
47
47
|
readonly id: number
|
|
48
48
|
private value: TextChatEntry
|
|
49
|
-
private streamFinished: boolean
|
|
50
49
|
private listeners: ChatEntryListener[] = []
|
|
51
|
-
abort: () => void
|
|
52
50
|
|
|
53
51
|
/**
|
|
54
52
|
* @param value the value of the entry.
|
|
55
53
|
* @param isStreamed whether or not this entry is streamed. Defaults to false.
|
|
56
|
-
* @param abort an abort function to cancel the transmission of this chat entry. Specially useful for canceling streamings.
|
|
57
54
|
*/
|
|
58
|
-
constructor(value: TextChatEntry
|
|
55
|
+
constructor(value: TextChatEntry) {
|
|
59
56
|
this.id = nextId++
|
|
60
57
|
this.value = value
|
|
61
|
-
this.streamFinished = !isStreamed
|
|
62
|
-
this.abort = abort
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
static createUserEntry(content: string, isMd = false) {
|
|
@@ -71,8 +66,8 @@ export class ChatEntry {
|
|
|
71
66
|
})
|
|
72
67
|
}
|
|
73
68
|
|
|
74
|
-
static createStreamedBotEntry(
|
|
75
|
-
return new ChatEntry({ agentType: 'bot', type: 'md', content: '' }
|
|
69
|
+
static createStreamedBotEntry() {
|
|
70
|
+
return new ChatEntry({ agentType: 'bot', type: 'md', content: '' })
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
setValue(value: TextChatEntry) {
|
|
@@ -84,16 +79,8 @@ export class ChatEntry {
|
|
|
84
79
|
return this.value
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
finish() {
|
|
88
|
-
this.streamFinished = true
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
hasFinished() {
|
|
92
|
-
return this.streamFinished
|
|
93
|
-
}
|
|
94
|
-
|
|
95
82
|
onChange(listener: ChatEntryListener) {
|
|
96
|
-
|
|
83
|
+
this.listeners.push(listener)
|
|
97
84
|
return () => {
|
|
98
85
|
pull(this.listeners, listener)
|
|
99
86
|
}
|