Add virtual folder system that surfaces Skills, Memories, and Chat sessions in the workspace sidebar alongside real dench files. Rearchitect the home page into a landing hub and move the ChatPanel into the workspace as the default view. New API route — virtual-file: - apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path traversal protection and directory allowlisting. Tree API — virtual folder builders: - apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and ~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name + emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily logs from ~/.openclaw/workspace/memory/. Virtual folders are appended after real workspace entries and are also returned when no dench root exists. File manager tree — virtual node awareness: - apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode() helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual nodes show a lock badge, disable drag-and-drop, block rename/delete, and reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/ paths. Markdown editor — virtual path routing: - apps/web/app/components/workspace/markdown-editor.tsx: save to /api/workspace/virtual-file for paths starting with ~ instead of the regular /api/workspace/file endpoint. Home page redesign: - apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel) with a branded landing page showing the OpenClaw Dench heading, tagline, and a single "Open Workspace" CTA linking to /workspace. Workspace page — unified layout with integrated chat: - apps/web/app/workspace/page.tsx: ChatPanel is now the default main view when no file is selected. Add session fetching from /api/web-sessions and build an enhanced tree with a virtual "Chats" folder listing all sessions. Clicking ~chats/<id> loads the session; clicking ~chats starts a new one. Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a "Back to chat" button in the top bar alongside the chat sidebar toggle. Sidebar + empty-state cosmetic updates: - apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon to HomeIcon (house SVG), change label from "Back to Chat" to "Home". - apps/web/app/components/workspace/empty-state.tsx: update link text from "Back to Chat" to "Back to Home".
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { FileManagerTree, type TreeNode } from "./file-manager-tree";
|
|
|
|
type WorkspaceSidebarProps = {
|
|
tree: TreeNode[];
|
|
activePath: string | null;
|
|
onSelect: (node: TreeNode) => void;
|
|
onRefresh: () => void;
|
|
orgName?: string;
|
|
loading?: boolean;
|
|
};
|
|
|
|
function WorkspaceLogo() {
|
|
return (
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect width="7" height="7" x="3" y="3" rx="1" />
|
|
<rect width="7" height="7" x="14" y="3" rx="1" />
|
|
<rect width="7" height="7" x="14" y="14" rx="1" />
|
|
<rect width="7" height="7" x="3" y="14" rx="1" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function HomeIcon() {
|
|
return (
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
<polyline points="9 22 9 12 15 12 15 22" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export function WorkspaceSidebar({
|
|
tree,
|
|
activePath,
|
|
onSelect,
|
|
onRefresh,
|
|
orgName,
|
|
loading,
|
|
}: WorkspaceSidebarProps) {
|
|
return (
|
|
<aside
|
|
className="flex flex-col h-screen border-r flex-shrink-0"
|
|
style={{
|
|
width: "260px",
|
|
background: "var(--color-surface)",
|
|
borderColor: "var(--color-border)",
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<div
|
|
className="flex items-center gap-2.5 px-4 py-3 border-b"
|
|
style={{ borderColor: "var(--color-border)" }}
|
|
>
|
|
<span style={{ color: "var(--color-accent)" }}>
|
|
<WorkspaceLogo />
|
|
</span>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm font-medium truncate" style={{ color: "var(--color-text)" }}>
|
|
{orgName || "Workspace"}
|
|
</div>
|
|
<div className="text-xs" style={{ color: "var(--color-text-muted)" }}>
|
|
Dench CRM
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section label */}
|
|
<div
|
|
className="px-4 pt-4 pb-1 text-[11px] font-medium uppercase tracking-wider"
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
>
|
|
Knowledge
|
|
</div>
|
|
|
|
{/* Tree (includes real files + virtual Skills, Memories, Chats folders) */}
|
|
<div className="flex-1 overflow-y-auto px-1">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div
|
|
className="w-5 h-5 border-2 rounded-full animate-spin"
|
|
style={{
|
|
borderColor: "var(--color-border)",
|
|
borderTopColor: "var(--color-accent)",
|
|
}}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<FileManagerTree
|
|
tree={tree}
|
|
activePath={activePath}
|
|
onSelect={onSelect}
|
|
onRefresh={onRefresh}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div
|
|
className="px-3 py-2.5 border-t"
|
|
style={{ borderColor: "var(--color-border)" }}
|
|
>
|
|
<a
|
|
href="/"
|
|
className="flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors"
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
onMouseEnter={(e) => {
|
|
(e.currentTarget as HTMLElement).style.background =
|
|
"var(--color-surface-hover)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
(e.currentTarget as HTMLElement).style.background = "transparent";
|
|
}}
|
|
>
|
|
<HomeIcon />
|
|
Home
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|