Skip to content

Commit ce4e106

Browse files
joepioPolleps
authored andcommitted
Add paste/drop support to AIChatInput #951
1 parent e875ce3 commit ce4e106

File tree

4 files changed

+71
-25
lines changed

4 files changed

+71
-25
lines changed

browser/data-browser/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@radix-ui/react-scroll-area": "^1.2.0",
2424
"@radix-ui/react-tabs": "^1.1.1",
2525
"@tanstack/react-router": "^1.95.1",
26+
"@tiptap/extension-file-handler": "^2.25.0",
2627
"@tiptap/extension-image": "^2.11.7",
2728
"@tiptap/extension-link": "^2.11.7",
2829
"@tiptap/extension-mention": "^2.11.7",

browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/AsyncAIChatInput.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { EditorContent, useEditor, type JSONContent } from '@tiptap/react';
22
import { styled } from 'styled-components';
33
import StarterKit from '@tiptap/starter-kit';
44
import Mention from '@tiptap/extension-mention';
5+
import FileHandler from '@tiptap/extension-file-handler';
56
import { TiptapContextProvider } from '../TiptapContext';
67
import { EditorWrapperBase } from '../EditorWrapperBase';
78
import { searchSuggestionBuilder } from './resourceSuggestions';
@@ -24,6 +25,7 @@ import {
2425
IconButtonVariant,
2526
} from '../../../components/IconButton/IconButton';
2627
import { FaArrowRight } from 'react-icons/fa6';
28+
import { addIf } from '../../../helpers/addIf';
2729

2830
const createAttribute = (propName: string, dataName: string) => {
2931
return {
@@ -73,12 +75,20 @@ interface AsyncAIChatInputProps {
7375
onMentionUpdate: (mentions: MentionItem[]) => void;
7476
onChange: (markdown: string) => void;
7577
onSubmit: () => void;
78+
onFileAdded?: (files: File[]) => void;
7679
hasFiles: boolean;
7780
}
7881

7982
const AsyncAIChatInput: React.FC<
8083
React.PropsWithChildren<AsyncAIChatInputProps>
81-
> = ({ onMentionUpdate, onChange, onSubmit, children, hasFiles }) => {
84+
> = ({
85+
onMentionUpdate,
86+
onChange,
87+
onSubmit,
88+
children,
89+
hasFiles,
90+
onFileAdded,
91+
}) => {
8292
const store = useStore();
8393
const { drive, mcpServers } = useSettings();
8494
const [markdown, setMarkdown] = useState('');
@@ -141,6 +151,19 @@ const AsyncAIChatInput: React.FC<
141151
Placeholder.configure({
142152
placeholder: 'Ask me anything...',
143153
}),
154+
...addIf(
155+
!!onFileAdded,
156+
FileHandler.configure({
157+
onDrop: (_currentEditor, files) => {
158+
onFileAdded!(Array.from(files));
159+
},
160+
onPaste: (_currentEditor, files, htmlContent) => {
161+
if (htmlContent) return false;
162+
163+
onFileAdded!(Array.from(files));
164+
},
165+
}),
166+
),
144167
],
145168
autofocus: true,
146169
},

browser/data-browser/src/components/AI/SimpleAIChat.tsx

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -145,28 +145,27 @@ export const SimpleAIChat: React.FC<
145145
},
146146
});
147147

148-
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
149-
const file = event.target.files?.[0];
150-
if (!file) return;
151-
152-
const reader = new FileReader();
153-
154-
reader.onloadend = () => {
155-
const base64Content = (reader.result as string).split(',')[1];
156-
setAttachedFile({
157-
name: file.name,
158-
type: file.type,
159-
base64Content,
160-
isImage: IMAGE_MIME_TYPES.includes(file.type),
161-
});
162-
toast.success(`File "${file.name}" attached`);
163-
};
148+
const handleFileUpload = (files: File[]) => {
149+
for (const file of files) {
150+
const reader = new FileReader();
151+
152+
reader.onloadend = () => {
153+
const base64Content = (reader.result as string).split(',')[1];
154+
setAttachedFile({
155+
name: file.name,
156+
type: file.type,
157+
base64Content,
158+
isImage: IMAGE_MIME_TYPES.includes(file.type),
159+
});
160+
toast.success(`File "${file.name}" attached`);
161+
};
164162

165-
reader.onerror = () => {
166-
toast.error('Error reading file');
167-
};
163+
reader.onerror = () => {
164+
toast.error('Error reading file');
165+
};
168166

169-
reader.readAsDataURL(file);
167+
reader.readAsDataURL(file);
168+
}
170169
};
171170

172171
const removeAttachedFile = () => {
@@ -607,6 +606,11 @@ export const SimpleAIChat: React.FC<
607606
onChange={setUserInput}
608607
onSubmit={handleSubmit}
609608
hasFiles={!!attachedFile}
609+
onFileAdded={
610+
checkModelSupportsImageInput(selectedAgent.model)
611+
? handleFileUpload
612+
: undefined
613+
}
610614
>
611615
<Row gap='0.5rem'>
612616
<SubtleButton onClick={() => setAgentConfigOpen(true)}>
@@ -629,7 +633,11 @@ export const SimpleAIChat: React.FC<
629633
<input
630634
type='file'
631635
ref={fileInputRef}
632-
onChange={handleFileUpload}
636+
onChange={e => {
637+
if (!e.target.files) return;
638+
639+
handleFileUpload(Array.from(e.target.files));
640+
}}
633641
style={{ display: 'none' }}
634642
/>
635643
<IconButton

browser/pnpm-lock.yaml

Lines changed: 17 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)