File size: 4,721 Bytes
f59fbe2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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>
  );
}