mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 14:44:21 +01:00
feat: drag files into the window (#2412)
This commit is contained in:
@@ -70,6 +70,7 @@ module.exports = [
|
||||
HTMLTextAreaElement: 'readonly',
|
||||
HTMLButtonElement: 'readonly',
|
||||
HTMLDivElement: 'readonly',
|
||||
File: 'readonly',
|
||||
FileList: 'readonly',
|
||||
FileReader: 'readonly',
|
||||
DOMParser: 'readonly',
|
||||
|
||||
@@ -93,6 +93,7 @@ function ChatContent({
|
||||
const [showGame, setShowGame] = useState(false);
|
||||
const [isGeneratingRecipe, setIsGeneratingRecipe] = useState(false);
|
||||
const [sessionTokenCount, setSessionTokenCount] = useState<number>(0);
|
||||
const [droppedFiles, setDroppedFiles] = useState<string[]>([]);
|
||||
const scrollRef = useRef<ScrollAreaHandle>(null);
|
||||
|
||||
const {
|
||||
@@ -402,6 +403,22 @@ function ChatContent({
|
||||
}
|
||||
}, [chat.id, messages]);
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
const paths: string[] = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
paths.push(window.electron.getPathForFile(files[i]));
|
||||
}
|
||||
setDroppedFiles(paths);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen items-center justify-center">
|
||||
{/* Loader when generating recipe */}
|
||||
@@ -410,7 +427,11 @@ function ChatContent({
|
||||
<MoreMenuLayout setView={setView} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
||||
</div>
|
||||
|
||||
<Card className="flex flex-col flex-1 rounded-none h-[calc(100vh-95px)] w-full bg-bgApp mt-0 border-none relative">
|
||||
<Card
|
||||
className="flex flex-col flex-1 rounded-none h-[calc(100vh-95px)] w-full bg-bgApp mt-0 border-none relative"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
{recipeConfig?.title && messages.length > 0 && (
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
@@ -501,6 +522,7 @@ function ChatContent({
|
||||
onStop={onStopGoose}
|
||||
commandHistory={commandHistory}
|
||||
initialValue={_input}
|
||||
droppedFiles={droppedFiles}
|
||||
/>
|
||||
<BottomMenu hasMessages={hasMessages} setView={setView} numTokens={sessionTokenCount} />
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ interface InputProps {
|
||||
onStop?: () => void;
|
||||
commandHistory?: string[];
|
||||
initialValue?: string;
|
||||
droppedFiles?: string[];
|
||||
}
|
||||
|
||||
export default function Input({
|
||||
@@ -18,6 +19,7 @@ export default function Input({
|
||||
onStop,
|
||||
commandHistory = [],
|
||||
initialValue = '',
|
||||
droppedFiles = [],
|
||||
}: InputProps) {
|
||||
const [_value, setValue] = useState(initialValue);
|
||||
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
|
||||
@@ -35,6 +37,7 @@ export default function Input({
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
const [savedInput, setSavedInput] = useState('');
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [processedFilePaths, setProcessedFilePaths] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (textAreaRef.current) {
|
||||
@@ -45,6 +48,19 @@ export default function Input({
|
||||
const minHeight = '1rem';
|
||||
const maxHeight = 10 * 24;
|
||||
|
||||
// If we have dropped files, add them to the input and update our state.
|
||||
if (processedFilePaths !== droppedFiles) {
|
||||
// Append file paths that aren't in displayValue.
|
||||
let joinedPaths =
|
||||
displayValue.trim() +
|
||||
' ' +
|
||||
droppedFiles.filter((path) => !displayValue.includes(path)).join(' ');
|
||||
setDisplayValue(joinedPaths);
|
||||
setValue(joinedPaths);
|
||||
textAreaRef.current?.focus();
|
||||
setProcessedFilePaths(droppedFiles);
|
||||
}
|
||||
|
||||
// Debounced function to update actual value
|
||||
const debouncedSetValue = useCallback((val: string) => {
|
||||
debounce((value: string) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Electron, { contextBridge, ipcRenderer } from 'electron';
|
||||
import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron';
|
||||
|
||||
interface RecipeConfig {
|
||||
id: string;
|
||||
@@ -50,6 +50,7 @@ type ElectronAPI = {
|
||||
readFile: (directory: string) => Promise<FileResponse>;
|
||||
writeFile: (directory: string, content: string) => Promise<boolean>;
|
||||
getAllowedExtensions: () => Promise<string[]>;
|
||||
getPathForFile: (file: File) => string;
|
||||
on: (
|
||||
channel: string,
|
||||
callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
|
||||
@@ -101,6 +102,7 @@ const electronAPI: ElectronAPI = {
|
||||
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
|
||||
writeFile: (filePath: string, content: string) =>
|
||||
ipcRenderer.invoke('write-file', filePath, content),
|
||||
getPathForFile: (file: File) => webUtils.getPathForFile(file),
|
||||
getAllowedExtensions: () => ipcRenderer.invoke('get-allowed-extensions'),
|
||||
on: (
|
||||
channel: string,
|
||||
|
||||
Reference in New Issue
Block a user