diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index a8d43b60..c9f90780 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -24,6 +24,7 @@ const ArchivedPage = lazy(() => import("@/pages/chat/archived-page")); const AgentsPage = lazy(() => import("@/pages/agents/agents-page")); const AgentNewPage = lazy(() => import("@/pages/agents/agent-new")); const AgentDetailPage = lazy(() => import("@/pages/agents/agent-detail")); +const FilesPage = lazy(() => import("@/pages/files/files-page")); const AutomationsPage = lazy(() => import("@/pages/automations/automations-page")); const PluginsPage = lazy(() => import("@/pages/plugins/plugins-page")); const ActivityPage = lazy(() => import("@/pages/activity/activity-page")); @@ -202,6 +203,8 @@ export function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/apps/desktop/src/components/agents/detail/memory-tab.tsx b/apps/desktop/src/components/agents/detail/memory-tab.tsx index 256e136a..5901ac8f 100644 --- a/apps/desktop/src/components/agents/detail/memory-tab.tsx +++ b/apps/desktop/src/components/agents/detail/memory-tab.tsx @@ -1,34 +1,14 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo, useState } from "react"; import { Link } from "react-router"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { toast } from "sonner"; -import { formatDistanceToNowStrict } from "date-fns"; -import { - Brain, - ChevronRight, - Eye, - FileCode2, - FileJson, - FileText, - Folder, - FolderOpen, - Lock, - Moon, - PencilLine, - RotateCcw, - Save, -} from "lucide-react"; +import { ChevronRight, Folder, FolderOpen, FolderSearch } from "lucide-react"; import type { Agent, AgentFileEntry } from "@godmode/shared"; -import { api, ApiRequestError, errorMessage } from "@/lib/api"; +import { api, errorMessage } from "@/lib/api"; import { qk } from "@/lib/queryKeys"; -import { isMac, modKey } from "@/lib/desktop"; import { cn } from "@/lib/utils"; -import { Kbd } from "@/components/common"; -import { Markdown } from "@/components/chat/markdown"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; -import { Spinner } from "@/components/ui/spinner"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { AlertDialog, AlertDialogAction, @@ -39,39 +19,11 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; +import { FileEditor, fileIcon, nameOf, parentOf } from "@/components/files/file-editor"; import { DreamsPanel, useDreamingNow } from "./dreams-panel"; -import { BrokenLinks, brokenMemoryLinks, MemoryBudget, memoryLinkTarget, memorySections, SectionMenu, type MemorySection } from "./memory-editor-parts"; const PINNED = ["MEMORY.md", "CLAUDE.md"]; const HIDDEN = new Set([".git"]); -const MAX_EDITABLE = 512 * 1024; - -/** Why a file can't be edited here (null = editable). */ -function readOnlyReason(path: string, size: number): string | null { - if (path === "CLAUDE.md") return "Generated from the agent's settings — edit the instructions in Settings instead."; - if (path.startsWith("state/")) return "Snapshot written by Godmode — changes would be overwritten."; - if (path.startsWith("runs/")) return "Raw run log — read-only."; - if (path.startsWith("conversations/")) return "Conversation transcript — read-only."; - if (size > MAX_EDITABLE) return "This file is too large to edit here."; - return null; -} - -/** MEMORY.md and memory/** — a running dream owns these (the core refuses edits meanwhile). */ -function isMemoryPath(path: string) { - return path === "MEMORY.md" || path.startsWith("memory/"); -} - -/** The file changed on the server since the draft started — saving would overwrite someone else's edit. */ -class StaleDraftError extends Error {} - -function parentOf(path: string) { - const i = path.lastIndexOf("/"); - return i === -1 ? "" : path.slice(0, i); -} - -function nameOf(path: string) { - return path.slice(path.lastIndexOf("/") + 1); -} /** Normalise listing entries to repo-relative paths directly inside `dir`. */ function childrenOf(entries: AgentFileEntry[], dir: string): AgentFileEntry[] { @@ -96,20 +48,6 @@ function childrenOf(entries: AgentFileEntry[], dir: string): AgentFileEntry[] { }); } -function fileIcon(path: string) { - if (path === "MEMORY.md") return ; - if (path === "CLAUDE.md") return ; - if (/\.(json|jsonl)$/.test(path)) return ; - if (/\.(ts|tsx|js|py|sh|yaml|yml|toml)$/.test(path)) return ; - return ; -} - -function formatSize(n: number) { - if (n < 1024) return `${n} B`; - if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; - return `${(n / 1024 / 1024).toFixed(1)} MB`; -} - export function MemoryTab({ agent }: { agent: Agent }) { const [selected, setSelected] = useState<{ path: string; size: number } | null>({ path: "MEMORY.md", size: 0 }); const [expanded, setExpanded] = useState>(() => new Set(["memory"])); @@ -140,11 +78,23 @@ export function MemoryTab({ agent }: { agent: Agent }) {