Add Deep Agents UI, pipeline fixes, and agent improvements.
Includes deep-agents-ui integration, rework detection via Gitea, tool-call sanitization fixes, and startup scripts.
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useCallback, useState, useEffect } from "react";
|
||||
import { FileText, Copy, Download, Edit, Save, X, Loader2 } from "lucide-react";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { toast } from "sonner";
|
||||
import { MarkdownContent } from "@/app/components/MarkdownContent";
|
||||
import type { FileItem } from "@/app/types/types";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
|
||||
const LANGUAGE_MAP: Record<string, string> = {
|
||||
js: "javascript",
|
||||
jsx: "javascript",
|
||||
ts: "typescript",
|
||||
tsx: "typescript",
|
||||
py: "python",
|
||||
rb: "ruby",
|
||||
go: "go",
|
||||
rs: "rust",
|
||||
java: "java",
|
||||
cpp: "cpp",
|
||||
c: "c",
|
||||
cs: "csharp",
|
||||
php: "php",
|
||||
swift: "swift",
|
||||
kt: "kotlin",
|
||||
scala: "scala",
|
||||
sh: "bash",
|
||||
bash: "bash",
|
||||
zsh: "bash",
|
||||
json: "json",
|
||||
xml: "xml",
|
||||
html: "html",
|
||||
css: "css",
|
||||
scss: "scss",
|
||||
sass: "sass",
|
||||
less: "less",
|
||||
sql: "sql",
|
||||
yaml: "yaml",
|
||||
yml: "yaml",
|
||||
toml: "toml",
|
||||
ini: "ini",
|
||||
dockerfile: "dockerfile",
|
||||
makefile: "makefile",
|
||||
};
|
||||
|
||||
export const FileViewDialog = React.memo<{
|
||||
file: FileItem | null;
|
||||
onSaveFile: (fileName: string, content: string) => Promise<void>;
|
||||
onClose: () => void;
|
||||
editDisabled: boolean;
|
||||
}>(({ file, onSaveFile, onClose, editDisabled }) => {
|
||||
const [isEditingMode, setIsEditingMode] = useState(file === null);
|
||||
const [fileName, setFileName] = useState(String(file?.path || ""));
|
||||
const [fileContent, setFileContent] = useState(String(file?.content || ""));
|
||||
|
||||
const fileUpdate = useSWRMutation(
|
||||
{ kind: "files-update", fileName, fileContent },
|
||||
async ({ fileName, fileContent }) => {
|
||||
if (!fileName || !fileContent) return;
|
||||
return await onSaveFile(fileName, fileContent);
|
||||
},
|
||||
{
|
||||
onSuccess: () => setIsEditingMode(false),
|
||||
onError: (error) => toast.error(`Failed to save file: ${error}`),
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFileName(String(file?.path || ""));
|
||||
setFileContent(String(file?.content || ""));
|
||||
setIsEditingMode(file === null);
|
||||
}, [file]);
|
||||
|
||||
const fileExtension = useMemo(() => {
|
||||
const fileNameStr = String(fileName || "");
|
||||
return fileNameStr.split(".").pop()?.toLowerCase() || "";
|
||||
}, [fileName]);
|
||||
|
||||
const isMarkdown = useMemo(() => {
|
||||
return fileExtension === "md" || fileExtension === "markdown";
|
||||
}, [fileExtension]);
|
||||
|
||||
const language = useMemo(() => {
|
||||
return LANGUAGE_MAP[fileExtension] || "text";
|
||||
}, [fileExtension]);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
if (fileContent) {
|
||||
navigator.clipboard.writeText(fileContent);
|
||||
}
|
||||
}, [fileContent]);
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
if (fileContent && fileName) {
|
||||
const blob = new Blob([fileContent], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}, [fileContent, fileName]);
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
setIsEditingMode(true);
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
if (file === null) {
|
||||
onClose();
|
||||
} else {
|
||||
setFileName(String(file.path));
|
||||
setFileContent(String(file.content));
|
||||
setIsEditingMode(false);
|
||||
}
|
||||
}, [file, onClose]);
|
||||
|
||||
const fileNameIsValid = useMemo(() => {
|
||||
return (
|
||||
fileName.trim() !== "" &&
|
||||
!fileName.includes("/") &&
|
||||
!fileName.includes(" ")
|
||||
);
|
||||
}, [fileName]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={true}
|
||||
onOpenChange={onClose}
|
||||
>
|
||||
<DialogContent className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6">
|
||||
<DialogTitle className="sr-only">
|
||||
{file?.path || "New File"}
|
||||
</DialogTitle>
|
||||
<div className="mb-4 flex items-center justify-between border-b border-border pb-4">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<FileText className="text-primary/50 h-5 w-5 shrink-0" />
|
||||
{isEditingMode && file === null ? (
|
||||
<Input
|
||||
value={fileName}
|
||||
onChange={(e) => setFileName(e.target.value)}
|
||||
placeholder="Enter filename..."
|
||||
className="text-base font-medium"
|
||||
aria-invalid={!fileNameIsValid}
|
||||
/>
|
||||
) : (
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-base font-medium text-primary">
|
||||
{file?.path}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{!isEditingMode && (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleEdit}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
disabled={editDisabled}
|
||||
>
|
||||
<Edit
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCopy}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
>
|
||||
<Copy
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
Copy
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
>
|
||||
<Download
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
Download
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-hidden">
|
||||
{isEditingMode ? (
|
||||
<Textarea
|
||||
value={fileContent}
|
||||
onChange={(e) => setFileContent(e.target.value)}
|
||||
placeholder="Enter file content..."
|
||||
className="h-full min-h-[400px] resize-none font-mono text-sm"
|
||||
/>
|
||||
) : (
|
||||
<ScrollArea className="bg-surface h-full rounded-md">
|
||||
<div className="p-4">
|
||||
{fileContent ? (
|
||||
isMarkdown ? (
|
||||
<div className="rounded-md p-6">
|
||||
<MarkdownContent content={fileContent} />
|
||||
</div>
|
||||
) : (
|
||||
<SyntaxHighlighter
|
||||
language={language}
|
||||
style={oneDark}
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
borderRadius: "0.5rem",
|
||||
fontSize: "0.875rem",
|
||||
}}
|
||||
showLineNumbers
|
||||
wrapLines={true}
|
||||
lineProps={{
|
||||
style: {
|
||||
whiteSpace: "pre-wrap",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{fileContent}
|
||||
</SyntaxHighlighter>
|
||||
)
|
||||
) : (
|
||||
<div className="flex items-center justify-center p-12">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
File is empty
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
{isEditingMode && (
|
||||
<div className="mt-4 flex justify-end gap-2 border-t border-border pt-4">
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<X
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => fileUpdate.trigger()}
|
||||
size="sm"
|
||||
disabled={
|
||||
fileUpdate.isMutating ||
|
||||
!fileName.trim() ||
|
||||
!fileContent.trim() ||
|
||||
!fileNameIsValid
|
||||
}
|
||||
>
|
||||
{fileUpdate.isMutating ? (
|
||||
<Loader2
|
||||
size={16}
|
||||
className="mr-1 animate-spin"
|
||||
/>
|
||||
) : (
|
||||
<Save
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
)}
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
FileViewDialog.displayName = "FileViewDialog";
|
||||
Reference in New Issue
Block a user