mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-11-28 13:10:24 +08:00
Enables dragging files from preview directory listings directly into the
WaveAI panel for analysis.
## Changes
**Modified `frontend/app/aipanel/aipanel.tsx`:**
- Added `useDrop` hook to accept `FILE_ITEM` drag type from preview
directory
- Implemented `handleFileItemDrop` to:
- Read file content via `RpcApi.FileReadCommand` using the remote URI
- Convert base64 data to browser `File` object with proper MIME type
- Validate and add to panel using existing `model.addFile()` flow
- Integrated with existing drag overlay for visual feedback
- Rejects directories with appropriate error messaging
## Implementation
```typescript
const handleFileItemDrop = useCallback(
async (draggedFile: DraggedFile) => {
if (draggedFile.isDir) {
model.setError("Cannot add directories to Wave AI. Please select a file.");
return;
}
const fileData = await RpcApi.FileReadCommand(TabRpcClient, {
info: { path: draggedFile.uri }
}, null);
const bytes = new Uint8Array(atob(fileData.data64).split('').map(c => c.charCodeAt(0)));
const file = new File([bytes], draggedFile.relName, {
type: fileData.info?.mimetype || "application/octet-stream"
});
// Existing validation and addFile flow
await model.addFile(file);
},
[model]
);
const [{ isOver, canDrop }, drop] = useDrop(() => ({
accept: "FILE_ITEM",
drop: handleFileItemDrop,
collect: (monitor) => ({ isOver: monitor.isOver(), canDrop: monitor.canDrop() })
}), [handleFileItemDrop]);
```
No changes required to preview directory—it already exports `FILE_ITEM`
drag items. Works independently from native file system drag-and-drop.
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { tryReinjectKey } from "@/app/store/keymodel";
|
|
import { CodeEditor } from "@/app/view/codeeditor/codeeditor";
|
|
import { globalStore } from "@/store/global";
|
|
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
|
import { fireAndForget } from "@/util/util";
|
|
import { Monaco } from "@monaco-editor/react";
|
|
import { useAtomValue, useSetAtom } from "jotai";
|
|
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
|
import { useEffect } from "react";
|
|
import type { SpecializedViewProps } from "./preview";
|
|
|
|
export const shellFileMap: Record<string, string> = {
|
|
".bashrc": "shell",
|
|
".bash_profile": "shell",
|
|
".bash_login": "shell",
|
|
".bash_logout": "shell",
|
|
".profile": "shell",
|
|
".zshrc": "shell",
|
|
".zprofile": "shell",
|
|
".zshenv": "shell",
|
|
".zlogin": "shell",
|
|
".zlogout": "shell",
|
|
".kshrc": "shell",
|
|
".cshrc": "shell",
|
|
".tcshrc": "shell",
|
|
".xonshrc": "python",
|
|
".shrc": "shell",
|
|
".aliases": "shell",
|
|
".functions": "shell",
|
|
".exports": "shell",
|
|
".direnvrc": "shell",
|
|
".vimrc": "shell",
|
|
".gvimrc": "shell",
|
|
};
|
|
|
|
function CodeEditPreview({ model }: SpecializedViewProps) {
|
|
const fileContent = useAtomValue(model.fileContent);
|
|
const setNewFileContent = useSetAtom(model.newFileContent);
|
|
const fileInfo = useAtomValue(model.statFile);
|
|
const fileName = fileInfo?.path || fileInfo?.name;
|
|
|
|
const baseName = fileName ? fileName.split("/").pop() : null;
|
|
const language = baseName && shellFileMap[baseName] ? shellFileMap[baseName] : undefined;
|
|
|
|
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
|
|
if (checkKeyPressed(e, "Cmd:e")) {
|
|
fireAndForget(() => model.setEditMode(false));
|
|
return true;
|
|
}
|
|
if (checkKeyPressed(e, "Cmd:s") || checkKeyPressed(e, "Ctrl:s")) {
|
|
fireAndForget(model.handleFileSave.bind(model));
|
|
return true;
|
|
}
|
|
if (checkKeyPressed(e, "Cmd:r")) {
|
|
fireAndForget(model.handleFileRevert.bind(model));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
useEffect(() => {
|
|
model.codeEditKeyDownHandler = codeEditKeyDownHandler;
|
|
return () => {
|
|
model.codeEditKeyDownHandler = null;
|
|
model.monacoRef.current = null;
|
|
};
|
|
}, []);
|
|
|
|
function onMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco): () => void {
|
|
model.monacoRef.current = editor;
|
|
|
|
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
|
const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
|
const handled = tryReinjectKey(waveEvent);
|
|
if (handled) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
const isFocused = globalStore.get(model.nodeModel.isFocused);
|
|
if (isFocused) {
|
|
editor.focus();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<CodeEditor
|
|
blockId={model.blockId}
|
|
text={fileContent}
|
|
fileName={fileName}
|
|
language={language}
|
|
readonly={fileInfo.readonly}
|
|
onChange={(text) => setNewFileContent(text)}
|
|
onMount={onMount}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export { CodeEditPreview };
|