Files
CC-Switch/src/components/workspace/WorkspaceFilesPanel.tsx
T
Jason 9035784aa4 feat(openclaw): extend workspace files with HEARTBEAT/BOOTSTRAP/BOOT
Add 3 new markdown files to the workspace whitelist and frontend grid:
- HEARTBEAT.md: activity runlist
- BOOTSTRAP.md: first-run ritual
- BOOT.md: gateway restart checklist

Update i18n for all 3 locales (zh, en, ja).
2026-02-14 15:32:17 +08:00

130 lines
3.8 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
FileCode,
Heart,
User,
IdCard,
Wrench,
Brain,
Activity,
Rocket,
Power,
CheckCircle2,
Circle,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { workspaceApi } from "@/lib/api/workspace";
import WorkspaceFileEditor from "./WorkspaceFileEditor";
interface WorkspaceFile {
filename: string;
icon: LucideIcon;
descKey: string;
}
const WORKSPACE_FILES: WorkspaceFile[] = [
{ filename: "AGENTS.md", icon: FileCode, descKey: "workspace.files.agents" },
{ filename: "SOUL.md", icon: Heart, descKey: "workspace.files.soul" },
{ filename: "USER.md", icon: User, descKey: "workspace.files.user" },
{
filename: "IDENTITY.md",
icon: IdCard,
descKey: "workspace.files.identity",
},
{ filename: "TOOLS.md", icon: Wrench, descKey: "workspace.files.tools" },
{ filename: "MEMORY.md", icon: Brain, descKey: "workspace.files.memory" },
{
filename: "HEARTBEAT.md",
icon: Activity,
descKey: "workspace.files.heartbeat",
},
{
filename: "BOOTSTRAP.md",
icon: Rocket,
descKey: "workspace.files.bootstrap",
},
{ filename: "BOOT.md", icon: Power, descKey: "workspace.files.boot" },
];
const WorkspaceFilesPanel: React.FC = () => {
const { t } = useTranslation();
const [editingFile, setEditingFile] = useState<string | null>(null);
const [fileExists, setFileExists] = useState<Record<string, boolean>>({});
const checkFileExistence = async () => {
const results: Record<string, boolean> = {};
await Promise.all(
WORKSPACE_FILES.map(async (f) => {
try {
const content = await workspaceApi.readFile(f.filename);
results[f.filename] = content !== null;
} catch {
results[f.filename] = false;
}
}),
);
setFileExists(results);
};
useEffect(() => {
void checkFileExistence();
}, []);
const handleEditorClose = () => {
setEditingFile(null);
// Re-check file existence after closing editor (file may have been created)
void checkFileExistence();
};
return (
<div className="px-6 pt-4 pb-8">
<p className="text-sm text-muted-foreground mb-6">
~/.openclaw/workspace/
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{WORKSPACE_FILES.map((file) => {
const Icon = file.icon;
const exists = fileExists[file.filename];
return (
<button
key={file.filename}
onClick={() => setEditingFile(file.filename)}
className="flex items-start gap-3 p-4 rounded-xl border border-border bg-card hover:bg-accent/50 transition-colors text-left group"
>
<div className="mt-0.5 text-muted-foreground group-hover:text-foreground transition-colors">
<Icon className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-medium text-sm text-foreground">
{file.filename}
</span>
{exists ? (
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-500 flex-shrink-0" />
) : (
<Circle className="w-3.5 h-3.5 text-muted-foreground/40 flex-shrink-0" />
)}
</div>
<p className="text-xs text-muted-foreground mt-0.5">
{t(file.descKey)}
</p>
</div>
</button>
);
})}
</div>
<WorkspaceFileEditor
filename={editingFile ?? ""}
isOpen={!!editingFile}
onClose={handleEditorClose}
/>
</div>
);
};
export default WorkspaceFilesPanel;