openclaw/apps/web/app/components/workspace/workspace-sidebar.tsx
kumarabhirup 6d8623b00f
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
  rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
  copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
  events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
  (compact mode), add workspace refresh callback

Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
  (resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
  incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
  PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
  effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
  components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
  selector dropdown and relation/reverse-relation badge counts

Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
  with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
  context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists

Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00

122 lines
3.4 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 BackIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m12 19-7-7 7-7" /><path d="M19 12H5" />
</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 */}
<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";
}}
>
<BackIcon />
Back to Chat
</a>
</div>
</aside>
);
}