kimi-code / apps /vscode /webview-ui /src /components /WorkDirModal.tsx
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f59fbe2 verified
Raw
History Blame Contribute Delete
4.72 kB
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { IconFolder, IconFolderOpen, IconCheck, IconHome } from "@tabler/icons-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { useSettingsStore, useChatStore } from "@/stores";
import { bridge } from "@/services";
import { cn } from "@/lib/utils";
export function WorkDirModal() {
const { workDirModalOpen, setWorkDirModalOpen, currentWorkDir, workspaceRoot, setCurrentWorkDir } = useSettingsStore();
const { startNewConversation } = useChatStore();
const [loading, setLoading] = useState(false);
const { data: workDirs = [], isPending, isError } = useQuery({
queryKey: ["registeredWorkDirs"],
queryFn: () => bridge.getRegisteredWorkDirs(),
enabled: workDirModalOpen,
});
const handleSelect = async (dir: string | null) => {
setLoading(true);
try {
const result = await bridge.setWorkDir(dir);
if (result.ok) {
setCurrentWorkDir(result.workDir === workspaceRoot ? null : result.workDir);
await startNewConversation();
setWorkDirModalOpen(false);
}
} finally {
setLoading(false);
}
};
const handleBrowse = async () => {
setLoading(true);
try {
const result = await bridge.browseWorkDir();
if (result.ok && result.workDir) {
setCurrentWorkDir(result.workDir === workspaceRoot ? null : result.workDir);
await startNewConversation();
setWorkDirModalOpen(false);
}
} finally {
setLoading(false);
}
};
const displayPath = (fullPath: string) => {
if (!workspaceRoot) return fullPath;
if (fullPath === workspaceRoot) return fullPath.split("/").pop() || fullPath;
return fullPath.replace(workspaceRoot, ".");
};
const isSelected = (dir: string) => {
if (!currentWorkDir) return dir === workspaceRoot;
return dir === currentWorkDir;
};
return (
<Dialog open={workDirModalOpen} onOpenChange={setWorkDirModalOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Select Working Directory</DialogTitle>
</DialogHeader>
<div className="space-y-1 max-h-64 overflow-y-auto -mx-1 px-1">
{isPending ? (
<div className="px-3 py-8 text-center text-xs text-muted-foreground">Loading…</div>
) : isError ? (
<div className="px-3 py-8 text-center text-xs text-destructive">Failed to load working directories</div>
) : (
workDirs.map((dir) => (
<button
key={dir}
onClick={() => {
void handleSelect(dir === workspaceRoot ? null : dir);
}}
disabled={loading}
className={cn(
"w-full flex items-center gap-2 px-3 py-2 rounded-md text-left text-sm transition-colors",
isSelected(dir) ? "bg-accent" : "hover:bg-accent/50",
loading && "opacity-50 cursor-not-allowed",
)}
>
{dir === workspaceRoot ? (
<IconHome className="size-4 text-muted-foreground shrink-0" />
) : (
<IconFolder className="size-4 text-muted-foreground shrink-0" />
)}
<span className="flex-1 truncate">{displayPath(dir)}</span>
{isSelected(dir) && <IconCheck className="size-4 text-blue-500 shrink-0" />}
{dir === workspaceRoot && <span className="text-xs text-muted-foreground">(root)</span>}
</button>
))
)}
</div>
<DialogFooter className="sm:justify-between gap-2">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => {
void handleBrowse();
}}
disabled={loading}
>
<IconFolderOpen className="size-4 mr-1.5" />
Browse...
</Button>
{currentWorkDir && (
<Button
variant="outline"
size="sm"
onClick={() => {
void handleSelect(null);
}}
disabled={loading}
>
<IconHome className="size-4 mr-1.5" />
Reset
</Button>
)}
</div>
<Button variant="ghost" size="sm" onClick={() => setWorkDirModalOpen(false)}>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}