@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,6 +1,7 @@
|
|
|
1
|
-
import { type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
1
|
import type { Editor } from '@tiptap/core';
|
|
3
2
|
import type { ChatAttachmentDropItem } from '../Chat.types';
|
|
3
|
+
import { type ChatPromptComposerHandle } from './chatPromptComposerInsert';
|
|
4
|
+
export type { ChatPromptComposerHandle, ChatPromptComposerInsertOptions, } from './ChatPromptComposer.types';
|
|
4
5
|
export type ChatPromptComposerProps = {
|
|
5
6
|
editor: Editor;
|
|
6
7
|
disabled: boolean;
|
|
@@ -8,6 +9,5 @@ export type ChatPromptComposerProps = {
|
|
|
8
9
|
attachments: ChatAttachmentDropItem[];
|
|
9
10
|
attachmentAccept?: string;
|
|
10
11
|
onAttachmentFiles?: (files: File[]) => void;
|
|
11
|
-
onComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
12
12
|
};
|
|
13
|
-
export declare
|
|
13
|
+
export declare const ChatPromptComposer: import("react").ForwardRefExoticComponent<ChatPromptComposerProps & import("react").RefAttributes<ChatPromptComposerHandle>>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type { ChatPromptComposerHandle, ChatPromptComposerInsertOptions, } from './chatPromptComposerInsert';
|
|
2
|
+
export { CHAT_PROMPT_COMMAND_CHIP_CLASS, chatPromptChipHtml, createChatPromptComposerHandle, getChatPromptTokenRangeBeforePos, insertChatPromptContentAtCaret, } from './chatPromptComposerInsert';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Editor, JSONContent } from '@tiptap/core';
|
|
2
|
+
export type ChatPromptComposerInsertOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Doc range to replace before insert (e.g. slash suggestion `range` covering `/query`).
|
|
5
|
+
* When set, `replaceTriggerToken` is ignored.
|
|
6
|
+
*/
|
|
7
|
+
replaceRange?: {
|
|
8
|
+
from: number;
|
|
9
|
+
to: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* When true and no `replaceRange`, delete text from caret back to the preceding
|
|
13
|
+
* space or start of line before insert. Default true.
|
|
14
|
+
*/
|
|
15
|
+
replaceTriggerToken?: boolean;
|
|
16
|
+
/** Append a space after inserted content so the user can keep typing. Default true. */
|
|
17
|
+
trailingSpace?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Global class for inline command chips inserted via `insertAtCaret`. */
|
|
20
|
+
export declare const CHAT_PROMPT_COMMAND_CHIP_CLASS = "chat-prompt-command-chip";
|
|
21
|
+
export type ChatPromptComposerHandle = {
|
|
22
|
+
/** Insert HTML string or TipTap JSON at the caret (or after clearing trigger text). */
|
|
23
|
+
insertAtCaret: (content: string | JSONContent, options?: ChatPromptComposerInsertOptions) => void;
|
|
24
|
+
focus: () => void;
|
|
25
|
+
getEditor: () => Editor;
|
|
26
|
+
};
|
|
27
|
+
/** Range of the unfinished token immediately before `pos` (back to space or line start). */
|
|
28
|
+
export declare function getChatPromptTokenRangeBeforePos(editor: Editor, pos: number): {
|
|
29
|
+
from: number;
|
|
30
|
+
to: number;
|
|
31
|
+
};
|
|
32
|
+
/** Build inline chip HTML for `insertAtCaret` (uses global `CHAT_PROMPT_COMMAND_CHIP_CLASS`). */
|
|
33
|
+
export declare function chatPromptChipHtml(text: string, className?: string): string;
|
|
34
|
+
export declare function insertChatPromptContentAtCaret(editor: Editor, content: string | JSONContent, options?: ChatPromptComposerInsertOptions): void;
|
|
35
|
+
export declare function createChatPromptComposerHandle(editor: Editor): ChatPromptComposerHandle;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
1
|
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
3
2
|
import type { Editor } from '@tiptap/core';
|
|
4
3
|
export type UseChatPromptEditorOptions = {
|
|
@@ -16,6 +15,5 @@ export type UseChatPromptEditorResult = {
|
|
|
16
15
|
editor: Editor | null;
|
|
17
16
|
trimmedMessage: string;
|
|
18
17
|
resetAfterSend: () => void;
|
|
19
|
-
handleComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
20
18
|
};
|
|
21
19
|
export declare function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlashItemCommand, prefillMessage, attachmentsCount, onEnterSubmit, }: UseChatPromptEditorOptions): UseChatPromptEditorResult;
|
|
@@ -11,6 +11,9 @@ export type { ChatSheetActions, ChatSheetProps } from './ChatSheet/ChatSheet';
|
|
|
11
11
|
export type { UseChatPanelChromeModelInput, UseChatPanelChromeModelResult, } from './ChatSheet/useChatPanelChromeModel';
|
|
12
12
|
export { ChatMessage } from './ChatMessage';
|
|
13
13
|
export { ChatPrompt } from './ChatPrompt';
|
|
14
|
+
export type { ChatPromptComposerHandle } from './ChatPrompt/ChatPromptComposer';
|
|
15
|
+
export { CHAT_PROMPT_COMMAND_CHIP_CLASS, chatPromptChipHtml, createChatPromptComposerHandle, getChatPromptTokenRangeBeforePos, insertChatPromptContentAtCaret, } from './ChatPrompt/chatPromptComposerInsert';
|
|
16
|
+
export type { ChatPromptComposerInsertOptions } from './ChatPrompt/ChatPromptComposer.types';
|
|
14
17
|
export { ChatPresets } from './ChatPresets';
|
|
15
18
|
export type { ChatEmptyStateConfig, ChatEmptyStateContext, ChatEmptyStateProps, } from './ChatEmptyState/ChatEmptyState.types';
|
|
16
19
|
export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
|
|
@@ -7,7 +7,6 @@ export interface PageFooterLinkItem {
|
|
|
7
7
|
ariaLabel?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface PageFooterProps {
|
|
10
|
-
logo?: ReactNode;
|
|
11
10
|
/** Rendered between logo block and copyright (e.g. debug UI from the app). */
|
|
12
11
|
children?: ReactNode;
|
|
13
12
|
/** When non-empty, version badge links here (e.g. `/releases`). */
|
|
@@ -25,4 +24,4 @@ export interface PageFooterProps {
|
|
|
25
24
|
*/
|
|
26
25
|
links?: PageFooterLinkItem[];
|
|
27
26
|
}
|
|
28
|
-
export declare function PageFooter({ children, versionLink, versionLabel, homeTo, brandText,
|
|
27
|
+
export declare function PageFooter({ children, versionLink, versionLabel, homeTo, brandText, websiteHref, mailHref, copyrightText, links: linksProp, }: PageFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatSheetPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,6 +3,10 @@ export type SlashCommandItem = {
|
|
|
3
3
|
id: string;
|
|
4
4
|
label: string;
|
|
5
5
|
description?: string;
|
|
6
|
+
/** Extra class on the inserted mention chip (merged with `slash-mention`). */
|
|
7
|
+
className?: string;
|
|
8
|
+
/** CSS color for chip text; background is 30% of this color mixed with transparent. */
|
|
9
|
+
color?: string;
|
|
6
10
|
};
|
|
7
11
|
export type SlashItemCommandContext = {
|
|
8
12
|
item: SlashCommandItem;
|
|
@@ -11,8 +15,9 @@ export type SlashItemCommandContext = {
|
|
|
11
15
|
range?: Range;
|
|
12
16
|
};
|
|
13
17
|
/**
|
|
14
|
-
* If provided, run when a slash item is picked. Return true to skip mention insert
|
|
15
|
-
*
|
|
18
|
+
* If provided, run when a slash item is picked. Return true to skip default mention insert
|
|
19
|
+
* and handle the composer yourself (e.g. `createChatPromptComposerHandle(editor).insertAtCaret`
|
|
20
|
+
* with `{ replaceRange: range }` to replace `/query` with a custom chip).
|
|
16
21
|
*/
|
|
17
22
|
export type SlashOnItemCommand = (ctx: SlashItemCommandContext) => boolean;
|
|
18
23
|
/** Where the slash palette opens relative to the caret. */
|
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
border 1px solid var(--border) / 0.5
|
|
92
92
|
// background-color var(--background)
|
|
93
93
|
backdrop-filter blur(10px)
|
|
94
|
-
background-color unquote('color-mix(in srgb, var(--background)
|
|
94
|
+
background-color unquote('color-mix(in srgb, var(--background) 70%, transparent)')
|
|
95
95
|
font-size 0.75rem /* text-xs */
|
|
96
96
|
line-height 1rem
|
|
97
97
|
box-shadow 0 10px 10px -5px rgba(0 0 0 0.2), 0 0 1px 0 var(--muted-foreground)
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
transition opacity 0.5s ease-out
|
|
100
100
|
|
|
101
101
|
:global(.dark) &
|
|
102
|
+
background-color unquote('color-mix(in srgb, var(--background) 50%, transparent)')
|
|
102
103
|
box-shadow 0 0 1px 0 var(--sb-slate-700), 0 10px 10px -5px rgba(0 0 0 0.7)
|
|
103
104
|
|
|
104
105
|
.chartContainer:hover &
|
|
@@ -103,8 +103,15 @@ export function ChartAreaInteractive({
|
|
|
103
103
|
const raw = selectedAnalysisId ?? selectedForecast?.id ?? null;
|
|
104
104
|
const anchorId =
|
|
105
105
|
raw == null ? null : typeof raw === 'number' ? raw : Number(raw);
|
|
106
|
+
const anchorForecastLoaded =
|
|
107
|
+
anchorId != null &&
|
|
108
|
+
Number.isFinite(anchorId) &&
|
|
109
|
+
chartData.some(row => {
|
|
110
|
+
const value = row[`forecast_${anchorId}`];
|
|
111
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
112
|
+
});
|
|
106
113
|
const opts =
|
|
107
|
-
anchorId != null && Number.isFinite(anchorId)
|
|
114
|
+
anchorId != null && Number.isFinite(anchorId) && anchorForecastLoaded
|
|
108
115
|
? { endDateAnchorAnalysisId: anchorId }
|
|
109
116
|
: undefined;
|
|
110
117
|
return filterDataForTimeRange(chartData, timeRange, opts);
|
|
@@ -98,7 +98,7 @@ export interface ChatPromptProps {
|
|
|
98
98
|
onAttachmentFiles?: (files: File[]) => void;
|
|
99
99
|
/** Slash menu (`/`); omit or pass empty to disable Mention trigger (no default items). */
|
|
100
100
|
slashCommandItems?: SlashCommandItem[];
|
|
101
|
-
/** Custom slash pick handler; return true to skip default mention insert. */
|
|
101
|
+
/** Custom slash pick handler; return true to skip default mention insert and insert via composer API. */
|
|
102
102
|
onSlashItemCommand?: SlashOnItemCommand;
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -62,13 +62,38 @@ INPUT_MAX_HEIGHT = 200px
|
|
|
62
62
|
overflow-y auto !important
|
|
63
63
|
overflow-x hidden !important
|
|
64
64
|
|
|
65
|
-
& :global(.ProseMirror p.is-empty::before)
|
|
65
|
+
& :global(.ProseMirror p.is-empty.is-editor-empty::before)
|
|
66
66
|
color var(--muted-foreground)
|
|
67
67
|
content attr(data-placeholder)
|
|
68
68
|
float left
|
|
69
69
|
height 0
|
|
70
70
|
pointer-events none
|
|
71
71
|
|
|
72
|
+
& :global(.ProseMirror .slash-mention)
|
|
73
|
+
display inline-flex
|
|
74
|
+
align-items center
|
|
75
|
+
padding 0 var(--p-1)
|
|
76
|
+
border-radius var(--radius-sm)
|
|
77
|
+
background var(--muted)
|
|
78
|
+
font-size 0.875em
|
|
79
|
+
line-height 1.4
|
|
80
|
+
white-space nowrap
|
|
81
|
+
user-select none
|
|
82
|
+
vertical-align baseline
|
|
83
|
+
|
|
84
|
+
& :global(.ProseMirror .chat-prompt-command-chip)
|
|
85
|
+
display inline-flex
|
|
86
|
+
align-items center
|
|
87
|
+
padding 0 var(--p-1)
|
|
88
|
+
border-radius var(--radius-sm)
|
|
89
|
+
background var(--muted)
|
|
90
|
+
color var(--foreground)
|
|
91
|
+
font-size 0.875em
|
|
92
|
+
line-height 1.4
|
|
93
|
+
white-space nowrap
|
|
94
|
+
user-select none
|
|
95
|
+
vertical-align baseline
|
|
96
|
+
|
|
72
97
|
.submitColumn
|
|
73
98
|
display flex
|
|
74
99
|
flex-direction column
|
|
@@ -1,31 +1,46 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
FormEvent,
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
useRef,
|
|
8
|
+
} from 'react';
|
|
3
9
|
|
|
4
10
|
import type { ChatPromptProps } from '../Chat.types';
|
|
5
11
|
import S from './ChatPrompt.styl';
|
|
6
12
|
import { ChatPromptAttachments } from './ChatPromptAttachments';
|
|
7
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ChatPromptComposer,
|
|
15
|
+
type ChatPromptComposerHandle,
|
|
16
|
+
} from './ChatPromptComposer';
|
|
17
|
+
import { createChatPromptComposerHandle } from './chatPromptComposerInsert';
|
|
8
18
|
import { useChatPromptEditor } from './useChatPromptEditor';
|
|
9
19
|
|
|
10
|
-
export
|
|
11
|
-
onSubmit,
|
|
12
|
-
placeholder,
|
|
13
|
-
className,
|
|
14
|
-
footer,
|
|
15
|
-
prefillMessage,
|
|
16
|
-
slashCommandItems,
|
|
17
|
-
onSlashItemCommand,
|
|
18
|
-
attachments = [],
|
|
19
|
-
onRemoveAttachment,
|
|
20
|
-
disabled = false,
|
|
21
|
-
attachmentAccept,
|
|
22
|
-
onAttachmentFiles,
|
|
23
|
-
}: ChatPromptProps) {
|
|
24
|
-
const attachmentsCount = attachments.length;
|
|
20
|
+
export type { ChatPromptComposerHandle } from './ChatPromptComposer';
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
export const ChatPrompt = forwardRef<ChatPromptComposerHandle, ChatPromptProps>(
|
|
23
|
+
function ChatPrompt(
|
|
24
|
+
{
|
|
25
|
+
onSubmit,
|
|
26
|
+
placeholder,
|
|
27
|
+
className,
|
|
28
|
+
footer,
|
|
29
|
+
prefillMessage,
|
|
30
|
+
slashCommandItems,
|
|
31
|
+
onSlashItemCommand,
|
|
32
|
+
attachments = [],
|
|
33
|
+
onRemoveAttachment,
|
|
34
|
+
disabled = false,
|
|
35
|
+
attachmentAccept,
|
|
36
|
+
onAttachmentFiles,
|
|
37
|
+
},
|
|
38
|
+
ref,
|
|
39
|
+
) {
|
|
40
|
+
const attachmentsCount = attachments.length;
|
|
41
|
+
|
|
42
|
+
const emitSubmitRef = useRef(() => {});
|
|
43
|
+
const { editor, trimmedMessage, resetAfterSend } = useChatPromptEditor({
|
|
29
44
|
disabled,
|
|
30
45
|
placeholder,
|
|
31
46
|
slashCommandItems,
|
|
@@ -35,53 +50,59 @@ export function ChatPrompt({
|
|
|
35
50
|
onEnterSubmit: () => emitSubmitRef.current(),
|
|
36
51
|
});
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
useImperativeHandle(
|
|
54
|
+
ref,
|
|
55
|
+
() => (editor ? createChatPromptComposerHandle(editor) : null!),
|
|
56
|
+
[editor],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const emitSubmitAndClear = useCallback(() => {
|
|
60
|
+
if (!editor) return;
|
|
61
|
+
if (!trimmedMessage && attachmentsCount === 0) return;
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
const msg = trimmedMessage;
|
|
64
|
+
resetAfterSend();
|
|
65
|
+
onSubmit(msg, attachmentsCount > 0 ? attachments : undefined);
|
|
66
|
+
}, [
|
|
67
|
+
attachments,
|
|
68
|
+
attachmentsCount,
|
|
69
|
+
editor,
|
|
70
|
+
onSubmit,
|
|
71
|
+
resetAfterSend,
|
|
72
|
+
trimmedMessage,
|
|
73
|
+
]);
|
|
53
74
|
|
|
54
|
-
|
|
75
|
+
emitSubmitRef.current = emitSubmitAndClear;
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
const handleSubmitForm = useCallback(
|
|
78
|
+
(e?: FormEvent) => {
|
|
79
|
+
e?.preventDefault();
|
|
80
|
+
emitSubmitAndClear();
|
|
81
|
+
},
|
|
82
|
+
[emitSubmitAndClear],
|
|
83
|
+
);
|
|
63
84
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
if (!editor) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
67
88
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
return (
|
|
90
|
+
<form onSubmit={handleSubmitForm} className={cn(S.root, className)}>
|
|
91
|
+
<ChatPromptAttachments
|
|
92
|
+
attachments={attachments}
|
|
93
|
+
onRemove={index => onRemoveAttachment?.(index)}
|
|
94
|
+
disabled={disabled}
|
|
95
|
+
/>
|
|
96
|
+
<ChatPromptComposer
|
|
97
|
+
editor={editor}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
trimmedMessage={trimmedMessage}
|
|
100
|
+
attachments={attachments}
|
|
101
|
+
attachmentAccept={attachmentAccept}
|
|
102
|
+
onAttachmentFiles={onAttachmentFiles}
|
|
103
|
+
/>
|
|
104
|
+
{footer}
|
|
105
|
+
</form>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ChangeEvent,
|
|
3
|
-
|
|
3
|
+
forwardRef,
|
|
4
4
|
useCallback,
|
|
5
|
+
useImperativeHandle,
|
|
5
6
|
useRef,
|
|
6
7
|
} from 'react';
|
|
7
8
|
|
|
@@ -12,6 +13,15 @@ import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
|
|
|
12
13
|
import { Button } from '../../Button';
|
|
13
14
|
import type { ChatAttachmentDropItem } from '../Chat.types';
|
|
14
15
|
import S from './ChatPrompt.styl';
|
|
16
|
+
import {
|
|
17
|
+
type ChatPromptComposerHandle,
|
|
18
|
+
createChatPromptComposerHandle,
|
|
19
|
+
} from './chatPromptComposerInsert';
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
ChatPromptComposerHandle,
|
|
23
|
+
ChatPromptComposerInsertOptions,
|
|
24
|
+
} from './ChatPromptComposer.types';
|
|
15
25
|
|
|
16
26
|
export type ChatPromptComposerProps = {
|
|
17
27
|
editor: Editor;
|
|
@@ -20,21 +30,29 @@ export type ChatPromptComposerProps = {
|
|
|
20
30
|
attachments: ChatAttachmentDropItem[];
|
|
21
31
|
attachmentAccept?: string;
|
|
22
32
|
onAttachmentFiles?: (files: File[]) => void;
|
|
23
|
-
onComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
24
33
|
};
|
|
25
34
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
export const ChatPromptComposer = forwardRef<
|
|
36
|
+
ChatPromptComposerHandle,
|
|
37
|
+
ChatPromptComposerProps
|
|
38
|
+
>(function ChatPromptComposer(
|
|
39
|
+
{
|
|
40
|
+
editor,
|
|
41
|
+
disabled,
|
|
42
|
+
trimmedMessage,
|
|
43
|
+
attachments,
|
|
44
|
+
attachmentAccept,
|
|
45
|
+
onAttachmentFiles,
|
|
46
|
+
},
|
|
47
|
+
ref,
|
|
48
|
+
) {
|
|
35
49
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
36
50
|
const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
|
|
37
51
|
|
|
52
|
+
useImperativeHandle(ref, () => createChatPromptComposerHandle(editor), [
|
|
53
|
+
editor,
|
|
54
|
+
]);
|
|
55
|
+
|
|
38
56
|
const handleFileInputChange = useCallback(
|
|
39
57
|
(e: ChangeEvent<HTMLInputElement>) => {
|
|
40
58
|
const files = Array.from(e.target.files ?? []);
|
|
@@ -79,7 +97,7 @@ export function ChatPromptComposer({
|
|
|
79
97
|
</>
|
|
80
98
|
) : null}
|
|
81
99
|
|
|
82
|
-
<div className={S.editorWrap}
|
|
100
|
+
<div className={S.editorWrap}>
|
|
83
101
|
<EditorContent editor={editor} className={S.editorMount} />
|
|
84
102
|
</div>
|
|
85
103
|
|
|
@@ -95,4 +113,4 @@ export function ChatPromptComposer({
|
|
|
95
113
|
</div>
|
|
96
114
|
</div>
|
|
97
115
|
);
|
|
98
|
-
}
|
|
116
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ChatPromptComposerHandle,
|
|
3
|
+
ChatPromptComposerInsertOptions,
|
|
4
|
+
} from './chatPromptComposerInsert';
|
|
5
|
+
export {
|
|
6
|
+
CHAT_PROMPT_COMMAND_CHIP_CLASS,
|
|
7
|
+
chatPromptChipHtml,
|
|
8
|
+
createChatPromptComposerHandle,
|
|
9
|
+
getChatPromptTokenRangeBeforePos,
|
|
10
|
+
insertChatPromptContentAtCaret,
|
|
11
|
+
} from './chatPromptComposerInsert';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Editor, JSONContent } from '@tiptap/core';
|
|
2
|
+
|
|
3
|
+
export type ChatPromptComposerInsertOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Doc range to replace before insert (e.g. slash suggestion `range` covering `/query`).
|
|
6
|
+
* When set, `replaceTriggerToken` is ignored.
|
|
7
|
+
*/
|
|
8
|
+
replaceRange?: { from: number; to: number };
|
|
9
|
+
/**
|
|
10
|
+
* When true and no `replaceRange`, delete text from caret back to the preceding
|
|
11
|
+
* space or start of line before insert. Default true.
|
|
12
|
+
*/
|
|
13
|
+
replaceTriggerToken?: boolean;
|
|
14
|
+
/** Append a space after inserted content so the user can keep typing. Default true. */
|
|
15
|
+
trailingSpace?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Global class for inline command chips inserted via `insertAtCaret`. */
|
|
19
|
+
export const CHAT_PROMPT_COMMAND_CHIP_CLASS = 'chat-prompt-command-chip';
|
|
20
|
+
|
|
21
|
+
export type ChatPromptComposerHandle = {
|
|
22
|
+
/** Insert HTML string or TipTap JSON at the caret (or after clearing trigger text). */
|
|
23
|
+
insertAtCaret: (
|
|
24
|
+
content: string | JSONContent,
|
|
25
|
+
options?: ChatPromptComposerInsertOptions,
|
|
26
|
+
) => void;
|
|
27
|
+
focus: () => void;
|
|
28
|
+
getEditor: () => Editor;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Range of the unfinished token immediately before `pos` (back to space or line start). */
|
|
32
|
+
export function getChatPromptTokenRangeBeforePos(
|
|
33
|
+
editor: Editor,
|
|
34
|
+
pos: number,
|
|
35
|
+
): { from: number; to: number } {
|
|
36
|
+
const $pos = editor.state.doc.resolve(pos);
|
|
37
|
+
const textBefore = $pos.parent.textBetween(
|
|
38
|
+
0,
|
|
39
|
+
$pos.parentOffset,
|
|
40
|
+
undefined,
|
|
41
|
+
'\ufffc',
|
|
42
|
+
);
|
|
43
|
+
const token = textBefore.match(/[^\s]*$/)?.[0] ?? '';
|
|
44
|
+
return { from: pos - token.length, to: pos };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function escapeHtml(text: string): string {
|
|
48
|
+
return text
|
|
49
|
+
.replaceAll('&', '&')
|
|
50
|
+
.replaceAll('<', '<')
|
|
51
|
+
.replaceAll('>', '>')
|
|
52
|
+
.replaceAll('"', '"');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Build inline chip HTML for `insertAtCaret` (uses global `CHAT_PROMPT_COMMAND_CHIP_CLASS`). */
|
|
56
|
+
export function chatPromptChipHtml(
|
|
57
|
+
text: string,
|
|
58
|
+
className: string = CHAT_PROMPT_COMMAND_CHIP_CLASS,
|
|
59
|
+
): string {
|
|
60
|
+
const cls = className ? ` class="${className}"` : '';
|
|
61
|
+
return `<span${cls} data-chat-prompt-chip="true">${escapeHtml(text)}</span>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function insertChatPromptContentAtCaret(
|
|
65
|
+
editor: Editor,
|
|
66
|
+
content: string | JSONContent,
|
|
67
|
+
options?: ChatPromptComposerInsertOptions,
|
|
68
|
+
): void {
|
|
69
|
+
if (editor.isDestroyed) return;
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
replaceRange,
|
|
73
|
+
replaceTriggerToken = true,
|
|
74
|
+
trailingSpace = true,
|
|
75
|
+
} = options ?? {};
|
|
76
|
+
|
|
77
|
+
const { from: selFrom, to: selTo } = editor.state.selection;
|
|
78
|
+
let from: number;
|
|
79
|
+
let to: number;
|
|
80
|
+
|
|
81
|
+
if (replaceRange) {
|
|
82
|
+
from = replaceRange.from;
|
|
83
|
+
to = replaceRange.to;
|
|
84
|
+
} else if (replaceTriggerToken) {
|
|
85
|
+
({ from, to } = getChatPromptTokenRangeBeforePos(editor, selFrom));
|
|
86
|
+
} else {
|
|
87
|
+
from = selFrom;
|
|
88
|
+
to = selTo;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const chain = editor.chain().focus().deleteRange({ from, to });
|
|
92
|
+
|
|
93
|
+
if (typeof content === 'string') {
|
|
94
|
+
chain.insertContentAt(from, trailingSpace ? `${content} ` : content);
|
|
95
|
+
} else {
|
|
96
|
+
chain.insertContentAt(from, content);
|
|
97
|
+
if (trailingSpace) {
|
|
98
|
+
chain.insertContent(' ');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
chain.run();
|
|
103
|
+
|
|
104
|
+
queueMicrotask(() => {
|
|
105
|
+
editor.view.dom.ownerDocument?.defaultView
|
|
106
|
+
?.getSelection?.()
|
|
107
|
+
?.collapseToEnd();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createChatPromptComposerHandle(
|
|
112
|
+
editor: Editor,
|
|
113
|
+
): ChatPromptComposerHandle {
|
|
114
|
+
return {
|
|
115
|
+
insertAtCaret: (content, options) =>
|
|
116
|
+
insertChatPromptContentAtCaret(editor, content, options),
|
|
117
|
+
focus: () => {
|
|
118
|
+
editor.commands.focus();
|
|
119
|
+
},
|
|
120
|
+
getEditor: () => editor,
|
|
121
|
+
};
|
|
122
|
+
}
|