Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EyeOffIcon,
ZapIcon,
FolderIcon,
GitBranchIcon,
GitMergeIcon,
GitPullRequestIcon,
XCircleIcon,
Expand Down Expand Up @@ -121,6 +122,7 @@ import {
resolvePackageManagerResolution,
} from "~/projectScriptDefaults";
import { useClientMode } from "~/hooks/useClientMode";
import { CloneRepositoryDialog } from "~/components/CloneRepositoryDialog";
import { getProjectColor } from "~/projectColors";
import type { Thread } from "../types";

Expand Down Expand Up @@ -398,6 +400,7 @@ export default function Sidebar() {
const [isAddingProject, setIsAddingProject] = useState(false);
const [addProjectError, setAddProjectError] = useState<string | null>(null);
const [manualProjectPathEntry, setManualProjectPathEntry] = useState(false);
const [cloneDialogOpen, setCloneDialogOpen] = useState(false);
const addProjectInputRef = useRef<HTMLInputElement | null>(null);
const [expandedThreadListsByProject, setExpandedThreadListsByProject] = useState<
ReadonlySet<ProjectId>
Expand Down Expand Up @@ -680,6 +683,13 @@ export default function Sidebar() {
setAddingProject((prev) => !prev);
};

const handleCloneComplete = useCallback(
async (result: { path: string; branch: string; repoName: string }) => {
await addProjectFromPath(result.path);
},
[addProjectFromPath],
);

/**
* Delete a single thread: stop session, close terminal, dispatch delete,
* clean up drafts/state, and optionally remove orphaned worktree.
Expand Down Expand Up @@ -2044,6 +2054,15 @@ export default function Sidebar() {
<FolderIcon className="size-3.5" />
{isPickingFolder ? "Picking folder..." : "Browse for folder"}
</button>
<button
type="button"
className="mb-1.5 flex w-full items-center justify-center gap-2 rounded-md border border-border bg-secondary py-1.5 text-xs text-foreground/80 transition-[background-color,border-color,color] duration-150 ease-out hover:border-border/80 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => setCloneDialogOpen(true)}
disabled={isAddingProject}
>
<GitBranchIcon className="size-3.5" />
Clone from GitHub
</button>
{!manualProjectPathEntry && (
<button
type="button"
Expand Down Expand Up @@ -2226,6 +2245,12 @@ export default function Sidebar() {
)}
</SidebarMenu>
</SidebarFooter>

<CloneRepositoryDialog
open={cloneDialogOpen}
onOpenChange={setCloneDialogOpen}
onCloned={handleCloneComplete}
/>
</>
);
}
50 changes: 50 additions & 0 deletions apps/web/src/components/home/ChatHomeEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { serverConfigQueryOptions } from "../../lib/serverReactQuery";
import { newCommandId, newProjectId } from "../../lib/utils";
import { readNativeApi } from "../../nativeApi";
import { useStore } from "../../store";
import { CloneRepositoryDialog } from "../CloneRepositoryDialog";
import { sortProjectsForSidebar } from "../Sidebar.logic";
import { ProviderSetupCard } from "../chat/ProviderSetupCard";
import { SidebarTrigger, useSidebar } from "../ui/sidebar";
Expand All @@ -36,6 +37,7 @@ export function ChatHomeEmptyState() {
const threads = useStore((store) => store.threads);
const { handleNewThread } = useHandleNewThread();
const [isOpeningProject, setIsOpeningProject] = useState(false);
const [cloneDialogOpen, setCloneDialogOpen] = useState(false);
const { open: sidebarOpen, isMobile: sidebarIsMobile } = useSidebar();

const latestProject = useMemo(
Expand Down Expand Up @@ -129,6 +131,47 @@ export function ChatHomeEmptyState() {
setIsOpeningProject(false);
}, [appSettings.defaultThreadEnvMode, handleNewThread, isOpeningProject, projects]);

const handleCloneComplete = useCallback(
async (result: { path: string; branch: string; repoName: string }) => {
const api = readNativeApi();
if (!api) return;

const existingProject = projects.find((project) => project.cwd === result.path);
if (existingProject) {
await handleNewThread(existingProject.id, {
envMode: appSettings.defaultThreadEnvMode,
}).catch(() => undefined);
return;
}

const projectId = newProjectId();
try {
await api.orchestration.dispatchCommand({
type: "project.create",
commandId: newCommandId(),
projectId,
title: result.repoName,
workspaceRoot: result.path,
defaultModel: DEFAULT_MODEL_BY_PROVIDER.codex,
createdAt: new Date().toISOString(),
});
await handleNewThread(projectId, {
envMode: appSettings.defaultThreadEnvMode,
}).catch(() => undefined);
} catch (error) {
toastManager.add({
type: "error",
title: "Failed to add project",
description:
error instanceof Error
? error.message
: "An unexpected error occurred while adding the project.",
});
}
},
[appSettings.defaultThreadEnvMode, handleNewThread, projects],
);

const startLatestThread = useCallback(async () => {
if (!latestProject) {
await openProjectFolder();
Expand Down Expand Up @@ -175,9 +218,16 @@ export function ChatHomeEmptyState() {
isOpeningProject={isOpeningProject}
onNewThread={() => void startLatestThread()}
onOpenFolder={() => void openProjectFolder()}
onCloneRepo={() => setCloneDialogOpen(true)}
onSettings={() => void navigate({ to: "/settings" })}
/>

<CloneRepositoryDialog
open={cloneDialogOpen}
onOpenChange={setCloneDialogOpen}
onCloned={handleCloneComplete}
/>

<HomeProviderStatus
providers={providers}
onSettingsClick={() => void navigate({ to: "/settings" })}
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/components/home/HomeActions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FolderOpenIcon, SettingsIcon, TerminalSquareIcon } from "lucide-react";
import { FolderOpenIcon, GitBranchIcon, SettingsIcon, TerminalSquareIcon } from "lucide-react";

import { Button } from "../ui/button";

Expand All @@ -7,6 +7,7 @@ interface HomeActionsProps {
isOpeningProject: boolean;
onNewThread: () => void;
onOpenFolder: () => void;
onCloneRepo: () => void;
onSettings: () => void;
}

Expand All @@ -15,6 +16,7 @@ export function HomeActions({
isOpeningProject,
onNewThread,
onOpenFolder,
onCloneRepo,
onSettings,
}: HomeActionsProps) {
return (
Expand All @@ -27,6 +29,10 @@ export function HomeActions({
<FolderOpenIcon className="size-4" />
{isOpeningProject ? "Opening..." : "Open folder"}
</Button>
<Button variant="outline" onClick={onCloneRepo}>
<GitBranchIcon className="size-4" />
Clone repo
</Button>
<Button variant="ghost" onClick={onSettings}>
<SettingsIcon className="size-4" />
Settings
Expand Down
Loading