@stack-spot/ai-chat-widget 1.18.0 → 1.20.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 +19 -0
- package/dist/StackspotAIWidget.d.ts +3 -2
- package/dist/StackspotAIWidget.d.ts.map +1 -1
- package/dist/StackspotAIWidget.js +5 -4
- package/dist/StackspotAIWidget.js.map +1 -1
- package/dist/app-metadata.json +6 -6
- package/dist/chat-interceptors/send-message.d.ts.map +1 -1
- package/dist/chat-interceptors/send-message.js +7 -2
- package/dist/chat-interceptors/send-message.js.map +1 -1
- package/dist/components/ComponentNavigator.d.ts +38 -0
- package/dist/components/ComponentNavigator.d.ts.map +1 -0
- package/dist/components/ComponentNavigator.js +33 -0
- package/dist/components/ComponentNavigator.js.map +1 -0
- package/dist/components/ListResource.d.ts +29 -0
- package/dist/components/ListResource.d.ts.map +1 -0
- package/dist/components/ListResource.js +17 -0
- package/dist/components/ListResource.js.map +1 -0
- package/dist/components/RightPanelForm.d.ts.map +1 -1
- package/dist/components/RightPanelForm.js +29 -1
- package/dist/components/RightPanelForm.js.map +1 -1
- package/dist/components/Selector/index.js +5 -5
- package/dist/components/Selector/index.js.map +1 -1
- package/dist/components/Selector/styled.d.ts +3 -1
- package/dist/components/Selector/styled.d.ts.map +1 -1
- package/dist/components/Selector/styled.js +2 -1
- package/dist/components/Selector/styled.js.map +1 -1
- package/dist/components/WorkspaceTabNavigator.d.ts +17 -0
- package/dist/components/WorkspaceTabNavigator.d.ts.map +1 -0
- package/dist/components/WorkspaceTabNavigator.js +95 -0
- package/dist/components/WorkspaceTabNavigator.js.map +1 -0
- package/dist/components/form/DescribedCheckboxGroup.d.ts.map +1 -1
- package/dist/components/form/DescribedCheckboxGroup.js +23 -2
- package/dist/components/form/DescribedCheckboxGroup.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/state/ChatEntry.d.ts +17 -0
- package/dist/state/ChatEntry.d.ts.map +1 -1
- package/dist/state/ChatEntry.js.map +1 -1
- package/dist/views/Agents/AgentsPanel.d.ts.map +1 -1
- package/dist/views/Agents/AgentsPanel.js +19 -11
- package/dist/views/Agents/AgentsPanel.js.map +1 -1
- package/dist/views/Agents/AgentsTab.d.ts +9 -3
- package/dist/views/Agents/AgentsTab.d.ts.map +1 -1
- package/dist/views/Agents/AgentsTab.js +25 -7
- package/dist/views/Agents/AgentsTab.js.map +1 -1
- package/dist/views/Agents/dictionary.d.ts +1 -1
- package/dist/views/Agents/dictionary.d.ts.map +1 -1
- package/dist/views/Agents/dictionary.js +2 -0
- package/dist/views/Agents/dictionary.js.map +1 -1
- package/dist/views/Chat/ChatMessage.d.ts +16 -2
- package/dist/views/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessage.js +14 -12
- package/dist/views/Chat/ChatMessage.js.map +1 -1
- package/dist/views/Chat/ChatMessages.d.ts +3 -2
- package/dist/views/Chat/ChatMessages.d.ts.map +1 -1
- package/dist/views/Chat/ChatMessages.js +2 -2
- package/dist/views/Chat/ChatMessages.js.map +1 -1
- package/dist/views/Chat/StepsList.js +2 -2
- package/dist/views/Chat/StepsList.js.map +1 -1
- package/dist/views/Chat/index.d.ts +3 -2
- package/dist/views/Chat/index.d.ts.map +1 -1
- package/dist/views/Chat/index.js +2 -2
- package/dist/views/Chat/index.js.map +1 -1
- package/dist/views/ChatHistory/utils.d.ts.map +1 -1
- package/dist/views/ChatHistory/utils.js +12 -3
- package/dist/views/ChatHistory/utils.js.map +1 -1
- package/dist/views/KnowledgeSources.d.ts +12 -0
- package/dist/views/KnowledgeSources.d.ts.map +1 -1
- package/dist/views/KnowledgeSources.js +20 -6
- package/dist/views/KnowledgeSources.js.map +1 -1
- package/dist/views/MessageInput/AgentSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/AgentSelector.js +11 -7
- package/dist/views/MessageInput/AgentSelector.js.map +1 -1
- package/dist/views/MessageInput/ButtonGroup.js +2 -2
- package/dist/views/MessageInput/ButtonGroup.js.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.d.ts.map +1 -1
- package/dist/views/MessageInput/QuickCommandSelector.js +12 -4
- package/dist/views/MessageInput/QuickCommandSelector.js.map +1 -1
- package/dist/views/MessageInput/dictionary.d.ts +1 -1
- package/dist/views/MessageInput/dictionary.d.ts.map +1 -1
- package/dist/views/MessageInput/dictionary.js +4 -4
- package/dist/views/MessageInput/dictionary.js.map +1 -1
- package/dist/views/Stacks.d.ts +9 -0
- package/dist/views/Stacks.d.ts.map +1 -1
- package/dist/views/Stacks.js +37 -14
- package/dist/views/Stacks.js.map +1 -1
- package/dist/views/Workspaces/WorkspacesTab.d.ts +20 -0
- package/dist/views/Workspaces/WorkspacesTab.d.ts.map +1 -0
- package/dist/views/Workspaces/WorkspacesTab.js +64 -0
- package/dist/views/Workspaces/WorkspacesTab.js.map +1 -0
- package/dist/views/{Workspaces.d.ts → Workspaces/index.d.ts} +1 -1
- package/dist/views/Workspaces/index.d.ts.map +1 -0
- package/dist/views/Workspaces/index.js +67 -0
- package/dist/views/Workspaces/index.js.map +1 -0
- package/package.json +3 -3
- package/src/StackspotAIWidget.tsx +20 -6
- package/src/app-metadata.json +6 -6
- package/src/chat-interceptors/send-message.ts +7 -2
- package/src/components/ComponentNavigator.tsx +103 -0
- package/src/components/ListResource.tsx +60 -0
- package/src/components/RightPanelForm.tsx +29 -1
- package/src/components/Selector/index.tsx +5 -5
- package/src/components/Selector/styled.ts +3 -2
- package/src/components/WorkspaceTabNavigator.tsx +175 -0
- package/src/components/form/DescribedCheckboxGroup.tsx +38 -7
- package/src/index.ts +2 -0
- package/src/state/ChatEntry.ts +17 -0
- package/src/views/Agents/AgentsPanel.tsx +21 -11
- package/src/views/Agents/AgentsTab.tsx +42 -9
- package/src/views/Agents/dictionary.ts +3 -0
- package/src/views/Chat/ChatMessage.tsx +32 -18
- package/src/views/Chat/ChatMessages.tsx +11 -4
- package/src/views/Chat/StepsList.tsx +2 -2
- package/src/views/Chat/index.tsx +4 -3
- package/src/views/ChatHistory/utils.ts +14 -3
- package/src/views/KnowledgeSources.tsx +37 -14
- package/src/views/MessageInput/AgentSelector.tsx +20 -8
- package/src/views/MessageInput/ButtonGroup.tsx +3 -3
- package/src/views/MessageInput/QuickCommandSelector.tsx +19 -6
- package/src/views/MessageInput/dictionary.ts +4 -4
- package/src/views/Stacks.tsx +57 -17
- package/src/views/Workspaces/WorkspacesTab.tsx +120 -0
- package/src/views/Workspaces/index.tsx +76 -0
- package/dist/views/Workspaces.d.ts.map +0 -1
- package/dist/views/Workspaces.js +0 -103
- package/dist/views/Workspaces.js.map +0 -1
- package/src/views/Workspaces.tsx +0 -137
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Flex } from '@citric/core'
|
|
2
|
+
import { ArrowLeft } from '@citric/icons'
|
|
3
|
+
import { IconButton } from '@citric/ui'
|
|
4
|
+
import { createContext, useCallback, useContext, useMemo, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Map of string keys to React components.
|
|
8
|
+
*/
|
|
9
|
+
export type NavigationMap = Record<string, React.ComponentType<any>>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Navigation component describing which component to render and its props.
|
|
13
|
+
*/
|
|
14
|
+
export type NavigationComponent<T extends NavigationMap, K extends keyof T = keyof T> = {
|
|
15
|
+
/** Component key to render. */
|
|
16
|
+
component: K,
|
|
17
|
+
|
|
18
|
+
/** Props for the component. */
|
|
19
|
+
props?: T[K] extends React.ComponentType<infer P> ? P : never,
|
|
20
|
+
|
|
21
|
+
/** Render in fullscreen mode. */
|
|
22
|
+
fullScreen?: boolean,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NavigationContextType<T extends NavigationMap> {
|
|
26
|
+
/** Navigate to a new component. */
|
|
27
|
+
navigate: <K extends keyof T>(item: NavigationComponent<T, K>) => Promise<void>,
|
|
28
|
+
|
|
29
|
+
/** Go back to previous component. */
|
|
30
|
+
goBack: () => void,
|
|
31
|
+
|
|
32
|
+
/** True if can go back. */
|
|
33
|
+
canGoBack: boolean,
|
|
34
|
+
|
|
35
|
+
/** Current component. */
|
|
36
|
+
currentItem: NavigationComponent<T>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ComponentNavigatorProps<T extends NavigationMap, K extends keyof T> {
|
|
40
|
+
/** Initial component to render. */
|
|
41
|
+
initialItem: NavigationComponent<T, K>,
|
|
42
|
+
|
|
43
|
+
/** Map of available components. */
|
|
44
|
+
components: T,
|
|
45
|
+
|
|
46
|
+
/** Optional title renderer. */
|
|
47
|
+
renderTitle?: (item: NavigationComponent<T, keyof T>) => React.ReactNode,
|
|
48
|
+
|
|
49
|
+
/** Optional CSS class. */
|
|
50
|
+
className?: string,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const NavigationContext = createContext<NavigationContextType<NavigationMap> | null>(null)
|
|
54
|
+
|
|
55
|
+
export function ComponentNavigator<T extends NavigationMap, K extends keyof T>({
|
|
56
|
+
initialItem,
|
|
57
|
+
components,
|
|
58
|
+
renderTitle,
|
|
59
|
+
className,
|
|
60
|
+
}: ComponentNavigatorProps<T, K>) {
|
|
61
|
+
const [navigationStack, setNavigationStack] = useState<NavigationComponent<T>[]>([initialItem])
|
|
62
|
+
const currentItem = navigationStack[navigationStack.length - 1]
|
|
63
|
+
|
|
64
|
+
const navigate = useCallback((item: NavigationComponent<T>) => { setNavigationStack((prev) => [...prev, item]) }, [])
|
|
65
|
+
const canGoBack = navigationStack.length > 1
|
|
66
|
+
const goBack = useCallback(() => {
|
|
67
|
+
if (canGoBack) {
|
|
68
|
+
setNavigationStack((prev) => prev.slice(0, -1))
|
|
69
|
+
}
|
|
70
|
+
}, [canGoBack])
|
|
71
|
+
|
|
72
|
+
const navigationContext = useMemo(() => ({ navigate, goBack, canGoBack, currentItem }), [navigate, goBack, canGoBack, currentItem])
|
|
73
|
+
const Component = components[currentItem.component]
|
|
74
|
+
const isFullScreen = currentItem.fullScreen
|
|
75
|
+
|
|
76
|
+
if (!Component) {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error(`Componente not found: ${String(currentItem.component)}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (<NavigationContext.Provider value={navigationContext as NavigationContextType<NavigationMap>}>
|
|
82
|
+
<div className={`content-navigator ${className || isFullScreen ? 'full' : ''}`} role="navigation">
|
|
83
|
+
{canGoBack && (
|
|
84
|
+
<Flex alignItems="center" w={12} sx={{ gap: '4px' }}>
|
|
85
|
+
<IconButton onClick={goBack} appearance="square" className="back-button" aria-label="Back">
|
|
86
|
+
<ArrowLeft />
|
|
87
|
+
</IconButton>
|
|
88
|
+
{renderTitle?.(currentItem)}
|
|
89
|
+
</Flex>
|
|
90
|
+
)}
|
|
91
|
+
<Component {...(currentItem.props as any)} />
|
|
92
|
+
</div>
|
|
93
|
+
</NavigationContext.Provider>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function useComponentNavigation<T extends NavigationMap>(): NavigationContextType<T> {
|
|
98
|
+
const context = useContext(NavigationContext)
|
|
99
|
+
if (!context) {
|
|
100
|
+
throw new Error('useComponentNavigation should be used inside ComponentNavigator')
|
|
101
|
+
}
|
|
102
|
+
return context
|
|
103
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Box, Flex, Text } from '@citric/core'
|
|
2
|
+
import { WithStyle } from '@stack-spot/portal-theme'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @template T Type of list item.
|
|
8
|
+
*/
|
|
9
|
+
interface ListGroupProps<T> extends WithStyle {
|
|
10
|
+
|
|
11
|
+
/** Items to show. */
|
|
12
|
+
list: T[],
|
|
13
|
+
|
|
14
|
+
/** Render the label for an item. */
|
|
15
|
+
renderLabel: (item: T) => React.ReactNode,
|
|
16
|
+
|
|
17
|
+
/** Optional: element before label. */
|
|
18
|
+
renderBeforeElement?: (item: T) => React.ReactNode,
|
|
19
|
+
|
|
20
|
+
/** Optional: element after label. */
|
|
21
|
+
renderAfterElement?: (item: T) => React.ReactNode,
|
|
22
|
+
|
|
23
|
+
/** Optional: custom class name per item. */
|
|
24
|
+
optionClassName?: (item: T) => string | undefined,
|
|
25
|
+
|
|
26
|
+
/** Optional: custom style per item. */
|
|
27
|
+
optionStyle?: (item: T) => React.CSSProperties | undefined,
|
|
28
|
+
|
|
29
|
+
/** Unique key for each item. */
|
|
30
|
+
keygen: (item: T) => React.Key,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders a customizable list of items.
|
|
35
|
+
*
|
|
36
|
+
* @template T Type of list item.
|
|
37
|
+
*/
|
|
38
|
+
export function ListResource<T>({ list, renderLabel, renderBeforeElement, renderAfterElement, className, keygen, style }
|
|
39
|
+
: ListGroupProps<T>) {
|
|
40
|
+
const items = useMemo(() =>
|
|
41
|
+
list.map((listItem) => {
|
|
42
|
+
const label = renderLabel(listItem)
|
|
43
|
+
const content = typeof label === 'string' ? <Text>{label}</Text> : label
|
|
44
|
+
return (
|
|
45
|
+
<li key={keygen(listItem)}>
|
|
46
|
+
<Flex alignItems="center">
|
|
47
|
+
{renderBeforeElement?.(listItem)}
|
|
48
|
+
{content}
|
|
49
|
+
{renderAfterElement?.(listItem)}
|
|
50
|
+
</Flex>
|
|
51
|
+
</li>
|
|
52
|
+
)
|
|
53
|
+
}), [list])
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Box as="ul" m={0} p={0} w={12} style={style} className={className}>
|
|
57
|
+
{items}
|
|
58
|
+
</Box>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Card } from '@citric/ui'
|
|
2
|
+
import { theme } from '@stack-spot/portal-theme'
|
|
2
3
|
import { styled } from 'styled-components'
|
|
3
4
|
import { panelAnimationTime } from '../right-panel/constants'
|
|
4
5
|
import { PropsOf } from '../types'
|
|
@@ -13,12 +14,39 @@ const Form = styled.form`
|
|
|
13
14
|
flex: 1;
|
|
14
15
|
gap: 20px;
|
|
15
16
|
|
|
16
|
-
> .content {
|
|
17
|
+
> .content, .content-navigator .content, .content-navigator:not(.full) {
|
|
17
18
|
display: flex;
|
|
18
19
|
flex-direction: column;
|
|
19
20
|
gap: 8px;
|
|
20
21
|
flex: 1;
|
|
21
22
|
overflow: hidden;
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.content-navigator {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
align-items: flex-start;
|
|
30
|
+
ul {
|
|
31
|
+
list-style: none;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.content-navigator.full {
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
padding: 25px;
|
|
40
|
+
gap: 16px;
|
|
41
|
+
width: calc(100% - 50px);
|
|
42
|
+
height: calc(100% - 100px);
|
|
43
|
+
background: ${theme.color.light[300]};
|
|
44
|
+
|
|
45
|
+
~ .workspace-submit {
|
|
46
|
+
position: absolute;
|
|
47
|
+
left: 25px;
|
|
48
|
+
bottom: 25px;
|
|
49
|
+
}
|
|
22
50
|
}
|
|
23
51
|
|
|
24
52
|
> button {
|
|
@@ -152,7 +152,7 @@ const List = <T extends Item>({ selectorConfig, filter, visibility, onSelect, fa
|
|
|
152
152
|
const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentProps<any>) => {
|
|
153
153
|
const t = useTranslate(dictionary)
|
|
154
154
|
const ref = useRef<HTMLDivElement>(null)
|
|
155
|
-
const [visibility, setVisibility] = useState<SectionVisibility | undefined>(
|
|
155
|
+
const [visibility, setVisibility] = useState<SectionVisibility | undefined>()
|
|
156
156
|
const { resourceName, icon, onSelect, sections } = selectorConfig
|
|
157
157
|
const onSelectItem = useCallback((slug: string) => {
|
|
158
158
|
onSelect(slug)
|
|
@@ -186,8 +186,8 @@ const SelectorContent = ({ filter, onClose, selectorConfig, favorite }: ContentP
|
|
|
186
186
|
</header>
|
|
187
187
|
<div className="body">
|
|
188
188
|
<ul className="tabs">
|
|
189
|
-
{sections.map(createSectionItem)}
|
|
190
189
|
{createSectionItem()}
|
|
190
|
+
{sections.map(createSectionItem)}
|
|
191
191
|
</ul>
|
|
192
192
|
<FallbackBoundary message={interpolate(t.error, selectorConfig.resourceName)} mini>
|
|
193
193
|
<List filter={filter} visibility={visibility} selectorConfig={selectorConfig} onSelect={onSelectItem} favorite={favorite} />
|
|
@@ -248,7 +248,7 @@ export const Selector = <T, >({ inputRef, selectorConfig, favorite }: SelectorPr
|
|
|
248
248
|
}, [shouldRender])
|
|
249
249
|
|
|
250
250
|
return (
|
|
251
|
-
<SelectorBox>
|
|
251
|
+
<SelectorBox tabsCount={selectorConfig.sections.length}>
|
|
252
252
|
<Fading visible={shouldRender} ref={selectorRef} className="box-selector">
|
|
253
253
|
<SelectorContent
|
|
254
254
|
favorite={favorite}
|
|
@@ -269,7 +269,7 @@ const dictionary = {
|
|
|
269
269
|
account: 'Account',
|
|
270
270
|
shared: 'Shared',
|
|
271
271
|
builtIn: 'Built-in',
|
|
272
|
-
workspace: '
|
|
272
|
+
workspace: 'Spot',
|
|
273
273
|
error: 'Could not load the $0s.',
|
|
274
274
|
noData: 'You don\'t have any $0 yet.',
|
|
275
275
|
noResults: 'There are no $0s to show here.',
|
|
@@ -281,7 +281,7 @@ const dictionary = {
|
|
|
281
281
|
personal: 'Pessoal',
|
|
282
282
|
account: 'Conta',
|
|
283
283
|
shared: 'Compartilhado',
|
|
284
|
-
workspace: '
|
|
284
|
+
workspace: 'Spot',
|
|
285
285
|
builtIn: 'Embutido',
|
|
286
286
|
error: 'Não foi possível carregar os $0s.',
|
|
287
287
|
noData: 'Você ainda não possui $0s.',
|
|
@@ -2,7 +2,7 @@ import { theme } from '@stack-spot/portal-theme'
|
|
|
2
2
|
import { styled } from 'styled-components'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
export const SelectorBox = styled.div
|
|
5
|
+
export const SelectorBox = styled.div<{tabsCount: number}>`
|
|
6
6
|
position: absolute;
|
|
7
7
|
bottom: 0;
|
|
8
8
|
|
|
@@ -53,6 +53,8 @@ export const SelectorBox = styled.div`
|
|
|
53
53
|
display: flex;
|
|
54
54
|
flex-direction: row;
|
|
55
55
|
align-items: center;
|
|
56
|
+
align-items: flex-start;
|
|
57
|
+
height: ${({ tabsCount }) => 34 + (tabsCount * 34)}px;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
ul {
|
|
@@ -105,7 +107,6 @@ export const SelectorBox = styled.div`
|
|
|
105
107
|
gap: 2px;
|
|
106
108
|
overflow-y: auto;
|
|
107
109
|
flex: 1;
|
|
108
|
-
max-height: 170px;
|
|
109
110
|
|
|
110
111
|
li {
|
|
111
112
|
display: flex;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Box, Flex, IconBox, Image, Text } from '@citric/core'
|
|
2
|
+
import { ArrowRight, Search, Spaces, Times } from '@citric/icons'
|
|
3
|
+
import { Avatar, IconButton } from '@citric/ui'
|
|
4
|
+
import { Placeholder } from '@stack-spot/portal-components/Placeholder'
|
|
5
|
+
import { workspaceAiClient } from '@stack-spot/portal-network'
|
|
6
|
+
import { WorkspaceResponse, WorkspaceVisibilityLevelEnum } from '@stack-spot/portal-network/api/workspace-ai'
|
|
7
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
8
|
+
import { memo, useMemo, useState } from 'react'
|
|
9
|
+
import { useRightPanel } from '../right-panel/hooks'
|
|
10
|
+
import { ButtonFavorite } from './ButtonFavorite'
|
|
11
|
+
import { ComponentNavigator, ComponentNavigatorProps, NavigationComponent, NavigationMap, useComponentNavigation } from './ComponentNavigator'
|
|
12
|
+
import { IconInput } from './IconInput'
|
|
13
|
+
import { ListResource } from './ListResource'
|
|
14
|
+
|
|
15
|
+
interface CardSpaceProps {
|
|
16
|
+
onClick: VoidFunction,
|
|
17
|
+
name: string,
|
|
18
|
+
icon: React.ReactElement,
|
|
19
|
+
logoUrl?: string | null,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const CardSpace = ({ onClick, name, icon, logoUrl }: CardSpaceProps) =>
|
|
23
|
+
<Flex
|
|
24
|
+
onClick={onClick}
|
|
25
|
+
flex={1}
|
|
26
|
+
alignItems="center"
|
|
27
|
+
justifyContent="space-between"
|
|
28
|
+
mr={2}
|
|
29
|
+
bg="light.400"
|
|
30
|
+
r="sm"
|
|
31
|
+
p={3}
|
|
32
|
+
sx={{ cursor: 'pointer' }}
|
|
33
|
+
>
|
|
34
|
+
<Flex alignContent="center" alignItems="center" sx={{ gap: '8px', m: 1 }} >
|
|
35
|
+
<Avatar size="xxs" appearance="square" sx={{ bg: 'light.600', r: 'xxs' }}>
|
|
36
|
+
{logoUrl ? <Image src={logoUrl} /> : <IconBox> {icon} </IconBox>}
|
|
37
|
+
</Avatar>
|
|
38
|
+
<Text appearance="body2">{name}</Text>
|
|
39
|
+
</Flex>
|
|
40
|
+
<IconButton><ArrowRight /></IconButton>
|
|
41
|
+
</Flex>
|
|
42
|
+
|
|
43
|
+
interface WorkspaceSourcesTabProps {
|
|
44
|
+
visibility: WorkspaceVisibilityLevelEnum,
|
|
45
|
+
onClick: (workspace: WorkspaceResponse) => void,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const WorkspaceSourcesTab = ({ visibility, onClick }: WorkspaceSourcesTabProps) => {
|
|
49
|
+
const t = useTranslate(dictionary)
|
|
50
|
+
const [filter, setFilter] = useState('')
|
|
51
|
+
const workspaces = workspaceAiClient.workspacesAi.useQuery({ visibility })
|
|
52
|
+
const listFavorites = workspaceAiClient.workspacesAi.useQuery({ visibility: 'favorite' })
|
|
53
|
+
const [addFavorite, pendingAddFav] = workspaceAiClient.addFavoriteWorkspaceAi.useMutation()
|
|
54
|
+
const [removeFavorite, pendingRemoveFav] = workspaceAiClient.removeFavoriteWorkspaceAi.useMutation()
|
|
55
|
+
|
|
56
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
57
|
+
const onAddFavorite = async (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
|
|
58
|
+
try {
|
|
59
|
+
await addFavorite({ workspaceId: idOrSlug })
|
|
60
|
+
await workspaceAiClient.workspacesAi.invalidate()
|
|
61
|
+
if (!pendingAddFav) {
|
|
62
|
+
resolve(true)
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.error(error)
|
|
67
|
+
reject(error)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
72
|
+
const onRemoveFavorite = (idOrSlug: string) => new Promise<boolean>(async (resolve, reject) => {
|
|
73
|
+
try {
|
|
74
|
+
await removeFavorite({ workspaceId: idOrSlug })
|
|
75
|
+
await workspaceAiClient.workspacesAi.invalidate()
|
|
76
|
+
if (!pendingRemoveFav) {
|
|
77
|
+
resolve(true)
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error(error)
|
|
82
|
+
reject(error)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const filtered = useMemo(
|
|
87
|
+
// Recreate the list so that the favorites list is taken into account
|
|
88
|
+
() => filter ? workspaces.filter(w => w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) : [...workspaces],
|
|
89
|
+
[workspaces, filter, listFavorites],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<Box w={12}>
|
|
95
|
+
<IconInput icon={<Search />} value={filter} onChange={setFilter} className="search" />
|
|
96
|
+
</Box>
|
|
97
|
+
|
|
98
|
+
{!!filtered.length &&
|
|
99
|
+
<ListResource
|
|
100
|
+
list={filtered}
|
|
101
|
+
keygen={w => w.id}
|
|
102
|
+
style={{ gap: '6px', display: 'flex', flexDirection: 'column' }}
|
|
103
|
+
renderLabel={w => <CardSpace name={w.name} logoUrl={w.logo} icon={<Spaces />} onClick={() => onClick(w)} />}
|
|
104
|
+
renderAfterElement={(w) =>
|
|
105
|
+
<ButtonFavorite favorite={{ idOrSlug: w?.id, listFavorites, onAddFavorite, onRemoveFavorite }} />}
|
|
106
|
+
optionClassName={w => (filter && !w.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()))
|
|
107
|
+
? 'filtered-out'
|
|
108
|
+
: ''
|
|
109
|
+
}
|
|
110
|
+
className="option-list"
|
|
111
|
+
/>
|
|
112
|
+
}
|
|
113
|
+
<Box w={12}>
|
|
114
|
+
{!!workspaces.length && !filtered.length &&
|
|
115
|
+
<Placeholder title={t.noSearchResults} description={t.noSearchResultsDescription} className="no-data-placeholder" />}
|
|
116
|
+
{!workspaces.length && <Placeholder title={t.noData} description={t.noDataDescription} />}
|
|
117
|
+
</Box>
|
|
118
|
+
</>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const WorkspaceHeader = <T extends NavigationMap, K extends keyof T>({ data }: { data: NavigationComponent<T, K> }) => {
|
|
124
|
+
const { close: closeRightPanel } = useRightPanel()
|
|
125
|
+
const workspaceId = (data.props as any)['workspaceId']
|
|
126
|
+
if (!workspaceId) return
|
|
127
|
+
|
|
128
|
+
const workspace = workspaceAiClient.workspaceAi.useQuery({ id: workspaceId })
|
|
129
|
+
return <Flex justifyContent="space-between" alignItems="center" flex={1}>
|
|
130
|
+
{data.component === 'workspaceResource' ? 'Spots' : workspace.name}
|
|
131
|
+
{data.fullScreen && <IconButton title={'t.close'} aria-label={'t.close'} onClick={closeRightPanel}> <Times /> </IconButton>}
|
|
132
|
+
</Flex>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface WorkspaceTabNavigatorProps {
|
|
136
|
+
getNavigateParam: (workspace: WorkspaceResponse) => NavigationComponent<NavigationMap, string>,
|
|
137
|
+
visibility?: WorkspaceVisibilityLevelEnum,
|
|
138
|
+
className?: string,
|
|
139
|
+
}
|
|
140
|
+
export function WorkspaceTabNavigator<T extends NavigationMap, K extends keyof T>({ components, getNavigateParam, visibility, className }:
|
|
141
|
+
Omit<ComponentNavigatorProps<T, K>, 'initialItem'> & WorkspaceTabNavigatorProps) {
|
|
142
|
+
|
|
143
|
+
const workspaceTabComponents = useMemo(() => ({
|
|
144
|
+
workspace: memo(function WorkspacesTab() {
|
|
145
|
+
const { navigate } = useComponentNavigation()
|
|
146
|
+
return (<WorkspaceSourcesTab visibility={visibility ?? 'all'} onClick={(w) => navigate(getNavigateParam(w))} />)
|
|
147
|
+
}),
|
|
148
|
+
...components,
|
|
149
|
+
}), [components])
|
|
150
|
+
|
|
151
|
+
return <ComponentNavigator
|
|
152
|
+
initialItem={{ component: 'workspace' }}
|
|
153
|
+
components={workspaceTabComponents}
|
|
154
|
+
className={className}
|
|
155
|
+
renderTitle={(data) => <WorkspaceHeader data={data} />}
|
|
156
|
+
/>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const dictionary = {
|
|
160
|
+
en: {
|
|
161
|
+
noSearchResults: "Your search didn't yield results.",
|
|
162
|
+
noSearchResultsDescription: 'Please, try another search term.',
|
|
163
|
+
noData: 'There are no spaces yet.',
|
|
164
|
+
noDataDescription: 'Use the AI portal to create new spaces.',
|
|
165
|
+
apply: 'Apply',
|
|
166
|
+
},
|
|
167
|
+
pt: {
|
|
168
|
+
noSearchResults: 'Sua busca não produziu resultados',
|
|
169
|
+
noSearchResultsDescription: 'Por favor, tente outra busca.',
|
|
170
|
+
noData: 'Ainda não há spaces.',
|
|
171
|
+
noDataDescription: 'Use o Portal AI para criar novos spaces.',
|
|
172
|
+
apply: 'Aplicar',
|
|
173
|
+
},
|
|
174
|
+
} satisfies Dictionary
|
|
175
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Checkbox, Text } from '@citric/core'
|
|
1
|
+
import { Checkbox, Flex, Text } from '@citric/core'
|
|
2
2
|
import { listToClass } from '@stack-spot/portal-theme'
|
|
3
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
3
4
|
import { useMemo } from 'react'
|
|
4
5
|
import { Accordion } from '../Accordion'
|
|
5
6
|
import { RadioCheckBox } from './styled'
|
|
@@ -9,30 +10,40 @@ import { CheckProps } from './types'
|
|
|
9
10
|
* Renders a checkbox group where each option has a label and a description.
|
|
10
11
|
* The description in placed under the label and checkbox as an accordion.
|
|
11
12
|
*/
|
|
12
|
-
export function DescribedCheckboxGroup<T>({
|
|
13
|
+
export function DescribedCheckboxGroup<T>({
|
|
13
14
|
keygen,
|
|
14
15
|
onChange,
|
|
15
16
|
options,
|
|
16
17
|
renderDescription,
|
|
17
18
|
renderLabel,
|
|
18
|
-
renderBeforeElement,
|
|
19
|
-
renderAfterElement,
|
|
19
|
+
renderBeforeElement,
|
|
20
|
+
renderAfterElement,
|
|
20
21
|
optionClassName,
|
|
21
22
|
optionStyle,
|
|
22
23
|
value,
|
|
23
24
|
className,
|
|
24
25
|
style }: CheckProps<T>) {
|
|
26
|
+
const t = useTranslate(dictionary)
|
|
27
|
+
const allSelected = options.length > 0 && options.every(option => value.includes(option))
|
|
28
|
+
|
|
29
|
+
const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
30
|
+
if (e.target.checked) {
|
|
31
|
+
onChange(options)
|
|
32
|
+
} else {
|
|
33
|
+
onChange([])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
const items = useMemo(() => options.map((option) => {
|
|
26
38
|
const label = renderLabel(option)
|
|
27
39
|
const description = renderDescription(option)
|
|
28
|
-
|
|
29
40
|
const header = (
|
|
30
41
|
<label>
|
|
31
42
|
<Checkbox
|
|
32
43
|
checked={value.includes(option)}
|
|
33
44
|
onChange={(e) => {
|
|
34
45
|
if (e.target.checked && !value.includes(option)) onChange([...value, option])
|
|
35
|
-
else onChange(value.filter(item => item !== option))
|
|
46
|
+
else onChange(value.filter(item => item !== option))
|
|
36
47
|
}}
|
|
37
48
|
/>
|
|
38
49
|
{typeof label === 'string' ? <Text>{label}</Text> : label}
|
|
@@ -55,5 +66,25 @@ export function DescribedCheckboxGroup<T>({
|
|
|
55
66
|
)
|
|
56
67
|
}), [options, value])
|
|
57
68
|
|
|
58
|
-
return <RadioCheckBox style={style} className={className}>
|
|
69
|
+
return <RadioCheckBox style={style} className={className}>
|
|
70
|
+
<Flex as="li" alignItems="center" ml={3} pl={1}>
|
|
71
|
+
<Checkbox
|
|
72
|
+
checked={allSelected}
|
|
73
|
+
onChange={handleSelectAll}
|
|
74
|
+
/>
|
|
75
|
+
<Text>{allSelected ? t.removeAll : t.selectAll}</Text>
|
|
76
|
+
</Flex>
|
|
77
|
+
{items}
|
|
78
|
+
</RadioCheckBox>
|
|
59
79
|
}
|
|
80
|
+
|
|
81
|
+
const dictionary = {
|
|
82
|
+
en: {
|
|
83
|
+
selectAll: 'Select all',
|
|
84
|
+
removeAll: 'Remove all',
|
|
85
|
+
},
|
|
86
|
+
pt: {
|
|
87
|
+
selectAll: 'Selecionar todos',
|
|
88
|
+
removeAll: 'Remover todos',
|
|
89
|
+
},
|
|
90
|
+
} satisfies Dictionary
|
package/src/index.ts
CHANGED
|
@@ -15,3 +15,5 @@ export { ChatTabsController } from './state/ChatTabsController'
|
|
|
15
15
|
export { ObservableState } from './state/ObservableState'
|
|
16
16
|
export type { Labeled, LabeledAgent, LabeledWithImage } from './state/types'
|
|
17
17
|
export { WidgetState } from './state/WidgetState'
|
|
18
|
+
export { defaultLanguage, languages } from './utils/programming-languages'
|
|
19
|
+
|
package/src/state/ChatEntry.ts
CHANGED
|
@@ -84,8 +84,25 @@ export interface TextChatEntry {
|
|
|
84
84
|
* The content of the message.
|
|
85
85
|
*/
|
|
86
86
|
content: string,
|
|
87
|
+
/**
|
|
88
|
+
* The actual data to send as a message. This is the "content" if not provided.
|
|
89
|
+
*
|
|
90
|
+
* Useful for displaying a text in the chat, via "content", but sending something different to the backend.
|
|
91
|
+
*
|
|
92
|
+
* Serialization: if none of your interceptors return false, this will be converted to a JSON in the last interceptor (sendMessage).
|
|
93
|
+
*
|
|
94
|
+
* Attention: this is ignored by quick commands, which will always use "content".
|
|
95
|
+
*/
|
|
96
|
+
data?: any,
|
|
87
97
|
/**
|
|
88
98
|
* A content that is not shown to the user, it is used only to share data.
|
|
99
|
+
*
|
|
100
|
+
* This is used solely by the onboarding feature (checkboxes and radio button), implemented in the AI Portal. Maybe it shouldn't be
|
|
101
|
+
* implemented here. This needs a review (fixme).
|
|
102
|
+
*
|
|
103
|
+
* This has been implemented because the message rendered is different than the message we wanted to send to the backend.
|
|
104
|
+
*
|
|
105
|
+
* @deprecated prefer "data" instead.
|
|
89
106
|
*/
|
|
90
107
|
hiddenContent?: string[],
|
|
91
108
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { RightPanelTabs } from '../../components/RightPanelTabs'
|
|
3
3
|
import { useCurrentChat } from '../../context/hooks'
|
|
4
|
+
import { isAgentDefault } from '../../utils/agent'
|
|
4
5
|
import { checkIsTrial } from '../../utils/check-is-trial'
|
|
5
|
-
import { AgentsTab } from './AgentsTab'
|
|
6
|
+
import { AgentsTab, AgentsTabWorkspace } from './AgentsTab'
|
|
6
7
|
import { useAgentsDictionary } from './dictionary'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -12,18 +13,27 @@ export const AgentsPanel = () => {
|
|
|
12
13
|
const t = useAgentsDictionary()
|
|
13
14
|
const chat = useCurrentChat()
|
|
14
15
|
const isTrial = checkIsTrial()
|
|
16
|
+
const agent = useRef(chat.get('agent'))
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!isAgentDefault(chat.get('agent')?.slug)) {
|
|
20
|
+
agent.current = chat.get('agent')
|
|
21
|
+
}
|
|
22
|
+
}, [chat])
|
|
15
23
|
|
|
16
24
|
const tabs= useMemo(() => isTrial ? [
|
|
17
|
-
{ title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
|
|
18
|
-
{ title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
|
|
19
|
-
{ title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
|
|
25
|
+
{ title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" agent={agent} /> },
|
|
26
|
+
{ title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" agent={agent} /> },
|
|
27
|
+
{ title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" agent={agent} /> },
|
|
20
28
|
]: [
|
|
21
|
-
{ title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" /> },
|
|
22
|
-
{ title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" /> },
|
|
23
|
-
{ title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" /> },
|
|
24
|
-
{ title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" /> },
|
|
25
|
-
{ title: t.
|
|
26
|
-
|
|
29
|
+
{ title: t.favorites, content: <AgentsTab key="favorite" visibility="FAVORITE" agent={agent} /> },
|
|
30
|
+
{ title: t.builtin, content: <AgentsTab key="builtin" visibility="BUILT-IN" agent={agent} /> },
|
|
31
|
+
{ title: t.personal, content: <AgentsTab key="personal" visibility="PERSONAL" agent={agent} /> },
|
|
32
|
+
{ title: t.shared, content: <AgentsTab key="shared" visibility="SHARED" agent={agent} /> },
|
|
33
|
+
{ title: t.spots, content: <AgentsTabWorkspace key="workspace" visibility="WORKSPACE" agent={agent} /> },
|
|
34
|
+
{ title: t.account, content: <AgentsTab key="account" visibility="ACCOUNT" agent={agent} /> },
|
|
35
|
+
|
|
36
|
+
], [t, isTrial, agent])
|
|
27
37
|
|
|
28
38
|
return <RightPanelTabs key={chat.id} tabs={tabs} />
|
|
29
39
|
}
|