@sybilion/uilib 1.3.43 → 1.3.46
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/assets/logo.svg +1 -1
- package/dist/esm/components/ui/Chart/Chart.styl.js +1 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +7 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +7 -5
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptComposer.js +8 -4
- package/dist/esm/components/ui/Chat/ChatPrompt/chatPromptComposerInsert.js +67 -0
- package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +24 -17
- package/dist/esm/components/ui/Logo/Logo.styl.js +1 -1
- package/dist/esm/components/ui/Logo/logo.svg.js +1 -1
- package/dist/esm/components/ui/Page/PageFooter/PageFooter.js +3 -2
- package/dist/esm/components/ui/Page/PageHeader/PageHeader.js +1 -1
- package/dist/esm/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.js +23 -1
- package/dist/esm/components/widgets/DriverCard/DriverCard.js +1 -1
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +1 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceTable.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/tiptap/slash-mention/SlashSuggestionList.styl.js +2 -2
- package/dist/esm/tiptap/slash-mention/createSlashMentionExtension.js +45 -28
- package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -2
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +3 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.d.ts +3 -3
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.types.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/chatPromptComposerInsert.d.ts +35 -0
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +0 -2
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +3 -0
- package/dist/esm/types/src/components/ui/Page/PageFooter/PageFooter.d.ts +1 -2
- package/dist/esm/types/src/docs/pages/ChatSheetPage.d.ts +1 -0
- package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +7 -2
- package/package.json +1 -1
- package/src/components/ui/Chart/Chart.styl +2 -1
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +8 -1
- package/src/components/ui/Chat/Chat.types.ts +1 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +26 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +86 -65
- package/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.tsx +31 -13
- package/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.types.ts +11 -0
- package/src/components/ui/Chat/ChatPrompt/chatPromptComposerInsert.ts +122 -0
- package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +34 -21
- package/src/components/ui/Chat/index.ts +9 -0
- package/src/components/ui/Logo/Logo.styl +1 -0
- package/src/components/ui/Logo/logo.svg +1 -1
- package/src/components/ui/Page/PageFooter/PageFooter.tsx +2 -3
- package/src/components/ui/Page/PageHeader/PageHeader.tsx +1 -1
- package/src/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.tsx +35 -4
- package/src/components/widgets/DriverCard/DriverCard.tsx +5 -1
- package/src/docs/DocsShell.tsx +1 -6
- package/src/docs/pages/ChatSheetPage.tsx +61 -0
- package/src/docs/pages/ChatSlashCommandsPage.tsx +46 -120
- package/src/docs/pages/TooltipPage.tsx +0 -31
- package/src/docs/registry.ts +6 -0
- package/src/tiptap/slash-mention/SlashSuggestionList.styl +4 -0
- package/src/tiptap/slash-mention/SlashSuggestionList.styl.d.ts +1 -0
- package/src/tiptap/slash-mention/createSlashMentionExtension.ts +46 -27
- package/src/tiptap/slash-mention/types.ts +7 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type KeyboardEvent as ReactKeyboardEvent,
|
|
3
2
|
useCallback,
|
|
4
3
|
useEffect,
|
|
5
4
|
useLayoutEffect,
|
|
@@ -43,7 +42,6 @@ export type UseChatPromptEditorResult = {
|
|
|
43
42
|
editor: Editor | null;
|
|
44
43
|
trimmedMessage: string;
|
|
45
44
|
resetAfterSend: () => void;
|
|
46
|
-
handleComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
47
45
|
};
|
|
48
46
|
|
|
49
47
|
export function useChatPromptEditor({
|
|
@@ -94,7 +92,6 @@ export function useChatPromptEditor({
|
|
|
94
92
|
Placeholder.configure({
|
|
95
93
|
placeholder: placeholderText,
|
|
96
94
|
showOnlyWhenEditable: true,
|
|
97
|
-
showOnlyCurrent: false,
|
|
98
95
|
}),
|
|
99
96
|
];
|
|
100
97
|
if (slashItemsStable.length > 0) {
|
|
@@ -126,6 +123,38 @@ export function useChatPromptEditor({
|
|
|
126
123
|
|
|
127
124
|
const trimmedMessage = plainDraft.trim();
|
|
128
125
|
|
|
126
|
+
const trimmedMessageRef = useRef(trimmedMessage);
|
|
127
|
+
trimmedMessageRef.current = trimmedMessage;
|
|
128
|
+
|
|
129
|
+
const attachmentsCountRef = useRef(attachmentsCount);
|
|
130
|
+
attachmentsCountRef.current = attachmentsCount;
|
|
131
|
+
|
|
132
|
+
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
133
|
+
onEnterSubmitRef.current = onEnterSubmit;
|
|
134
|
+
|
|
135
|
+
const handleEditorKeyDown = useCallback(
|
|
136
|
+
(_view: unknown, event: KeyboardEvent) => {
|
|
137
|
+
if (
|
|
138
|
+
!(
|
|
139
|
+
event.key === 'Enter' &&
|
|
140
|
+
!event.shiftKey &&
|
|
141
|
+
!event.metaKey &&
|
|
142
|
+
!event.ctrlKey
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (slashOpenRef.current) return false;
|
|
148
|
+
if (!trimmedMessageRef.current && attachmentsCountRef.current === 0) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
onEnterSubmitRef.current();
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
[],
|
|
156
|
+
);
|
|
157
|
+
|
|
129
158
|
const editor = useEditor(
|
|
130
159
|
{
|
|
131
160
|
extensions,
|
|
@@ -137,6 +166,7 @@ export function useChatPromptEditor({
|
|
|
137
166
|
spellcheck: 'true',
|
|
138
167
|
'aria-label': ariaLabelComposer,
|
|
139
168
|
},
|
|
169
|
+
handleKeyDown: handleEditorKeyDown,
|
|
140
170
|
},
|
|
141
171
|
onTransaction: ({ editor: ed }) => {
|
|
142
172
|
const dom = chatPromptSafeEditorDom(ed);
|
|
@@ -147,7 +177,7 @@ export function useChatPromptEditor({
|
|
|
147
177
|
},
|
|
148
178
|
onCreate: bindEditorDom,
|
|
149
179
|
},
|
|
150
|
-
[extensions, bindEditorDom, ariaLabelComposer],
|
|
180
|
+
[extensions, bindEditorDom, ariaLabelComposer, handleEditorKeyDown],
|
|
151
181
|
);
|
|
152
182
|
|
|
153
183
|
useEffect(() => {
|
|
@@ -188,9 +218,6 @@ export function useChatPromptEditor({
|
|
|
188
218
|
syncChatPromptComposerHeight(dom, plainDraft);
|
|
189
219
|
}, [editor, plainDraft]);
|
|
190
220
|
|
|
191
|
-
const onEnterSubmitRef = useRef(onEnterSubmit);
|
|
192
|
-
onEnterSubmitRef.current = onEnterSubmit;
|
|
193
|
-
|
|
194
221
|
const resetAfterSend = useCallback(() => {
|
|
195
222
|
if (!editor) return;
|
|
196
223
|
editor.chain().clearContent().focus().run();
|
|
@@ -201,23 +228,9 @@ export function useChatPromptEditor({
|
|
|
201
228
|
setPlainDraft('');
|
|
202
229
|
}, [editor]);
|
|
203
230
|
|
|
204
|
-
const handleComposerKeyDown = useCallback(
|
|
205
|
-
(e: ReactKeyboardEvent) => {
|
|
206
|
-
if (!(e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey))
|
|
207
|
-
return;
|
|
208
|
-
if (!editorDomRef.current?.contains(e.target as Node)) return;
|
|
209
|
-
if (slashOpenRef.current) return;
|
|
210
|
-
if (!trimmedMessage && attachmentsCount === 0) return;
|
|
211
|
-
e.preventDefault();
|
|
212
|
-
onEnterSubmitRef.current();
|
|
213
|
-
},
|
|
214
|
-
[attachmentsCount, trimmedMessage],
|
|
215
|
-
);
|
|
216
|
-
|
|
217
231
|
return {
|
|
218
232
|
editor,
|
|
219
233
|
trimmedMessage,
|
|
220
234
|
resetAfterSend,
|
|
221
|
-
handleComposerKeyDown,
|
|
222
235
|
};
|
|
223
236
|
}
|
|
@@ -27,6 +27,15 @@ export type {
|
|
|
27
27
|
} from './ChatSheet/useChatPanelChromeModel';
|
|
28
28
|
export { ChatMessage } from './ChatMessage';
|
|
29
29
|
export { ChatPrompt } from './ChatPrompt';
|
|
30
|
+
export type { ChatPromptComposerHandle } from './ChatPrompt/ChatPromptComposer';
|
|
31
|
+
export {
|
|
32
|
+
CHAT_PROMPT_COMMAND_CHIP_CLASS,
|
|
33
|
+
chatPromptChipHtml,
|
|
34
|
+
createChatPromptComposerHandle,
|
|
35
|
+
getChatPromptTokenRangeBeforePos,
|
|
36
|
+
insertChatPromptContentAtCaret,
|
|
37
|
+
} from './ChatPrompt/chatPromptComposerInsert';
|
|
38
|
+
export type { ChatPromptComposerInsertOptions } from './ChatPrompt/ChatPromptComposer.types';
|
|
30
39
|
export { ChatPresets } from './ChatPresets';
|
|
31
40
|
export type {
|
|
32
41
|
ChatEmptyStateConfig,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
-
<path fill="
|
|
2
|
+
<path fill="currentColor" d="M13.43 24.023h-2.836v-2.836h2.836zm-2.835-2.844H5.63v-2.74h4.965Zm10.527-2.74h-2.726v2.74h-4.965v-2.74h4.952v-4.965h2.74zm-15.5 0h-2.74v-4.965h2.74ZM2.87 13.462H.034v-2.836H2.87Zm21.096 0H21.13v-2.836h2.836zM5.617 10.635h-2.74V5.669h2.74zm15.505 0h-2.74V5.669h2.74zM10.588 2.927v2.74H5.623v-2.74Zm2.842 0h4.96v2.74h-4.966v-2.74h-2.83V.09h2.836z" style="stroke-width:.13252" />
|
|
3
3
|
</svg>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
|
|
4
|
+
import { Logo } from '#uilib/components/ui/Logo';
|
|
4
5
|
import { GlobeIcon, MailIcon } from 'lucide-react';
|
|
5
6
|
|
|
6
7
|
import S from './PageFooter.styl';
|
|
@@ -14,7 +15,6 @@ export interface PageFooterLinkItem {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface PageFooterProps {
|
|
17
|
-
logo?: ReactNode;
|
|
18
18
|
/** Rendered between logo block and copyright (e.g. debug UI from the app). */
|
|
19
19
|
children?: ReactNode;
|
|
20
20
|
/** When non-empty, version badge links here (e.g. `/releases`). */
|
|
@@ -55,7 +55,6 @@ export function PageFooter({
|
|
|
55
55
|
versionLabel,
|
|
56
56
|
homeTo = '/',
|
|
57
57
|
brandText = 'Sybilion',
|
|
58
|
-
logo,
|
|
59
58
|
websiteHref = 'https://sybilion.com',
|
|
60
59
|
mailHref = 'mailto:support@sybilion.com',
|
|
61
60
|
copyrightText = '© 2026 Sybilion. All rights reserved.',
|
|
@@ -84,7 +83,7 @@ export function PageFooter({
|
|
|
84
83
|
<div className={S.line}>
|
|
85
84
|
<div className={S.logo}>
|
|
86
85
|
<Link to={homeTo}>
|
|
87
|
-
|
|
86
|
+
<Logo />
|
|
88
87
|
{brandText}
|
|
89
88
|
</Link>
|
|
90
89
|
{versionLink !== '' && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useState } from 'react';
|
|
1
|
+
import { CSSProperties, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Tooltip,
|
|
@@ -18,7 +18,9 @@ function TextWithDeferTooltip({
|
|
|
18
18
|
...props
|
|
19
19
|
}: TextWithDeferTooltipProps) {
|
|
20
20
|
const [withTooltip, setWithTooltip] = useState(false);
|
|
21
|
+
const [tooltipStyles, setTooltipStyles] = useState<CSSProperties>({});
|
|
21
22
|
const ref = useRef<HTMLDivElement>(null);
|
|
23
|
+
const cachedFontSizeRef = useRef<string | null>(null);
|
|
22
24
|
|
|
23
25
|
const handleMouseEnter = () => {
|
|
24
26
|
if (!ref.current) return;
|
|
@@ -29,6 +31,31 @@ function TextWithDeferTooltip({
|
|
|
29
31
|
ref.current.scrollHeight - ref.current.clientHeight > 3;
|
|
30
32
|
|
|
31
33
|
if (isOverflowingHorizontally || isOverflowingVertically) {
|
|
34
|
+
const styles: CSSProperties = {
|
|
35
|
+
fontSize: cachedFontSizeRef.current || undefined,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (ref.current) {
|
|
39
|
+
const {
|
|
40
|
+
width: rectWidth,
|
|
41
|
+
left,
|
|
42
|
+
top,
|
|
43
|
+
} = ref.current.getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
styles.width = `${width ?? rectWidth}px`;
|
|
46
|
+
|
|
47
|
+
if (!cachedFontSizeRef.current) {
|
|
48
|
+
const { fontSize } = window.getComputedStyle(ref.current);
|
|
49
|
+
cachedFontSizeRef.current = fontSize;
|
|
50
|
+
styles.fontSize = fontSize;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (overTrigger) {
|
|
54
|
+
styles.transform = `translate(${left}px, ${top}px) !important`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setTooltipStyles(styles);
|
|
32
59
|
setWithTooltip(true);
|
|
33
60
|
}
|
|
34
61
|
};
|
|
@@ -45,14 +72,18 @@ function TextWithDeferTooltip({
|
|
|
45
72
|
);
|
|
46
73
|
|
|
47
74
|
if (withTooltip) {
|
|
75
|
+
const tooltipSide = overTrigger ? 'bottom' : side;
|
|
76
|
+
|
|
48
77
|
return (
|
|
49
78
|
<Tooltip open={withTooltip} onOpenChange={setWithTooltip}>
|
|
50
79
|
<TooltipTrigger asChild>{textElement}</TooltipTrigger>
|
|
51
80
|
<TooltipContent
|
|
52
|
-
side={
|
|
81
|
+
side={tooltipSide}
|
|
82
|
+
style={{
|
|
83
|
+
...(maxWidth !== undefined && { maxWidth: `${maxWidth}px` }),
|
|
84
|
+
...tooltipStyles,
|
|
85
|
+
}}
|
|
53
86
|
overTrigger={overTrigger}
|
|
54
|
-
maxWidth={maxWidth}
|
|
55
|
-
style={width !== undefined ? { width: `${width}px` } : undefined}
|
|
56
87
|
>
|
|
57
88
|
{children}
|
|
58
89
|
</TooltipContent>
|
|
@@ -177,7 +177,11 @@ export function DriverCard({
|
|
|
177
177
|
id={id}
|
|
178
178
|
label={<TooltipTrigger asChild>{nameElem}</TooltipTrigger>}
|
|
179
179
|
/>
|
|
180
|
-
<TooltipContent
|
|
180
|
+
<TooltipContent
|
|
181
|
+
side="left"
|
|
182
|
+
className={S.tooltipContent}
|
|
183
|
+
overTrigger
|
|
184
|
+
>
|
|
181
185
|
<div className={S.tooltipTitle}>{name}</div>
|
|
182
186
|
</TooltipContent>
|
|
183
187
|
</Tooltip>
|
package/src/docs/DocsShell.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
|
2
2
|
|
|
3
3
|
import { AppHeaderHost } from '#uilib/components/ui/AppHeader';
|
|
4
|
-
import { LogoMark } from '#uilib/components/ui/Logo';
|
|
5
4
|
import { AppShell, AppShellMainContent } from '#uilib/components/ui/Page';
|
|
6
5
|
import { PageFooter } from '#uilib/components/ui/Page/PageFooter/PageFooter';
|
|
7
6
|
import { PageScroll } from '#uilib/components/ui/Page/PageScroll/PageScroll';
|
|
@@ -23,11 +22,7 @@ export function DocsShell() {
|
|
|
23
22
|
<AppShellMainContent
|
|
24
23
|
header={<AppHeaderHost />}
|
|
25
24
|
footer={
|
|
26
|
-
<PageFooter
|
|
27
|
-
logo={<LogoMark />}
|
|
28
|
-
versionLink="/releases"
|
|
29
|
-
versionLabel="1.0.0-docs"
|
|
30
|
-
/>
|
|
25
|
+
<PageFooter versionLink="/releases" versionLabel="1.0.0-docs" />
|
|
31
26
|
}
|
|
32
27
|
>
|
|
33
28
|
<SybilionAppHeader
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { SlashCommandItem } from '#uilib/components/ui/Chat';
|
|
2
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
3
|
+
import { MessageSquare } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
6
|
+
import { DOCS_CHAT_USER_KEY } from '../docsConstants';
|
|
7
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
8
|
+
|
|
9
|
+
const DOCS_CHAT_SHEET_SCOPE_ID = `${DOCS_CHAT_USER_KEY}-docs-chat-sheet-portal`;
|
|
10
|
+
|
|
11
|
+
const DOCS_SAMPLE_SLASH_ITEMS: SlashCommandItem[] = [
|
|
12
|
+
{
|
|
13
|
+
id: 'sample-command',
|
|
14
|
+
label: 'sample-command',
|
|
15
|
+
description: 'Demo slash item for portal ChatSheet regression.',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export default function ChatSheetPage() {
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<AppPageHeader
|
|
23
|
+
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat sheet' }]}
|
|
24
|
+
title="Chat sheet (portal)"
|
|
25
|
+
subheader="Same integration as design-demo: ChatSheet in page header actions, chat panel portaled into the shell sidebar slot. No inline ChatChrome on this page."
|
|
26
|
+
actions={
|
|
27
|
+
<DocsHeaderActions
|
|
28
|
+
scopeId={DOCS_CHAT_SHEET_SCOPE_ID}
|
|
29
|
+
slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS}
|
|
30
|
+
triggerLabel={
|
|
31
|
+
<>
|
|
32
|
+
<MessageSquare size={20} />
|
|
33
|
+
AI Assistant
|
|
34
|
+
</>
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
}
|
|
38
|
+
/>
|
|
39
|
+
<PageContentSection>
|
|
40
|
+
<p style={{ marginBottom: 16, fontSize: 14, lineHeight: 1.5 }}>
|
|
41
|
+
Open <strong>AI Assistant</strong> in the header. The composer runs
|
|
42
|
+
inside the portaled chat panel (not inline on this page).
|
|
43
|
+
</p>
|
|
44
|
+
<h3 style={{ marginBottom: 8, fontSize: 14, fontWeight: 600 }}>
|
|
45
|
+
Regression checklist
|
|
46
|
+
</h3>
|
|
47
|
+
<ul
|
|
48
|
+
style={{ margin: 0, paddingLeft: 20, fontSize: 14, lineHeight: 1.6 }}
|
|
49
|
+
>
|
|
50
|
+
<li>Plain Enter with text → submit and clear composer</li>
|
|
51
|
+
<li>
|
|
52
|
+
Plain Enter must not create empty lines with duplicated placeholders
|
|
53
|
+
</li>
|
|
54
|
+
<li>Shift+Enter with text → multiline, no placeholder after text</li>
|
|
55
|
+
<li>Empty Enter → no submit</li>
|
|
56
|
+
<li>Submit button and slash menu still work</li>
|
|
57
|
+
</ul>
|
|
58
|
+
</PageContentSection>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
type SlashCommandItem,
|
|
9
9
|
} from '#uilib/components/ui/Chat';
|
|
10
10
|
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
11
|
-
import type { SlashItemCommandContext } from '#uilib/tiptap/slash-mention/types';
|
|
12
11
|
import { ScrollRef } from '@homecode/ui';
|
|
13
12
|
|
|
14
13
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
@@ -16,65 +15,40 @@ import { DocsHeaderActions } from '../docsHeaderActions';
|
|
|
16
15
|
|
|
17
16
|
const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
/** Sample items — default TipTap mention insert; optional `className` / `color` style the chip. */
|
|
19
|
+
const DOCS_SLASH_ITEMS: SlashCommandItem[] = [
|
|
20
|
+
{
|
|
21
|
+
id: 'generate-dashboard',
|
|
22
|
+
label: 'Generate dashboard',
|
|
23
|
+
description: 'Insert /generate-dashboard',
|
|
24
|
+
color: 'var(--brand-color-600)',
|
|
25
|
+
},
|
|
23
26
|
{
|
|
24
|
-
id:
|
|
25
|
-
label:
|
|
26
|
-
description:
|
|
27
|
-
|
|
27
|
+
id: 'performance-chart',
|
|
28
|
+
label: 'Performance chart (bigger font)',
|
|
29
|
+
description: 'Insert /PerformanceChart',
|
|
30
|
+
className: 'docs-slash-mention-accent',
|
|
31
|
+
color: '#16a34a',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'mention-fallback',
|
|
35
|
+
label: 'Mention fallback',
|
|
36
|
+
description: 'Default mention styling only',
|
|
37
|
+
color: 'var(--foreground)',
|
|
28
38
|
},
|
|
29
39
|
];
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const SAMPLE_COMMAND_SUCCESS_TEXT = '✅ Sample command complete.';
|
|
34
|
-
|
|
35
|
-
function makeMessage(
|
|
36
|
-
role: MessageRole,
|
|
37
|
-
text: string,
|
|
38
|
-
options?: { inProgress?: boolean },
|
|
39
|
-
): Message {
|
|
41
|
+
function makeMessage(role: MessageRole, text: string): Message {
|
|
40
42
|
return {
|
|
41
43
|
id: crypto.randomUUID(),
|
|
42
44
|
role,
|
|
43
45
|
text,
|
|
44
46
|
timestamp: Date.now(),
|
|
45
|
-
...(options?.inProgress ? { inProgress: true } : {}),
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
/** Local-state equivalent of chat-context `updateMessageById`. */
|
|
50
|
-
function updateMessageInPlace(
|
|
51
|
-
messages: Message[],
|
|
52
|
-
messageId: string,
|
|
53
|
-
patch: { role?: MessageRole; text?: string; inProgress?: boolean },
|
|
54
|
-
): Message[] {
|
|
55
|
-
return messages.map(message => {
|
|
56
|
-
if (message.id !== messageId) return message;
|
|
57
|
-
const next: Message = { ...message };
|
|
58
|
-
if (patch.role != null) {
|
|
59
|
-
next.role = patch.role;
|
|
60
|
-
}
|
|
61
|
-
if (patch.text != null) {
|
|
62
|
-
next.text = patch.text;
|
|
63
|
-
}
|
|
64
|
-
if (patch.inProgress === true) {
|
|
65
|
-
next.inProgress = true;
|
|
66
|
-
} else if (
|
|
67
|
-
patch.inProgress === false ||
|
|
68
|
-
(patch.role != null && patch.role !== MessageRole.SYSTEM)
|
|
69
|
-
) {
|
|
70
|
-
delete next.inProgress;
|
|
71
|
-
}
|
|
72
|
-
return next;
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
50
|
const ASSISTANT_REPLY_TEXT =
|
|
77
|
-
'Demo reply
|
|
51
|
+
'Demo reply. Slash picks insert inline mention chips; plain text on send uses each item id (e.g. /generate-dashboard).';
|
|
78
52
|
|
|
79
53
|
export default function ChatSlashCommandsPage() {
|
|
80
54
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
@@ -95,84 +69,33 @@ export default function ChatSlashCommandsPage() {
|
|
|
95
69
|
messages.length > 0 &&
|
|
96
70
|
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
97
71
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
72
|
+
const onSubmit = useCallback((raw: string) => {
|
|
73
|
+
const text = raw.trim();
|
|
74
|
+
if (!text) return;
|
|
75
|
+
|
|
76
|
+
setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
|
|
100
77
|
setIsLoading(true);
|
|
101
|
-
setMessages(prev => [
|
|
102
|
-
...prev,
|
|
103
|
-
{
|
|
104
|
-
id: progressId,
|
|
105
|
-
role: MessageRole.SYSTEM,
|
|
106
|
-
text: SAMPLE_COMMAND_PROGRESS_TEXT,
|
|
107
|
-
timestamp: Date.now(),
|
|
108
|
-
inProgress: true,
|
|
109
|
-
},
|
|
110
|
-
]);
|
|
111
78
|
|
|
112
79
|
if (replyTimeoutRef.current != null) {
|
|
113
80
|
clearTimeout(replyTimeoutRef.current);
|
|
114
81
|
}
|
|
115
82
|
replyTimeoutRef.current = setTimeout(() => {
|
|
116
83
|
replyTimeoutRef.current = null;
|
|
117
|
-
setMessages(prev =>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
inProgress: false,
|
|
122
|
-
}),
|
|
123
|
-
);
|
|
84
|
+
setMessages(prev => [
|
|
85
|
+
...prev,
|
|
86
|
+
makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY_TEXT),
|
|
87
|
+
]);
|
|
124
88
|
setIsLoading(false);
|
|
125
|
-
},
|
|
89
|
+
}, 900);
|
|
126
90
|
}, []);
|
|
127
91
|
|
|
128
|
-
const onSlashItemCommand = useCallback(
|
|
129
|
-
({ item }: SlashItemCommandContext) => {
|
|
130
|
-
if (item.id !== DOCS_SAMPLE_COMMAND_ID) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
queueMicrotask(() => runSampleCommand());
|
|
134
|
-
return true;
|
|
135
|
-
},
|
|
136
|
-
[runSampleCommand],
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const onSubmit = useCallback(
|
|
140
|
-
(raw: string) => {
|
|
141
|
-
const text = raw.trim();
|
|
142
|
-
if (!text) return;
|
|
143
|
-
|
|
144
|
-
if (text === `/${DOCS_SAMPLE_COMMAND_ID}`) {
|
|
145
|
-
runSampleCommand();
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
|
|
150
|
-
setIsLoading(true);
|
|
151
|
-
|
|
152
|
-
if (replyTimeoutRef.current != null) {
|
|
153
|
-
clearTimeout(replyTimeoutRef.current);
|
|
154
|
-
}
|
|
155
|
-
replyTimeoutRef.current = setTimeout(() => {
|
|
156
|
-
replyTimeoutRef.current = null;
|
|
157
|
-
setMessages(prev => [
|
|
158
|
-
...prev,
|
|
159
|
-
makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY_TEXT),
|
|
160
|
-
]);
|
|
161
|
-
setIsLoading(false);
|
|
162
|
-
}, 900);
|
|
163
|
-
},
|
|
164
|
-
[runSampleCommand],
|
|
165
|
-
);
|
|
166
|
-
|
|
167
92
|
return (
|
|
168
93
|
<>
|
|
169
94
|
<AppPageHeader
|
|
170
95
|
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat slash commands' }]}
|
|
171
96
|
title="Chat slash commands"
|
|
172
|
-
subheader={`Slash palette uses TipTap Mention with "/" as the trigger. Pass slashCommandItems from the app
|
|
173
|
-
actions={
|
|
174
|
-
<DocsHeaderActions slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS} />
|
|
175
|
-
}
|
|
97
|
+
subheader={`Slash palette uses TipTap Mention with "/" as the trigger. Pass slashCommandItems from the app — each item inserts an inline mention chip by default. Optional className and color on SlashCommandItem style the chip in the composer.`}
|
|
98
|
+
actions={<DocsHeaderActions slashCommandItems={DOCS_SLASH_ITEMS} />}
|
|
176
99
|
/>
|
|
177
100
|
<PageContentSection>
|
|
178
101
|
<p style={{ marginBottom: 16, fontSize: 14, lineHeight: 1.5 }}>
|
|
@@ -180,13 +103,17 @@ export default function ChatSlashCommandsPage() {
|
|
|
180
103
|
<Link className="underline underline-offset-2" to="/docs/chat">
|
|
181
104
|
Chat
|
|
182
105
|
</Link>{' '}
|
|
183
|
-
shell below
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
or send <kbd className="font-mono">/sample-command</kbd> with Enter.
|
|
106
|
+
shell below. Type <kbd className="font-mono">/</kbd> at line start or
|
|
107
|
+
after a space, then pick a command — the composer inserts a styled
|
|
108
|
+
mention chip. Items can set <kbd className="font-mono">className</kbd>{' '}
|
|
109
|
+
and <kbd className="font-mono">color</kbd> on{' '}
|
|
110
|
+
<kbd className="font-mono">SlashCommandItem</kbd>.
|
|
189
111
|
</p>
|
|
112
|
+
<style>{`
|
|
113
|
+
.docs-slash-mention-accent {
|
|
114
|
+
font-size: 150%;
|
|
115
|
+
}
|
|
116
|
+
`}</style>
|
|
190
117
|
<ChatChrome
|
|
191
118
|
showResizeHandle={false}
|
|
192
119
|
resizeHandle={undefined}
|
|
@@ -207,13 +134,12 @@ export default function ChatSlashCommandsPage() {
|
|
|
207
134
|
effectiveScopeId="docs-chat-slash-inline"
|
|
208
135
|
onPromptSubmit={onSubmit}
|
|
209
136
|
onChatDeleted={() => {}}
|
|
210
|
-
slashCommandItems={
|
|
211
|
-
onSlashItemCommand={onSlashItemCommand}
|
|
137
|
+
slashCommandItems={DOCS_SLASH_ITEMS}
|
|
212
138
|
promptPlaceholder='Ask something or type "/" for demo commands…'
|
|
213
139
|
emptyState={{
|
|
214
|
-
title: 'Try
|
|
140
|
+
title: 'Try slash commands',
|
|
215
141
|
description:
|
|
216
|
-
'Pick
|
|
142
|
+
'Pick a command from the palette to insert a mention chip with optional className and color.',
|
|
217
143
|
}}
|
|
218
144
|
/>
|
|
219
145
|
</PageContentSection>
|
|
@@ -11,9 +11,6 @@ import { DocsHeaderActions } from '../docsHeaderActions';
|
|
|
11
11
|
|
|
12
12
|
const TOOLTIP_SIDES = ['left', 'top', 'bottom', 'right'] as const;
|
|
13
13
|
|
|
14
|
-
const OVER_TRIGGER_TEXT =
|
|
15
|
-
'Actual Order volume for Baerlocher MB 301, a compound that includes stearin as a component. MB 301 is typically used in construction-grade applications.';
|
|
16
|
-
|
|
17
14
|
export default function TooltipPage() {
|
|
18
15
|
return (
|
|
19
16
|
<>
|
|
@@ -37,34 +34,6 @@ export default function TooltipPage() {
|
|
|
37
34
|
))}
|
|
38
35
|
</div>
|
|
39
36
|
</PageContentSection>
|
|
40
|
-
<PageContentSection>
|
|
41
|
-
<p style={{ margin: '0 0 8px', fontWeight: 600 }}>Over trigger</p>
|
|
42
|
-
<div
|
|
43
|
-
style={{
|
|
44
|
-
maxWidth: 500,
|
|
45
|
-
border: '1px dashed var(--border)',
|
|
46
|
-
padding: 8,
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<Tooltip>
|
|
50
|
-
<TooltipTrigger asChild>
|
|
51
|
-
<div
|
|
52
|
-
style={{
|
|
53
|
-
overflow: 'hidden',
|
|
54
|
-
textOverflow: 'ellipsis',
|
|
55
|
-
whiteSpace: 'nowrap',
|
|
56
|
-
fontWeight: 600,
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{OVER_TRIGGER_TEXT}
|
|
60
|
-
</div>
|
|
61
|
-
</TooltipTrigger>
|
|
62
|
-
<TooltipContent overTrigger maxWidth={400}>
|
|
63
|
-
{OVER_TRIGGER_TEXT}
|
|
64
|
-
</TooltipContent>
|
|
65
|
-
</Tooltip>
|
|
66
|
-
</div>
|
|
67
|
-
</PageContentSection>
|
|
68
37
|
</>
|
|
69
38
|
);
|
|
70
39
|
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -115,6 +115,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
115
115
|
section: 'Chat',
|
|
116
116
|
load: () => import('./pages/ChatSlashCommandsPage'),
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
slug: 'chat-sheet',
|
|
120
|
+
title: 'Chat sheet (portal)',
|
|
121
|
+
section: 'Chat',
|
|
122
|
+
load: () => import('./pages/ChatSheetPage'),
|
|
123
|
+
},
|
|
118
124
|
{
|
|
119
125
|
slug: 'chat-user-csv-attachment',
|
|
120
126
|
title: 'Chat user CSV attachment',
|