@sybilion/uilib 1.3.10 → 1.3.12
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/esm/components/ui/Chat/ChatChrome/ChatChrome.js +52 -17
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.styl.js +2 -2
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.styl.js +2 -2
- package/dist/esm/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.js +3 -4
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +20 -6
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptAttachments.js +11 -0
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +1 -1
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +4 -1
- package/dist/esm/components/ui/Chat/chatAttachmentAccept.js +54 -0
- package/dist/esm/components/ui/Chat/chatAttachmentExtract.js +26 -0
- package/dist/esm/components/ui/Chat/chatPdfExtract.js +31 -0
- package/dist/esm/components/ui/DropZone/DropZone.js +50 -21
- package/dist/esm/components/ui/DropZone/DropZone.styl.js +2 -2
- package/dist/esm/components/ui/FileChip/FileChip.js +26 -0
- package/dist/esm/components/ui/FileChip/FileChip.styl.js +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +14 -1
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +9 -2
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/index.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptAttachments.d.ts +8 -0
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +7 -1
- package/dist/esm/types/src/components/ui/Chat/chatAttachmentAccept.d.ts +8 -0
- package/dist/esm/types/src/components/ui/Chat/chatAttachmentExtract.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chat/chatPdfExtract.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +2 -1
- package/dist/esm/types/src/components/ui/DropZone/DropZone.d.ts +2 -0
- package/dist/esm/types/src/components/ui/FileChip/FileChip.d.ts +2 -0
- package/dist/esm/types/src/components/ui/FileChip/FileChip.types.d.ts +10 -0
- package/dist/esm/types/src/components/ui/FileChip/index.d.ts +2 -0
- package/dist/esm/types/src/docs/pages/ChatAttachmentsDropzonePage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/FileChipPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/package.json +2 -1
- package/src/components/ui/Chat/Chat.types.ts +15 -1
- package/src/components/ui/Chat/ChatChrome/ChatChrome.styl +20 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.styl.d.ts +2 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +94 -4
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +18 -2
- package/src/components/ui/Chat/ChatChrome/index.ts +1 -0
- package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +0 -56
- package/src/components/ui/Chat/ChatMessage/ChatMessage.styl.d.ts +0 -5
- package/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.tsx +6 -15
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +15 -16
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +3 -1
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +65 -17
- package/src/components/ui/Chat/ChatPrompt/ChatPromptAttachments.tsx +34 -0
- package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +12 -11
- package/src/components/ui/Chat/ChatSheet/ChatSheet.styl.d.ts +13 -13
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +14 -0
- package/src/components/ui/Chat/chat-preset-utils.ts +4 -1
- package/src/components/ui/Chat/chatAttachmentAccept.ts +70 -0
- package/src/components/ui/Chat/chatAttachmentExtract.ts +33 -0
- package/src/components/ui/Chat/chatPdfExtract.ts +37 -0
- package/src/components/ui/Chat/index.ts +5 -0
- package/src/components/ui/DropZone/DropZone.styl +24 -0
- package/src/components/ui/DropZone/DropZone.styl.d.ts +3 -0
- package/src/components/ui/DropZone/DropZone.tsx +77 -24
- package/src/components/ui/FileChip/FileChip.styl +108 -0
- package/src/components/ui/FileChip/FileChip.styl.d.ts +12 -0
- package/src/components/ui/FileChip/FileChip.tsx +93 -0
- package/src/components/ui/FileChip/FileChip.types.ts +11 -0
- package/src/components/ui/FileChip/index.ts +2 -0
- package/src/docs/pages/ChatAttachmentsDropzonePage.tsx +162 -0
- package/src/docs/pages/FileChipPage.tsx +50 -0
- package/src/docs/registry.ts +12 -0
- package/src/index.ts +1 -0
|
@@ -57,9 +57,15 @@ export type ScriptCompletePayload = {
|
|
|
57
57
|
presetId: string;
|
|
58
58
|
answers: Record<string, string>;
|
|
59
59
|
};
|
|
60
|
+
export type ChatAttachmentDropItem = {
|
|
61
|
+
file: File;
|
|
62
|
+
/** UTF-8 text for native text files; PDF yields extracted text. */
|
|
63
|
+
text: string;
|
|
64
|
+
kind: 'text' | 'pdf';
|
|
65
|
+
};
|
|
60
66
|
export interface ChatPromptProps {
|
|
61
67
|
className?: string;
|
|
62
|
-
onSubmit: (message: string) => void;
|
|
68
|
+
onSubmit: (message: string, attachments?: ChatAttachmentDropItem[]) => void;
|
|
63
69
|
placeholder?: string;
|
|
64
70
|
presets?: ChatPreset[];
|
|
65
71
|
disabled?: boolean;
|
|
@@ -68,6 +74,13 @@ export interface ChatPromptProps {
|
|
|
68
74
|
prefillMessage?: string | null;
|
|
69
75
|
/** Disclaimer above composer; default true. ChatChrome sets false when thread has messages. */
|
|
70
76
|
showNotice?: boolean;
|
|
77
|
+
/** Staged files shown above the composer until send. */
|
|
78
|
+
attachments?: ChatAttachmentDropItem[];
|
|
79
|
+
onRemoveAttachment?: (index: number) => void;
|
|
80
|
+
/** HTML `accept` for the attach file picker; set with `onAttachmentFiles`. */
|
|
81
|
+
attachmentAccept?: string;
|
|
82
|
+
/** Called when the user picks files via the attach button. */
|
|
83
|
+
onAttachmentFiles?: (files: File[]) => void;
|
|
71
84
|
}
|
|
72
85
|
export interface ChatMessageProps {
|
|
73
86
|
role: MessageRole;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChatChromeProps } from './ChatChrome.types';
|
|
2
|
-
export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showBranchActionsRow, showSyntheticBranchButtons, unusedBranchKeys, isScriptComplete, onGenerateDashboard, generatingDashboard, onGenerateDashboardClick, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, }: ChatChromeProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showBranchActionsRow, showSyntheticBranchButtons, unusedBranchKeys, isScriptComplete, onGenerateDashboard, generatingDashboard, onGenerateDashboardClick, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, }: ChatChromeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
-
import type { Message } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
|
+
import type { ChatAttachmentDropItem, Message } from '#uilib/components/ui/Chat/Chat.types';
|
|
3
3
|
import type { ChatEmptyStateProps } from '#uilib/components/ui/Chat/ChatEmptyState/ChatEmptyState.types';
|
|
4
4
|
import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
|
|
5
5
|
import type { ScrollRef } from '@homecode/ui';
|
|
@@ -10,6 +10,7 @@ export type ChatChromeResizeHandleConfig = {
|
|
|
10
10
|
onDragWidth: (rawPx: number) => void;
|
|
11
11
|
onDragComplete: (finalRawPx: number) => void;
|
|
12
12
|
};
|
|
13
|
+
export type { ChatAttachmentDropItem };
|
|
13
14
|
export interface ChatChromeProps {
|
|
14
15
|
showResizeHandle: boolean;
|
|
15
16
|
resizeHandle: ChatChromeResizeHandleConfig | undefined;
|
|
@@ -35,10 +36,16 @@ export interface ChatChromeProps {
|
|
|
35
36
|
isLastMessageFromUser: boolean;
|
|
36
37
|
scrollRef: RefObject<ScrollRef | null>;
|
|
37
38
|
effectiveScopeId: string;
|
|
38
|
-
onPromptSubmit: (message: string) => void | Promise<void>;
|
|
39
|
+
onPromptSubmit: (message: string, attachments?: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
39
40
|
onChatDeleted: (sessionId: string) => void;
|
|
40
41
|
/** `?prompt=` deep link text for one-shot composer pre-fill. */
|
|
41
42
|
promptPrefill?: string | null;
|
|
42
43
|
footerClassName?: string;
|
|
43
44
|
emptyState?: ChatEmptyStateProps;
|
|
45
|
+
/** MIME types / extensions (filtered to text-only allowlist). Enables dropzone when non-empty. */
|
|
46
|
+
allowedAttachments?: readonly string[];
|
|
47
|
+
/** When true, PDF files are accepted and parsed to plain text on drop. */
|
|
48
|
+
allowPdfAttachments?: boolean;
|
|
49
|
+
/** Optional hook when attachments are sent with a message. */
|
|
50
|
+
onAttachmentsDropped?: (items: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
44
51
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { ChatChrome } from './ChatChrome';
|
|
2
|
-
export type { ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrome.types';
|
|
2
|
+
export type { ChatAttachmentDropItem, ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrome.types';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChatPromptProps } from '../Chat.types';
|
|
2
|
-
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage,
|
|
2
|
+
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments, onRemoveAttachment, disabled, attachmentAccept, onAttachmentFiles, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatAttachmentDropItem } from '../Chat.types';
|
|
2
|
+
type ChatPromptAttachmentsProps = {
|
|
3
|
+
attachments: ChatAttachmentDropItem[];
|
|
4
|
+
onRemove: (index: number) => void;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function ChatPromptAttachments({ attachments, onRemove, disabled, }: ChatPromptAttachmentsProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChatPreset, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
2
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
3
|
+
import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
|
|
3
4
|
import type { ChatEmptyStateProps } from '../ChatEmptyState/ChatEmptyState.types';
|
|
4
5
|
export type UseChatPanelChromeModelInput = {
|
|
5
6
|
/** When true, skip sidebar chat slot, URL `?chat=`, and portal behavior (e.g. page main content). */
|
|
@@ -16,6 +17,11 @@ export type UseChatPanelChromeModelInput = {
|
|
|
16
17
|
renderMessageChart?: () => React.ReactNode;
|
|
17
18
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
18
19
|
emptyState?: ChatEmptyStateProps;
|
|
20
|
+
/** MIME types / extensions for text-only chat attachments (filtered by uilib allowlist). */
|
|
21
|
+
allowedAttachments?: readonly string[];
|
|
22
|
+
/** When true, PDF drops are accepted and parsed to plain text. */
|
|
23
|
+
allowPdfAttachments?: boolean;
|
|
24
|
+
onAttachmentsDropped?: (items: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
19
25
|
};
|
|
20
26
|
export type UseChatPanelChromeModelResult = {
|
|
21
27
|
chromeProps: ChatChromeProps;
|
|
@@ -25,4 +31,4 @@ export type UseChatPanelChromeModelResult = {
|
|
|
25
31
|
newChat: () => void;
|
|
26
32
|
chatPanelContainer: HTMLElement | null;
|
|
27
33
|
};
|
|
28
|
-
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
|
|
34
|
+
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** MIME types and extensions accepted for chat text attachments. */
|
|
2
|
+
export declare const TEXT_ATTACHMENT_ACCEPT_PARTS: readonly ["text/plain", ".txt", "text/csv", ".csv", "text/markdown", ".md", ".markdown", "application/json", ".json", "text/html", ".html", ".htm", "text/xml", "application/xml", ".xml", "text/yaml", "application/yaml", "application/x-yaml", ".yaml", ".yml", "text/tab-separated-values", ".tsv", "text/calendar", ".ics"];
|
|
3
|
+
export declare const PDF_ATTACHMENT_ACCEPT_PARTS: readonly ["application/pdf", ".pdf"];
|
|
4
|
+
/** Keep only tokens from `parts` that appear in the text attachment allowlist. */
|
|
5
|
+
export declare function filterToTextAttachments(parts: readonly string[] | undefined): string[];
|
|
6
|
+
export declare function buildAcceptAttr(filteredTextParts: readonly string[], allowPdf: boolean): string;
|
|
7
|
+
export declare function isPdfFile(file: File): boolean;
|
|
8
|
+
export declare function isAttachmentsDropzoneEnabled(allowedAttachments: readonly string[] | undefined, allowPdfAttachments: boolean | undefined): boolean;
|
|
@@ -2,6 +2,7 @@ export { Chat } from './Chat';
|
|
|
2
2
|
export { usedPresetIdsFromMessages } from './chat-preset-utils';
|
|
3
3
|
export { ChatChrome } from './ChatChrome';
|
|
4
4
|
export type { ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrome';
|
|
5
|
+
export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments, } from './chatAttachmentAccept';
|
|
5
6
|
export { ChatSheet } from './ChatSheet/ChatSheet';
|
|
6
7
|
export { useChatPanelChromeModel } from './ChatSheet/useChatPanelChromeModel';
|
|
7
8
|
export type { ChatSheetActions, ChatSheetProps } from './ChatSheet/ChatSheet';
|
|
@@ -9,6 +10,6 @@ export type { UseChatPanelChromeModelInput, UseChatPanelChromeModelResult, } fro
|
|
|
9
10
|
export { ChatMessage } from './ChatMessage';
|
|
10
11
|
export { ChatPrompt } from './ChatPrompt';
|
|
11
12
|
export { ChatPresets } from './ChatPresets';
|
|
12
|
-
export type { Chat as ChatType, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserCsvAttachment, } from './Chat.types';
|
|
13
|
+
export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserCsvAttachment, } from './Chat.types';
|
|
13
14
|
export { MessageRole } from './Chat.types';
|
|
14
15
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
|
@@ -4,6 +4,8 @@ interface DropZoneBaseProps {
|
|
|
4
4
|
error?: string | null;
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
ghost?: boolean;
|
|
7
|
+
/** When `container`, drag overlay fills the parent bounds instead of the viewport. */
|
|
8
|
+
overlayScope?: 'viewport' | 'container';
|
|
7
9
|
id?: string;
|
|
8
10
|
className?: string;
|
|
9
11
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatAttachmentsDropzonePage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function FileChipPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -24,6 +24,7 @@ export * from './components/ui/Dialog';
|
|
|
24
24
|
export * from './components/ui/Drawer';
|
|
25
25
|
export * from './components/ui/DropdownMenu';
|
|
26
26
|
export * from './components/ui/DropZone/DropZone';
|
|
27
|
+
export * from './components/ui/FileChip';
|
|
27
28
|
export * from './components/ui/FlickeringGrid';
|
|
28
29
|
export * from './components/ui/Foldable';
|
|
29
30
|
export * from './components/ui/Gap/Gap';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sybilion/uilib",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "Sybilion Design System — React UI components (Webpack + Stylus)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"lightweight-charts": "^5.0.9",
|
|
104
104
|
"lucide-react": "^0.546.0",
|
|
105
105
|
"motion": "^12.23.12",
|
|
106
|
+
"pdfjs-dist": "^4.10.38",
|
|
106
107
|
"recharts": "^3.2.1",
|
|
107
108
|
"standard-version": "^9.5.0",
|
|
108
109
|
"style-inject": "^0.3.0",
|
|
@@ -67,9 +67,16 @@ export type ScriptCompletePayload = {
|
|
|
67
67
|
answers: Record<string, string>;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
export type ChatAttachmentDropItem = {
|
|
71
|
+
file: File;
|
|
72
|
+
/** UTF-8 text for native text files; PDF yields extracted text. */
|
|
73
|
+
text: string;
|
|
74
|
+
kind: 'text' | 'pdf';
|
|
75
|
+
};
|
|
76
|
+
|
|
70
77
|
export interface ChatPromptProps {
|
|
71
78
|
className?: string;
|
|
72
|
-
onSubmit: (message: string) => void;
|
|
79
|
+
onSubmit: (message: string, attachments?: ChatAttachmentDropItem[]) => void;
|
|
73
80
|
placeholder?: string;
|
|
74
81
|
presets?: ChatPreset[];
|
|
75
82
|
disabled?: boolean;
|
|
@@ -78,6 +85,13 @@ export interface ChatPromptProps {
|
|
|
78
85
|
prefillMessage?: string | null;
|
|
79
86
|
/** Disclaimer above composer; default true. ChatChrome sets false when thread has messages. */
|
|
80
87
|
showNotice?: boolean;
|
|
88
|
+
/** Staged files shown above the composer until send. */
|
|
89
|
+
attachments?: ChatAttachmentDropItem[];
|
|
90
|
+
onRemoveAttachment?: (index: number) => void;
|
|
91
|
+
/** HTML `accept` for the attach file picker; set with `onAttachmentFiles`. */
|
|
92
|
+
attachmentAccept?: string;
|
|
93
|
+
/** Called when the user picks files via the attach button. */
|
|
94
|
+
onAttachmentFiles?: (files: File[]) => void;
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
export interface ChatMessageProps {
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
flex-direction column
|
|
58
58
|
position relative
|
|
59
59
|
|
|
60
|
+
.attachmentDropzone
|
|
61
|
+
position absolute
|
|
62
|
+
inset 0
|
|
63
|
+
z-index 200
|
|
64
|
+
|
|
60
65
|
.scrollWrapper
|
|
61
66
|
position relative
|
|
62
67
|
flex 1
|
|
@@ -102,6 +107,21 @@
|
|
|
102
107
|
border-top 1px solid var(--border)
|
|
103
108
|
box-shadow 0 0 20px 16px var(--background)
|
|
104
109
|
|
|
110
|
+
.notice
|
|
111
|
+
position absolute
|
|
112
|
+
top calc(-1 * var(--p-7))
|
|
113
|
+
left 0
|
|
114
|
+
right 0
|
|
115
|
+
margin-bottom var(--p-1)
|
|
116
|
+
|
|
117
|
+
font-size var(--text-xs)
|
|
118
|
+
text-align center
|
|
119
|
+
color var(--muted-foreground)
|
|
120
|
+
pointer-events none
|
|
121
|
+
|
|
122
|
+
@media (max-width MOBILE)
|
|
123
|
+
font-size 10px
|
|
124
|
+
|
|
105
125
|
.loader
|
|
106
126
|
// z-index 1
|
|
107
127
|
// position absolute
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// This file is automatically generated.
|
|
2
2
|
// Please do not change this file!
|
|
3
3
|
interface CssExports {
|
|
4
|
+
'attachmentDropzone': string;
|
|
4
5
|
'branchBtnWrap': string;
|
|
5
6
|
'branchRow': string;
|
|
6
7
|
'chatResizeHandle': string;
|
|
7
8
|
'content': string;
|
|
8
9
|
'footer': string;
|
|
9
10
|
'loader': string;
|
|
11
|
+
'notice': string;
|
|
10
12
|
'panelClose': string;
|
|
11
13
|
'panelHeader': string;
|
|
12
14
|
'root': string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
|
-
import { useEffect } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
displayLabelForBranchKeyFromMessages,
|
|
@@ -10,9 +10,17 @@ import { Scroll } from '@homecode/ui';
|
|
|
10
10
|
import { ChartLineIcon, PaperPlaneRightIcon, X } from '@phosphor-icons/react';
|
|
11
11
|
|
|
12
12
|
import { Button } from '../../Button';
|
|
13
|
+
import { DropZone } from '../../DropZone/DropZone';
|
|
13
14
|
import { PanelResizeHandle } from '../../Sidebar/Sidebar';
|
|
14
15
|
import SidebarStem from '../../Sidebar/Sidebar.styl';
|
|
15
16
|
import { Chat } from '../Chat';
|
|
17
|
+
import type { ChatAttachmentDropItem } from '../Chat.types';
|
|
18
|
+
import {
|
|
19
|
+
buildAcceptAttr,
|
|
20
|
+
filterToTextAttachments,
|
|
21
|
+
isAttachmentsDropzoneEnabled,
|
|
22
|
+
} from '../chatAttachmentAccept';
|
|
23
|
+
import { extractChatAttachmentItems } from '../chatAttachmentExtract';
|
|
16
24
|
import S from './ChatChrome.styl';
|
|
17
25
|
import type { ChatChromeProps } from './ChatChrome.types';
|
|
18
26
|
|
|
@@ -45,7 +53,65 @@ export function ChatChrome({
|
|
|
45
53
|
promptPrefill,
|
|
46
54
|
footerClassName,
|
|
47
55
|
emptyState,
|
|
56
|
+
allowedAttachments,
|
|
57
|
+
allowPdfAttachments = false,
|
|
58
|
+
onAttachmentsDropped,
|
|
48
59
|
}: ChatChromeProps) {
|
|
60
|
+
const filteredAllowedAttachments = useMemo(
|
|
61
|
+
() => filterToTextAttachments(allowedAttachments),
|
|
62
|
+
[allowedAttachments],
|
|
63
|
+
);
|
|
64
|
+
const attachmentsDropzoneEnabled = isAttachmentsDropzoneEnabled(
|
|
65
|
+
allowedAttachments,
|
|
66
|
+
allowPdfAttachments,
|
|
67
|
+
);
|
|
68
|
+
const attachmentAccept = useMemo(
|
|
69
|
+
() => buildAcceptAttr(filteredAllowedAttachments, allowPdfAttachments),
|
|
70
|
+
[filteredAllowedAttachments, allowPdfAttachments],
|
|
71
|
+
);
|
|
72
|
+
const [pendingAttachments, setPendingAttachments] = useState<
|
|
73
|
+
ChatAttachmentDropItem[]
|
|
74
|
+
>([]);
|
|
75
|
+
const [isExtractingAttachments, setIsExtractingAttachments] = useState(false);
|
|
76
|
+
const promptBusy = isLoading || isExtractingAttachments;
|
|
77
|
+
|
|
78
|
+
const handleAttachmentFiles = useCallback(
|
|
79
|
+
(files: File[]) => {
|
|
80
|
+
if (promptBusy || files.length === 0) return;
|
|
81
|
+
|
|
82
|
+
setIsExtractingAttachments(true);
|
|
83
|
+
void extractChatAttachmentItems(files, allowPdfAttachments)
|
|
84
|
+
.then(items => {
|
|
85
|
+
if (items.length > 0) {
|
|
86
|
+
setPendingAttachments(prev => [...prev, ...items]);
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.finally(() => setIsExtractingAttachments(false));
|
|
90
|
+
},
|
|
91
|
+
[allowPdfAttachments, promptBusy],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const handleRemoveAttachment = useCallback((index: number) => {
|
|
95
|
+
setPendingAttachments(prev => prev.filter((_, i) => i !== index));
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const handlePromptSubmitWithAttachments = useCallback(
|
|
99
|
+
(message: string, submittedAttachments?: ChatAttachmentDropItem[]) => {
|
|
100
|
+
const trimmed = message.trim();
|
|
101
|
+
const attachments = submittedAttachments ?? [];
|
|
102
|
+
if (!trimmed && attachments.length === 0) return;
|
|
103
|
+
|
|
104
|
+
void onPromptSubmit(trimmed, attachments);
|
|
105
|
+
|
|
106
|
+
if (attachments.length > 0 && onAttachmentsDropped) {
|
|
107
|
+
void onAttachmentsDropped(attachments);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setPendingAttachments([]);
|
|
111
|
+
},
|
|
112
|
+
[onAttachmentsDropped, onPromptSubmit],
|
|
113
|
+
);
|
|
114
|
+
|
|
49
115
|
useEffect(() => {
|
|
50
116
|
if (isEmpty) return;
|
|
51
117
|
|
|
@@ -91,6 +157,18 @@ export function ChatChrome({
|
|
|
91
157
|
) : null}
|
|
92
158
|
</div>
|
|
93
159
|
<div className={S.content}>
|
|
160
|
+
{attachmentsDropzoneEnabled ? (
|
|
161
|
+
<DropZone
|
|
162
|
+
accept={attachmentAccept}
|
|
163
|
+
label="Drop text files to attach"
|
|
164
|
+
multiple
|
|
165
|
+
ghost
|
|
166
|
+
overlayScope="container"
|
|
167
|
+
disabled={promptBusy}
|
|
168
|
+
className={S.attachmentDropzone}
|
|
169
|
+
onFiles={handleAttachmentFiles}
|
|
170
|
+
/>
|
|
171
|
+
) : null}
|
|
94
172
|
<Chat
|
|
95
173
|
isEmpty={isEmpty}
|
|
96
174
|
scopeId={effectiveScopeId}
|
|
@@ -195,11 +273,23 @@ export function ChatChrome({
|
|
|
195
273
|
)}
|
|
196
274
|
|
|
197
275
|
<div className={cn(S.footer, footerClassName)}>
|
|
276
|
+
{isEmpty ? (
|
|
277
|
+
<div className={S.notice}>
|
|
278
|
+
Forecast Assistant can make mistakes.
|
|
279
|
+
</div>
|
|
280
|
+
) : null}
|
|
198
281
|
<Chat.Prompt
|
|
199
|
-
onSubmit={
|
|
200
|
-
disabled={
|
|
282
|
+
onSubmit={handlePromptSubmitWithAttachments}
|
|
283
|
+
disabled={promptBusy}
|
|
284
|
+
attachments={pendingAttachments}
|
|
285
|
+
onRemoveAttachment={handleRemoveAttachment}
|
|
201
286
|
prefillMessage={promptPrefill ?? undefined}
|
|
202
|
-
|
|
287
|
+
attachmentAccept={
|
|
288
|
+
attachmentsDropzoneEnabled ? attachmentAccept : undefined
|
|
289
|
+
}
|
|
290
|
+
onAttachmentFiles={
|
|
291
|
+
attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined
|
|
292
|
+
}
|
|
203
293
|
/>
|
|
204
294
|
</div>
|
|
205
295
|
</Chat>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ChatAttachmentDropItem,
|
|
5
|
+
Message,
|
|
6
|
+
} from '#uilib/components/ui/Chat/Chat.types';
|
|
4
7
|
import type { ChatEmptyStateProps } from '#uilib/components/ui/Chat/ChatEmptyState/ChatEmptyState.types';
|
|
5
8
|
import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
|
|
6
9
|
import type { ScrollRef } from '@homecode/ui';
|
|
@@ -13,6 +16,8 @@ export type ChatChromeResizeHandleConfig = {
|
|
|
13
16
|
onDragComplete: (finalRawPx: number) => void;
|
|
14
17
|
};
|
|
15
18
|
|
|
19
|
+
export type { ChatAttachmentDropItem };
|
|
20
|
+
|
|
16
21
|
export interface ChatChromeProps {
|
|
17
22
|
showResizeHandle: boolean;
|
|
18
23
|
resizeHandle: ChatChromeResizeHandleConfig | undefined;
|
|
@@ -40,10 +45,21 @@ export interface ChatChromeProps {
|
|
|
40
45
|
isLastMessageFromUser: boolean;
|
|
41
46
|
scrollRef: RefObject<ScrollRef | null>;
|
|
42
47
|
effectiveScopeId: string;
|
|
43
|
-
onPromptSubmit: (
|
|
48
|
+
onPromptSubmit: (
|
|
49
|
+
message: string,
|
|
50
|
+
attachments?: ChatAttachmentDropItem[],
|
|
51
|
+
) => void | Promise<void>;
|
|
44
52
|
onChatDeleted: (sessionId: string) => void;
|
|
45
53
|
/** `?prompt=` deep link text for one-shot composer pre-fill. */
|
|
46
54
|
promptPrefill?: string | null;
|
|
47
55
|
footerClassName?: string;
|
|
48
56
|
emptyState?: ChatEmptyStateProps;
|
|
57
|
+
/** MIME types / extensions (filtered to text-only allowlist). Enables dropzone when non-empty. */
|
|
58
|
+
allowedAttachments?: readonly string[];
|
|
59
|
+
/** When true, PDF files are accepted and parsed to plain text on drop. */
|
|
60
|
+
allowPdfAttachments?: boolean;
|
|
61
|
+
/** Optional hook when attachments are sent with a message. */
|
|
62
|
+
onAttachmentsDropped?: (
|
|
63
|
+
items: ChatAttachmentDropItem[],
|
|
64
|
+
) => void | Promise<void>;
|
|
49
65
|
}
|
|
@@ -35,62 +35,6 @@
|
|
|
35
35
|
:global(.dark) &
|
|
36
36
|
background-color var(--sb-gray-800)
|
|
37
37
|
|
|
38
|
-
.userCsvCard
|
|
39
|
-
appearance none
|
|
40
|
-
border 0
|
|
41
|
-
margin 0
|
|
42
|
-
font inherit
|
|
43
|
-
display flex
|
|
44
|
-
align-items center
|
|
45
|
-
gap var(--p-2)
|
|
46
|
-
padding var(--p-3)
|
|
47
|
-
padding-right var(--p-4)
|
|
48
|
-
background-color var(--background)
|
|
49
|
-
box-shadow 0 0 0 1px var(--border)
|
|
50
|
-
border-radius var(--p-3)
|
|
51
|
-
width fit-content
|
|
52
|
-
max-width 100%
|
|
53
|
-
text-align left
|
|
54
|
-
cursor pointer
|
|
55
|
-
transition background-color 150ms
|
|
56
|
-
|
|
57
|
-
&:hover
|
|
58
|
-
background-color var(--sb-gray-50)
|
|
59
|
-
|
|
60
|
-
&:focus-visible
|
|
61
|
-
outline 2px solid var(--ring)
|
|
62
|
-
outline-offset 2px
|
|
63
|
-
|
|
64
|
-
:global(.dark) &
|
|
65
|
-
background-color var(--sb-gray-800)
|
|
66
|
-
|
|
67
|
-
&:hover
|
|
68
|
-
background-color var(--sb-gray-900)
|
|
69
|
-
|
|
70
|
-
.userCsvCardIcon
|
|
71
|
-
display flex
|
|
72
|
-
align-items center
|
|
73
|
-
justify-content center
|
|
74
|
-
width 32px
|
|
75
|
-
height 32px
|
|
76
|
-
flex-shrink 0
|
|
77
|
-
|
|
78
|
-
.userCsvCardContent
|
|
79
|
-
display flex
|
|
80
|
-
flex-direction column
|
|
81
|
-
flex 1
|
|
82
|
-
min-width 0
|
|
83
|
-
|
|
84
|
-
.userCsvCardTitle
|
|
85
|
-
font-size var(--text-sm)
|
|
86
|
-
font-weight 500
|
|
87
|
-
line-height 1.4
|
|
88
|
-
|
|
89
|
-
.userCsvCardSubtitle
|
|
90
|
-
font-size var(--text-xs)
|
|
91
|
-
color var(--muted-foreground)
|
|
92
|
-
line-height 1.4
|
|
93
|
-
|
|
94
38
|
.role-system
|
|
95
39
|
align-items center
|
|
96
40
|
|
|
@@ -18,11 +18,6 @@ interface CssExports {
|
|
|
18
18
|
'scrollHorizontal': string;
|
|
19
19
|
'text': string;
|
|
20
20
|
'userColumn': string;
|
|
21
|
-
'userCsvCard': string;
|
|
22
|
-
'userCsvCardContent': string;
|
|
23
|
-
'userCsvCardIcon': string;
|
|
24
|
-
'userCsvCardSubtitle': string;
|
|
25
|
-
'userCsvCardTitle': string;
|
|
26
21
|
}
|
|
27
22
|
export const cssExports: CssExports;
|
|
28
23
|
export default cssExports;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { FileChip } from '#uilib/components/ui/FileChip';
|
|
1
2
|
import { downloadTextFile } from '#uilib/utils/downloadTextFile';
|
|
2
3
|
|
|
3
|
-
import { CsvIcon } from '../../../icons/CsvIcon/CsvIcon';
|
|
4
4
|
import type { UserCsvAttachment } from '../Chat.types';
|
|
5
|
-
import S from './ChatMessage.styl';
|
|
6
5
|
|
|
7
6
|
const CSV_DOWNLOAD_HINT = 'Download .CSV file';
|
|
8
7
|
|
|
@@ -12,10 +11,10 @@ export function UserCsvAttachmentBubble({
|
|
|
12
11
|
attachment: UserCsvAttachment;
|
|
13
12
|
}) {
|
|
14
13
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
<FileChip
|
|
15
|
+
name={attachment.displayName}
|
|
16
|
+
format="csv"
|
|
17
|
+
hint={CSV_DOWNLOAD_HINT}
|
|
19
18
|
onClick={() =>
|
|
20
19
|
downloadTextFile(
|
|
21
20
|
attachment.content,
|
|
@@ -23,14 +22,6 @@ export function UserCsvAttachmentBubble({
|
|
|
23
22
|
'text/csv;charset=utf-8',
|
|
24
23
|
)
|
|
25
24
|
}
|
|
26
|
-
|
|
27
|
-
<div className={S.userCsvCardIcon}>
|
|
28
|
-
<CsvIcon size={32} />
|
|
29
|
-
</div>
|
|
30
|
-
<div className={S.userCsvCardContent}>
|
|
31
|
-
<div className={S.userCsvCardTitle}>{attachment.displayName}</div>
|
|
32
|
-
<div className={S.userCsvCardSubtitle}>{CSV_DOWNLOAD_HINT}</div>
|
|
33
|
-
</div>
|
|
34
|
-
</button>
|
|
25
|
+
/>
|
|
35
26
|
);
|
|
36
27
|
}
|
|
@@ -19,20 +19,12 @@ INPUT_MAX_HEIGHT = 200px
|
|
|
19
19
|
gap var(--p-3)
|
|
20
20
|
width 100%
|
|
21
21
|
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
top calc(-1 * var(--p-12))
|
|
25
|
-
left 0
|
|
26
|
-
right 0
|
|
27
|
-
margin-bottom var(--p-1)
|
|
22
|
+
.fileInput
|
|
23
|
+
display none
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
pointer-events none
|
|
33
|
-
|
|
34
|
-
@media (max-width MOBILE)
|
|
35
|
-
font-size 10px
|
|
25
|
+
.attachButton
|
|
26
|
+
flex-shrink 0
|
|
27
|
+
align-self flex-end
|
|
36
28
|
|
|
37
29
|
.input
|
|
38
30
|
flex 1
|
|
@@ -76,6 +68,13 @@ INPUT_MAX_HEIGHT = 200px
|
|
|
76
68
|
right -100%
|
|
77
69
|
bottom -100%
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
.attachments
|
|
72
|
+
display flex
|
|
73
|
+
flex-wrap wrap
|
|
74
|
+
gap var(--p-2)
|
|
75
|
+
margin-bottom var(--p-2)
|
|
76
|
+
|
|
77
|
+
.attachmentItem
|
|
78
|
+
flex 1 1 300px
|
|
79
|
+
max-width 300px
|
|
80
|
+
min-width 0
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Please do not change this file!
|
|
3
3
|
interface CssExports {
|
|
4
4
|
'attachButton': string;
|
|
5
|
+
'attachmentItem': string;
|
|
6
|
+
'attachments': string;
|
|
5
7
|
'composer': string;
|
|
8
|
+
'fileInput': string;
|
|
6
9
|
'input': string;
|
|
7
|
-
'notice': string;
|
|
8
10
|
'root': string;
|
|
9
11
|
'submitColumn': string;
|
|
10
12
|
}
|