Improve workspace sidebar and file panel

This commit is contained in:
武阳
2026-05-06 17:46:49 +08:00
parent d9a8110b7c
commit 93a0eb74d7
6 changed files with 702 additions and 83 deletions
+52 -3
View File
@@ -1,6 +1,11 @@
import { readFile, stat } from "node:fs/promises";
import { readdir, readFile, stat } from "node:fs/promises";
import path from "node:path";
import { accountBaseRoot, getCurrentAccount } from "@/lib/claw-auth";
import {
accountBaseRoot,
accountSessionInputRoot,
accountSessionOutputRoot,
getCurrentAccount,
} from "@/lib/claw-auth";
export const runtime = "nodejs";
@@ -11,8 +16,15 @@ export async function GET(req: Request) {
const url = new URL(req.url);
const requestedPath = url.searchParams.get("path");
const sessionId = url.searchParams.get("session_id");
if (!requestedPath && sessionId) {
return Response.json({
input: await listSessionFiles(account.id, sessionId, "input"),
output: await listSessionFiles(account.id, sessionId, "output"),
});
}
if (!requestedPath) {
return Response.json({ error: "缺少文件路径" }, { status: 400 });
return Response.json({ error: "缺少文件路径或会话 ID" }, { status: 400 });
}
const accountRoot = path.resolve(accountBaseRoot(account.id));
@@ -38,6 +50,43 @@ export async function GET(req: Request) {
}
}
async function listSessionFiles(
accountId: string,
sessionId: string,
kind: "input" | "output",
) {
const accountRoot = path.resolve(accountBaseRoot(accountId));
const dir =
kind === "input"
? accountSessionInputRoot(accountId, sessionId)
: accountSessionOutputRoot(accountId, sessionId);
const resolvedDir = path.resolve(dir);
if (!resolvedDir.startsWith(`${accountRoot}${path.sep}`)) return [];
try {
const entries = await readdir(resolvedDir, { withFileTypes: true });
const files = await Promise.all(
entries
.filter((entry) => entry.isFile())
.map(async (entry) => {
const filePath = path.join(resolvedDir, entry.name);
const fileStat = await stat(filePath);
return {
name: entry.name,
path: filePath,
kind,
size: fileStat.size,
modified_at: fileStat.mtime.toISOString(),
download_url: `/api/claw/files?path=${encodeURIComponent(filePath)}`,
};
}),
);
return files.sort((a, b) => b.modified_at.localeCompare(a.modified_at));
} catch {
return [];
}
}
function contentTypeFor(filePath: string) {
const ext = path.extname(filePath).toLowerCase();
if (ext === ".json") return "application/json; charset=utf-8";
+2 -21
View File
@@ -14,16 +14,9 @@ import {
} from "@/components/assistant-ui/activity-panel";
import { Thread } from "@/components/assistant-ui/thread";
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
import { Separator } from "@/components/ui/separator";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { ClawSessionReplayProvider } from "@/lib/claw-session-replay";
import { ClawAccountGate, useClawAccount } from "./claw-account-gate";
import { ClawLlmSettings } from "./claw-llm-settings";
import { ClawThemeSwitcher } from "./claw-theme-switcher";
export const Assistant = () => {
const { account, isLoading, setAccount } = useClawAccount();
@@ -83,24 +76,12 @@ export const Assistant = () => {
<ClawSessionReplayProvider value={{ replaySession }}>
<ActivityProvider>
<SidebarProvider>
<div className="flex h-dvh w-full pr-0.5">
<div className="flex h-dvh w-full">
<ThreadListSidebar
account={account}
onLogout={() => setAccount(null)}
/>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
<div className="min-w-0 flex-1">
<div className="truncate font-medium text-sm"></div>
<div className="text-muted-foreground text-xs">
ZK Data Agent
</div>
</div>
<ClawThemeSwitcher />
<ClawLlmSettings />
</header>
<div className="flex min-h-0 flex-1 overflow-hidden">
<div className="min-w-0 flex-1 overflow-hidden">
<Thread />