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