mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
🗂️ refactor: implement Phase 2 folder-pattern compliance
- Move dialogs to src/components/dialogs/ folder: - delete-session-dialog.tsx - delete-all-archived-sessions-dialog.tsx - new-project-modal.tsx - workspace-picker-modal.tsx - Update all imports to reference new dialog locations - Create barrel export (index.ts) for board-view/components/kanban-card/ - Create barrel exports (index.ts) for all 11 settings-view subfolders: - api-keys/, api-keys/hooks/, appearance/, audio/, cli-status/ - components/, config/, danger-zone/, feature-defaults/ - keyboard-shortcuts/, shared/ This is Phase 2 of folder-pattern.md compliance: organizing dialogs and establishing consistent barrel export patterns across all view subfolders. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -6,9 +5,9 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button';
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
interface DeleteAllArchivedSessionsDialogProps {
|
interface DeleteAllArchivedSessionsDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -29,8 +28,7 @@ export function DeleteAllArchivedSessionsDialog({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Delete All Archived Sessions</DialogTitle>
|
<DialogTitle>Delete All Archived Sessions</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Are you sure you want to delete all archived sessions? This action
|
Are you sure you want to delete all archived sessions? This action cannot be undone.
|
||||||
cannot be undone.
|
|
||||||
{archivedCount > 0 && (
|
{archivedCount > 0 && (
|
||||||
<span className="block mt-2 text-yellow-500">
|
<span className="block mt-2 text-yellow-500">
|
||||||
{archivedCount} session(s) will be deleted.
|
{archivedCount} session(s) will be deleted.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MessageSquare } from "lucide-react";
|
import { MessageSquare } from 'lucide-react';
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
|
||||||
import type { SessionListItem } from "@/types/electron";
|
import type { SessionListItem } from '@/types/electron';
|
||||||
|
|
||||||
interface DeleteSessionDialogProps {
|
interface DeleteSessionDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -38,12 +38,8 @@ export function DeleteSessionDialog({
|
|||||||
<MessageSquare className="w-5 h-5 text-brand-500" />
|
<MessageSquare className="w-5 h-5 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="font-medium text-foreground truncate">
|
<p className="font-medium text-foreground truncate">{session.name}</p>
|
||||||
{session.name}
|
<p className="text-xs text-muted-foreground">{session.messageCount} messages</p>
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{session.messageCount} messages
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
export { BoardBackgroundModal } from './board-background-modal';
|
export { BoardBackgroundModal } from './board-background-modal';
|
||||||
|
export { DeleteAllArchivedSessionsDialog } from './delete-all-archived-sessions-dialog';
|
||||||
|
export { DeleteSessionDialog } from './delete-session-dialog';
|
||||||
export { FileBrowserDialog } from './file-browser-dialog';
|
export { FileBrowserDialog } from './file-browser-dialog';
|
||||||
|
export { NewProjectModal } from './new-project-modal';
|
||||||
|
export { WorkspacePickerModal } from './workspace-picker-modal';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -6,13 +6,13 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button';
|
||||||
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
import { HotkeyButton } from '@/components/ui/hotkey-button';
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from '@/components/ui/label';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from '@/components/ui/badge';
|
||||||
import {
|
import {
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
@@ -22,15 +22,12 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
Link,
|
Link,
|
||||||
Folder,
|
Folder,
|
||||||
} from "lucide-react";
|
} from 'lucide-react';
|
||||||
import { starterTemplates, type StarterTemplate } from "@/lib/templates";
|
import { starterTemplates, type StarterTemplate } from '@/lib/templates';
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from '@/lib/utils';
|
||||||
import { useFileBrowser } from "@/contexts/file-browser-context";
|
import { useFileBrowser } from '@/contexts/file-browser-context';
|
||||||
import {
|
import { getDefaultWorkspaceDirectory, saveLastProjectDirectory } from '@/lib/workspace-config';
|
||||||
getDefaultWorkspaceDirectory,
|
|
||||||
saveLastProjectDirectory,
|
|
||||||
} from "@/lib/workspace-config";
|
|
||||||
|
|
||||||
interface ValidationErrors {
|
interface ValidationErrors {
|
||||||
projectName?: boolean;
|
projectName?: boolean;
|
||||||
@@ -42,20 +39,13 @@ interface ValidationErrors {
|
|||||||
interface NewProjectModalProps {
|
interface NewProjectModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
onCreateBlankProject: (
|
onCreateBlankProject: (projectName: string, parentDir: string) => Promise<void>;
|
||||||
projectName: string,
|
|
||||||
parentDir: string
|
|
||||||
) => Promise<void>;
|
|
||||||
onCreateFromTemplate: (
|
onCreateFromTemplate: (
|
||||||
template: StarterTemplate,
|
template: StarterTemplate,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
parentDir: string
|
parentDir: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
onCreateFromCustomUrl: (
|
onCreateFromCustomUrl: (repoUrl: string, projectName: string, parentDir: string) => Promise<void>;
|
||||||
repoUrl: string,
|
|
||||||
projectName: string,
|
|
||||||
parentDir: string
|
|
||||||
) => Promise<void>;
|
|
||||||
isCreating: boolean;
|
isCreating: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +57,13 @@ export function NewProjectModal({
|
|||||||
onCreateFromCustomUrl,
|
onCreateFromCustomUrl,
|
||||||
isCreating,
|
isCreating,
|
||||||
}: NewProjectModalProps) {
|
}: NewProjectModalProps) {
|
||||||
const [activeTab, setActiveTab] = useState<"blank" | "template">("blank");
|
const [activeTab, setActiveTab] = useState<'blank' | 'template'>('blank');
|
||||||
const [projectName, setProjectName] = useState("");
|
const [projectName, setProjectName] = useState('');
|
||||||
const [workspaceDir, setWorkspaceDir] = useState<string>("");
|
const [workspaceDir, setWorkspaceDir] = useState<string>('');
|
||||||
const [isLoadingWorkspace, setIsLoadingWorkspace] = useState(false);
|
const [isLoadingWorkspace, setIsLoadingWorkspace] = useState(false);
|
||||||
const [selectedTemplate, setSelectedTemplate] =
|
const [selectedTemplate, setSelectedTemplate] = useState<StarterTemplate | null>(null);
|
||||||
useState<StarterTemplate | null>(null);
|
|
||||||
const [useCustomUrl, setUseCustomUrl] = useState(false);
|
const [useCustomUrl, setUseCustomUrl] = useState(false);
|
||||||
const [customUrl, setCustomUrl] = useState("");
|
const [customUrl, setCustomUrl] = useState('');
|
||||||
const [errors, setErrors] = useState<ValidationErrors>({});
|
const [errors, setErrors] = useState<ValidationErrors>({});
|
||||||
const { openFileBrowser } = useFileBrowser();
|
const { openFileBrowser } = useFileBrowser();
|
||||||
|
|
||||||
@@ -89,7 +78,7 @@ export function NewProjectModal({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Failed to get default workspace directory:", error);
|
console.error('Failed to get default workspace directory:', error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoadingWorkspace(false);
|
setIsLoadingWorkspace(false);
|
||||||
@@ -100,11 +89,11 @@ export function NewProjectModal({
|
|||||||
// Reset form when modal closes
|
// Reset form when modal closes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setProjectName("");
|
setProjectName('');
|
||||||
setSelectedTemplate(null);
|
setSelectedTemplate(null);
|
||||||
setUseCustomUrl(false);
|
setUseCustomUrl(false);
|
||||||
setCustomUrl("");
|
setCustomUrl('');
|
||||||
setActiveTab("blank");
|
setActiveTab('blank');
|
||||||
setErrors({});
|
setErrors({});
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
@@ -117,10 +106,7 @@ export function NewProjectModal({
|
|||||||
}, [projectName, errors.projectName]);
|
}, [projectName, errors.projectName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if ((selectedTemplate || (useCustomUrl && customUrl)) && errors.templateSelection) {
|
||||||
(selectedTemplate || (useCustomUrl && customUrl)) &&
|
|
||||||
errors.templateSelection
|
|
||||||
) {
|
|
||||||
setErrors((prev) => ({ ...prev, templateSelection: false }));
|
setErrors((prev) => ({ ...prev, templateSelection: false }));
|
||||||
}
|
}
|
||||||
}, [selectedTemplate, useCustomUrl, customUrl, errors.templateSelection]);
|
}, [selectedTemplate, useCustomUrl, customUrl, errors.templateSelection]);
|
||||||
@@ -145,7 +131,7 @@ export function NewProjectModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check template selection (only for template tab)
|
// Check template selection (only for template tab)
|
||||||
if (activeTab === "template") {
|
if (activeTab === 'template') {
|
||||||
if (useCustomUrl) {
|
if (useCustomUrl) {
|
||||||
if (!customUrl.trim()) {
|
if (!customUrl.trim()) {
|
||||||
newErrors.customUrl = true;
|
newErrors.customUrl = true;
|
||||||
@@ -164,7 +150,7 @@ export function NewProjectModal({
|
|||||||
// Clear errors and proceed
|
// Clear errors and proceed
|
||||||
setErrors({});
|
setErrors({});
|
||||||
|
|
||||||
if (activeTab === "blank") {
|
if (activeTab === 'blank') {
|
||||||
await onCreateBlankProject(projectName, workspaceDir);
|
await onCreateBlankProject(projectName, workspaceDir);
|
||||||
} else if (useCustomUrl && customUrl) {
|
} else if (useCustomUrl && customUrl) {
|
||||||
await onCreateFromCustomUrl(customUrl, projectName, workspaceDir);
|
await onCreateFromCustomUrl(customUrl, projectName, workspaceDir);
|
||||||
@@ -181,7 +167,7 @@ export function NewProjectModal({
|
|||||||
const handleSelectTemplate = (template: StarterTemplate) => {
|
const handleSelectTemplate = (template: StarterTemplate) => {
|
||||||
setSelectedTemplate(template);
|
setSelectedTemplate(template);
|
||||||
setUseCustomUrl(false);
|
setUseCustomUrl(false);
|
||||||
setCustomUrl("");
|
setCustomUrl('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleCustomUrl = () => {
|
const handleToggleCustomUrl = () => {
|
||||||
@@ -193,9 +179,8 @@ export function NewProjectModal({
|
|||||||
|
|
||||||
const handleBrowseDirectory = async () => {
|
const handleBrowseDirectory = async () => {
|
||||||
const selectedPath = await openFileBrowser({
|
const selectedPath = await openFileBrowser({
|
||||||
title: "Select Base Project Directory",
|
title: 'Select Base Project Directory',
|
||||||
description:
|
description: 'Choose the parent directory where your project will be created',
|
||||||
"Choose the parent directory where your project will be created",
|
|
||||||
initialPath: workspaceDir || undefined,
|
initialPath: workspaceDir || undefined,
|
||||||
});
|
});
|
||||||
if (selectedPath) {
|
if (selectedPath) {
|
||||||
@@ -211,15 +196,12 @@ export function NewProjectModal({
|
|||||||
|
|
||||||
// Use platform-specific path separator
|
// Use platform-specific path separator
|
||||||
const pathSep =
|
const pathSep =
|
||||||
typeof window !== "undefined" && (window as any).electronAPI
|
typeof window !== 'undefined' && (window as any).electronAPI
|
||||||
? navigator.platform.indexOf("Win") !== -1
|
? navigator.platform.indexOf('Win') !== -1
|
||||||
? "\\"
|
? '\\'
|
||||||
: "/"
|
: '/'
|
||||||
: "/";
|
: '/';
|
||||||
const projectPath =
|
const projectPath = workspaceDir && projectName ? `${workspaceDir}${pathSep}${projectName}` : '';
|
||||||
workspaceDir && projectName
|
|
||||||
? `${workspaceDir}${pathSep}${projectName}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
@@ -228,9 +210,7 @@ export function NewProjectModal({
|
|||||||
data-testid="new-project-modal"
|
data-testid="new-project-modal"
|
||||||
>
|
>
|
||||||
<DialogHeader className="pb-2">
|
<DialogHeader className="pb-2">
|
||||||
<DialogTitle className="text-foreground">
|
<DialogTitle className="text-foreground">Create New Project</DialogTitle>
|
||||||
Create New Project
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className="text-muted-foreground">
|
<DialogDescription className="text-muted-foreground">
|
||||||
Start with a blank project or choose from a starter template.
|
Start with a blank project or choose from a starter template.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -241,13 +221,9 @@ export function NewProjectModal({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="project-name"
|
htmlFor="project-name"
|
||||||
className={cn(
|
className={cn('text-foreground', errors.projectName && 'text-red-500')}
|
||||||
"text-foreground",
|
|
||||||
errors.projectName && "text-red-500"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
Project Name{" "}
|
Project Name {errors.projectName && <span className="text-red-500">*</span>}
|
||||||
{errors.projectName && <span className="text-red-500">*</span>}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="project-name"
|
id="project-name"
|
||||||
@@ -255,33 +231,31 @@ export function NewProjectModal({
|
|||||||
value={projectName}
|
value={projectName}
|
||||||
onChange={(e) => setProjectName(e.target.value)}
|
onChange={(e) => setProjectName(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-input text-foreground placeholder:text-muted-foreground",
|
'bg-input text-foreground placeholder:text-muted-foreground',
|
||||||
errors.projectName
|
errors.projectName
|
||||||
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
|
? 'border-red-500 focus:border-red-500 focus:ring-red-500/20'
|
||||||
: "border-border"
|
: 'border-border'
|
||||||
)}
|
)}
|
||||||
data-testid="project-name-input"
|
data-testid="project-name-input"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{errors.projectName && (
|
{errors.projectName && <p className="text-xs text-red-500">Project name is required</p>}
|
||||||
<p className="text-xs text-red-500">Project name is required</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Workspace Directory Display */}
|
{/* Workspace Directory Display */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 text-sm",
|
'flex items-center gap-2 text-sm',
|
||||||
errors.workspaceDir ? "text-red-500" : "text-muted-foreground"
|
errors.workspaceDir ? 'text-red-500' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Folder className="w-4 h-4 shrink-0" />
|
<Folder className="w-4 h-4 shrink-0" />
|
||||||
<span className="flex-1 min-w-0">
|
<span className="flex-1 min-w-0">
|
||||||
{isLoadingWorkspace ? (
|
{isLoadingWorkspace ? (
|
||||||
"Loading workspace..."
|
'Loading workspace...'
|
||||||
) : workspaceDir ? (
|
) : workspaceDir ? (
|
||||||
<>
|
<>
|
||||||
Will be created at:{" "}
|
Will be created at:{' '}
|
||||||
<code className="text-xs bg-muted px-1.5 py-0.5 rounded truncate">
|
<code className="text-xs bg-muted px-1.5 py-0.5 rounded truncate">
|
||||||
{projectPath || workspaceDir}
|
{projectPath || workspaceDir}
|
||||||
</code>
|
</code>
|
||||||
@@ -305,7 +279,7 @@ export function NewProjectModal({
|
|||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onValueChange={(v) => setActiveTab(v as "blank" | "template")}
|
onValueChange={(v) => setActiveTab(v as 'blank' | 'template')}
|
||||||
className="flex-1 flex flex-col overflow-hidden"
|
className="flex-1 flex flex-col overflow-hidden"
|
||||||
>
|
>
|
||||||
<TabsList className="w-full justify-start">
|
<TabsList className="w-full justify-start">
|
||||||
@@ -323,9 +297,8 @@ export function NewProjectModal({
|
|||||||
<TabsContent value="blank" className="mt-0">
|
<TabsContent value="blank" className="mt-0">
|
||||||
<div className="p-4 rounded-lg bg-muted/50 border border-border">
|
<div className="p-4 rounded-lg bg-muted/50 border border-border">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Create an empty project with the standard .automaker directory
|
Create an empty project with the standard .automaker directory structure. Perfect
|
||||||
structure. Perfect for starting from scratch or importing an
|
for starting from scratch or importing an existing codebase.
|
||||||
existing codebase.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@@ -342,18 +315,18 @@ export function NewProjectModal({
|
|||||||
{/* Preset Templates */}
|
{/* Preset Templates */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"space-y-3 rounded-lg p-1 -m-1",
|
'space-y-3 rounded-lg p-1 -m-1',
|
||||||
errors.templateSelection && "ring-2 ring-red-500/50"
|
errors.templateSelection && 'ring-2 ring-red-500/50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{starterTemplates.map((template) => (
|
{starterTemplates.map((template) => (
|
||||||
<div
|
<div
|
||||||
key={template.id}
|
key={template.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-4 rounded-lg border cursor-pointer transition-all",
|
'p-4 rounded-lg border cursor-pointer transition-all',
|
||||||
selectedTemplate?.id === template.id && !useCustomUrl
|
selectedTemplate?.id === template.id && !useCustomUrl
|
||||||
? "border-brand-500 bg-brand-500/10"
|
? 'border-brand-500 bg-brand-500/10'
|
||||||
: "border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50"
|
: 'border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50'
|
||||||
)}
|
)}
|
||||||
onClick={() => handleSelectTemplate(template)}
|
onClick={() => handleSelectTemplate(template)}
|
||||||
data-testid={`template-${template.id}`}
|
data-testid={`template-${template.id}`}
|
||||||
@@ -361,13 +334,10 @@ export function NewProjectModal({
|
|||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h4 className="font-medium text-foreground">
|
<h4 className="font-medium text-foreground">{template.name}</h4>
|
||||||
{template.name}
|
{selectedTemplate?.id === template.id && !useCustomUrl && (
|
||||||
</h4>
|
<Check className="w-4 h-4 text-brand-500" />
|
||||||
{selectedTemplate?.id === template.id &&
|
)}
|
||||||
!useCustomUrl && (
|
|
||||||
<Check className="w-4 h-4 text-brand-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mb-3">
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
{template.description}
|
{template.description}
|
||||||
@@ -376,11 +346,7 @@ export function NewProjectModal({
|
|||||||
{/* Tech Stack */}
|
{/* Tech Stack */}
|
||||||
<div className="flex flex-wrap gap-1.5 mb-3">
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
||||||
{template.techStack.slice(0, 6).map((tech) => (
|
{template.techStack.slice(0, 6).map((tech) => (
|
||||||
<Badge
|
<Badge key={tech} variant="secondary" className="text-xs">
|
||||||
key={tech}
|
|
||||||
variant="secondary"
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
{tech}
|
{tech}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
@@ -394,7 +360,7 @@ export function NewProjectModal({
|
|||||||
{/* Key Features */}
|
{/* Key Features */}
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
<span className="font-medium">Features: </span>
|
<span className="font-medium">Features: </span>
|
||||||
{template.features.slice(0, 3).join(" · ")}
|
{template.features.slice(0, 3).join(' · ')}
|
||||||
{template.features.length > 3 &&
|
{template.features.length > 3 &&
|
||||||
` · +${template.features.length - 3} more`}
|
` · +${template.features.length - 3} more`}
|
||||||
</div>
|
</div>
|
||||||
@@ -419,47 +385,38 @@ export function NewProjectModal({
|
|||||||
{/* Custom URL Option */}
|
{/* Custom URL Option */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-4 rounded-lg border cursor-pointer transition-all",
|
'p-4 rounded-lg border cursor-pointer transition-all',
|
||||||
useCustomUrl
|
useCustomUrl
|
||||||
? "border-brand-500 bg-brand-500/10"
|
? 'border-brand-500 bg-brand-500/10'
|
||||||
: "border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50"
|
: 'border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50'
|
||||||
)}
|
)}
|
||||||
onClick={handleToggleCustomUrl}
|
onClick={handleToggleCustomUrl}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Link className="w-4 h-4 text-muted-foreground" />
|
<Link className="w-4 h-4 text-muted-foreground" />
|
||||||
<h4 className="font-medium text-foreground">
|
<h4 className="font-medium text-foreground">Custom GitHub URL</h4>
|
||||||
Custom GitHub URL
|
{useCustomUrl && <Check className="w-4 h-4 text-brand-500" />}
|
||||||
</h4>
|
|
||||||
{useCustomUrl && (
|
|
||||||
<Check className="w-4 h-4 text-brand-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mb-3">
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
Clone any public GitHub repository as a starting point.
|
Clone any public GitHub repository as a starting point.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{useCustomUrl && (
|
{useCustomUrl && (
|
||||||
<div
|
<div onClick={(e) => e.stopPropagation()} className="space-y-1">
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
className="space-y-1"
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://github.com/username/repository"
|
placeholder="https://github.com/username/repository"
|
||||||
value={customUrl}
|
value={customUrl}
|
||||||
onChange={(e) => setCustomUrl(e.target.value)}
|
onChange={(e) => setCustomUrl(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-input text-foreground placeholder:text-muted-foreground",
|
'bg-input text-foreground placeholder:text-muted-foreground',
|
||||||
errors.customUrl
|
errors.customUrl
|
||||||
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
|
? 'border-red-500 focus:border-red-500 focus:ring-red-500/20'
|
||||||
: "border-border"
|
: 'border-border'
|
||||||
)}
|
)}
|
||||||
data-testid="custom-url-input"
|
data-testid="custom-url-input"
|
||||||
/>
|
/>
|
||||||
{errors.customUrl && (
|
{errors.customUrl && (
|
||||||
<p className="text-xs text-red-500">
|
<p className="text-xs text-red-500">GitHub URL is required</p>
|
||||||
GitHub URL is required
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -482,14 +439,14 @@ export function NewProjectModal({
|
|||||||
onClick={validateAndCreate}
|
onClick={validateAndCreate}
|
||||||
disabled={isCreating}
|
disabled={isCreating}
|
||||||
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
|
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
|
||||||
hotkey={{ key: "Enter", cmdCtrl: true }}
|
hotkey={{ key: 'Enter', cmdCtrl: true }}
|
||||||
hotkeyActive={open}
|
hotkeyActive={open}
|
||||||
data-testid="confirm-create-project"
|
data-testid="confirm-create-project"
|
||||||
>
|
>
|
||||||
{isCreating ? (
|
{isCreating ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
{activeTab === "template" ? "Cloning..." : "Creating..."}
|
{activeTab === 'template' ? 'Cloning...' : 'Creating...'}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>Create Project</>
|
<>Create Project</>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useState, useEffect, useCallback } from "react";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -7,10 +6,10 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button';
|
||||||
import { Folder, Loader2, FolderOpen, AlertCircle } from "lucide-react";
|
import { Folder, Loader2, FolderOpen, AlertCircle } from 'lucide-react';
|
||||||
import { getHttpApiClient } from "@/lib/http-api-client";
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
|
|
||||||
interface WorkspaceDirectory {
|
interface WorkspaceDirectory {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -23,11 +22,7 @@ interface WorkspacePickerModalProps {
|
|||||||
onSelect: (path: string, name: string) => void;
|
onSelect: (path: string, name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WorkspacePickerModal({
|
export function WorkspacePickerModal({ open, onOpenChange, onSelect }: WorkspacePickerModalProps) {
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
onSelect,
|
|
||||||
}: WorkspacePickerModalProps) {
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [directories, setDirectories] = useState<WorkspaceDirectory[]>([]);
|
const [directories, setDirectories] = useState<WorkspaceDirectory[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -43,10 +38,10 @@ export function WorkspacePickerModal({
|
|||||||
if (result.success && result.directories) {
|
if (result.success && result.directories) {
|
||||||
setDirectories(result.directories);
|
setDirectories(result.directories);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || "Failed to load directories");
|
setError(result.error || 'Failed to load directories');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load directories");
|
setError(err instanceof Error ? err.message : 'Failed to load directories');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -90,12 +85,7 @@ export function WorkspacePickerModal({
|
|||||||
<AlertCircle className="w-6 h-6 text-destructive" />
|
<AlertCircle className="w-6 h-6 text-destructive" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-destructive">{error}</p>
|
<p className="text-sm text-destructive">{error}</p>
|
||||||
<Button
|
<Button variant="secondary" size="sm" onClick={loadDirectories} className="mt-2">
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onClick={loadDirectories}
|
|
||||||
className="mt-2"
|
|
||||||
>
|
|
||||||
Try Again
|
Try Again
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,9 +118,7 @@ export function WorkspacePickerModal({
|
|||||||
<p className="font-medium text-foreground truncate group-hover:text-brand-500 transition-colors">
|
<p className="font-medium text-foreground truncate group-hover:text-brand-500 transition-colors">
|
||||||
{dir.name}
|
{dir.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground/70 truncate">
|
<p className="text-xs text-muted-foreground/70 truncate">{dir.path}</p>
|
||||||
{dir.path}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -72,7 +72,7 @@ import { toast } from 'sonner';
|
|||||||
import { themeOptions } from '@/config/theme-options';
|
import { themeOptions } from '@/config/theme-options';
|
||||||
import type { SpecRegenerationEvent } from '@/types/electron';
|
import type { SpecRegenerationEvent } from '@/types/electron';
|
||||||
import { DeleteProjectDialog } from '@/components/views/settings-view/components/delete-project-dialog';
|
import { DeleteProjectDialog } from '@/components/views/settings-view/components/delete-project-dialog';
|
||||||
import { NewProjectModal } from '@/components/new-project-modal';
|
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
|
||||||
import { CreateSpecDialog } from '@/components/views/spec-view/dialogs';
|
import { CreateSpecDialog } from '@/components/views/spec-view/dialogs';
|
||||||
import type { FeatureCount } from '@/components/views/spec-view/types';
|
import type { FeatureCount } from '@/components/views/spec-view/types';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { useState, useEffect } from "react";
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Button } from '@/components/ui/button';
|
||||||
import { Button } from "@/components/ui/button";
|
import { HotkeyButton } from '@/components/ui/hotkey-button';
|
||||||
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
import { Input } from '@/components/ui/input';
|
||||||
import { Input } from "@/components/ui/input";
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
@@ -15,66 +14,66 @@ import {
|
|||||||
X,
|
X,
|
||||||
ArchiveRestore,
|
ArchiveRestore,
|
||||||
Loader2,
|
Loader2,
|
||||||
} from "lucide-react";
|
} from 'lucide-react';
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from '@/lib/utils';
|
||||||
import type { SessionListItem } from "@/types/electron";
|
import type { SessionListItem } from '@/types/electron';
|
||||||
import { useKeyboardShortcutsConfig } from "@/hooks/use-keyboard-shortcuts";
|
import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { DeleteSessionDialog } from "@/components/delete-session-dialog";
|
import { DeleteSessionDialog } from '@/components/dialogs/delete-session-dialog';
|
||||||
import { DeleteAllArchivedSessionsDialog } from "@/components/delete-all-archived-sessions-dialog";
|
import { DeleteAllArchivedSessionsDialog } from '@/components/dialogs/delete-all-archived-sessions-dialog';
|
||||||
|
|
||||||
// Random session name generator
|
// Random session name generator
|
||||||
const adjectives = [
|
const adjectives = [
|
||||||
"Swift",
|
'Swift',
|
||||||
"Bright",
|
'Bright',
|
||||||
"Clever",
|
'Clever',
|
||||||
"Dynamic",
|
'Dynamic',
|
||||||
"Eager",
|
'Eager',
|
||||||
"Focused",
|
'Focused',
|
||||||
"Gentle",
|
'Gentle',
|
||||||
"Happy",
|
'Happy',
|
||||||
"Inventive",
|
'Inventive',
|
||||||
"Jolly",
|
'Jolly',
|
||||||
"Keen",
|
'Keen',
|
||||||
"Lively",
|
'Lively',
|
||||||
"Mighty",
|
'Mighty',
|
||||||
"Noble",
|
'Noble',
|
||||||
"Optimal",
|
'Optimal',
|
||||||
"Peaceful",
|
'Peaceful',
|
||||||
"Quick",
|
'Quick',
|
||||||
"Radiant",
|
'Radiant',
|
||||||
"Smart",
|
'Smart',
|
||||||
"Tranquil",
|
'Tranquil',
|
||||||
"Unique",
|
'Unique',
|
||||||
"Vibrant",
|
'Vibrant',
|
||||||
"Wise",
|
'Wise',
|
||||||
"Zealous",
|
'Zealous',
|
||||||
];
|
];
|
||||||
|
|
||||||
const nouns = [
|
const nouns = [
|
||||||
"Agent",
|
'Agent',
|
||||||
"Builder",
|
'Builder',
|
||||||
"Coder",
|
'Coder',
|
||||||
"Developer",
|
'Developer',
|
||||||
"Explorer",
|
'Explorer',
|
||||||
"Forge",
|
'Forge',
|
||||||
"Garden",
|
'Garden',
|
||||||
"Helper",
|
'Helper',
|
||||||
"Innovator",
|
'Innovator',
|
||||||
"Journey",
|
'Journey',
|
||||||
"Kernel",
|
'Kernel',
|
||||||
"Lighthouse",
|
'Lighthouse',
|
||||||
"Mission",
|
'Mission',
|
||||||
"Navigator",
|
'Navigator',
|
||||||
"Oracle",
|
'Oracle',
|
||||||
"Project",
|
'Project',
|
||||||
"Quest",
|
'Quest',
|
||||||
"Runner",
|
'Runner',
|
||||||
"Spark",
|
'Spark',
|
||||||
"Task",
|
'Task',
|
||||||
"Unicorn",
|
'Unicorn',
|
||||||
"Voyage",
|
'Voyage',
|
||||||
"Workshop",
|
'Workshop',
|
||||||
];
|
];
|
||||||
|
|
||||||
function generateRandomSessionName(): string {
|
function generateRandomSessionName(): string {
|
||||||
@@ -101,19 +100,15 @@ export function SessionManager({
|
|||||||
}: SessionManagerProps) {
|
}: SessionManagerProps) {
|
||||||
const shortcuts = useKeyboardShortcutsConfig();
|
const shortcuts = useKeyboardShortcutsConfig();
|
||||||
const [sessions, setSessions] = useState<SessionListItem[]>([]);
|
const [sessions, setSessions] = useState<SessionListItem[]>([]);
|
||||||
const [activeTab, setActiveTab] = useState<"active" | "archived">("active");
|
const [activeTab, setActiveTab] = useState<'active' | 'archived'>('active');
|
||||||
const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
|
const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
|
||||||
const [editingName, setEditingName] = useState("");
|
const [editingName, setEditingName] = useState('');
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [newSessionName, setNewSessionName] = useState("");
|
const [newSessionName, setNewSessionName] = useState('');
|
||||||
const [runningSessions, setRunningSessions] = useState<Set<string>>(
|
const [runningSessions, setRunningSessions] = useState<Set<string>>(new Set());
|
||||||
new Set()
|
|
||||||
);
|
|
||||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
const [sessionToDelete, setSessionToDelete] =
|
const [sessionToDelete, setSessionToDelete] = useState<SessionListItem | null>(null);
|
||||||
useState<SessionListItem | null>(null);
|
const [isDeleteAllArchivedDialogOpen, setIsDeleteAllArchivedDialogOpen] = useState(false);
|
||||||
const [isDeleteAllArchivedDialogOpen, setIsDeleteAllArchivedDialogOpen] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
// Check running state for all sessions
|
// Check running state for all sessions
|
||||||
const checkRunningSessions = async (sessionList: SessionListItem[]) => {
|
const checkRunningSessions = async (sessionList: SessionListItem[]) => {
|
||||||
@@ -131,10 +126,7 @@ export function SessionManager({
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors for individual session checks
|
// Ignore errors for individual session checks
|
||||||
console.warn(
|
console.warn(`[SessionManager] Failed to check running state for ${session.id}:`, err);
|
||||||
`[SessionManager] Failed to check running state for ${session.id}:`,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,14 +172,10 @@ export function SessionManager({
|
|||||||
|
|
||||||
const sessionName = newSessionName.trim() || generateRandomSessionName();
|
const sessionName = newSessionName.trim() || generateRandomSessionName();
|
||||||
|
|
||||||
const result = await api.sessions.create(
|
const result = await api.sessions.create(sessionName, projectPath, projectPath);
|
||||||
sessionName,
|
|
||||||
projectPath,
|
|
||||||
projectPath
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success && result.session?.id) {
|
if (result.success && result.session?.id) {
|
||||||
setNewSessionName("");
|
setNewSessionName('');
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
onSelectSession(result.session.id);
|
onSelectSession(result.session.id);
|
||||||
@@ -201,11 +189,7 @@ export function SessionManager({
|
|||||||
|
|
||||||
const sessionName = generateRandomSessionName();
|
const sessionName = generateRandomSessionName();
|
||||||
|
|
||||||
const result = await api.sessions.create(
|
const result = await api.sessions.create(sessionName, projectPath, projectPath);
|
||||||
sessionName,
|
|
||||||
projectPath,
|
|
||||||
projectPath
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success && result.session?.id) {
|
if (result.success && result.session?.id) {
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
@@ -234,7 +218,7 @@ export function SessionManager({
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setEditingSessionId(null);
|
setEditingSessionId(null);
|
||||||
setEditingName("");
|
setEditingName('');
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -243,7 +227,7 @@ export function SessionManager({
|
|||||||
const handleArchiveSession = async (sessionId: string) => {
|
const handleArchiveSession = async (sessionId: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api?.sessions) {
|
if (!api?.sessions) {
|
||||||
console.error("[SessionManager] Sessions API not available");
|
console.error('[SessionManager] Sessions API not available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,10 +240,10 @@ export function SessionManager({
|
|||||||
}
|
}
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
} else {
|
} else {
|
||||||
console.error("[SessionManager] Archive failed:", result.error);
|
console.error('[SessionManager] Archive failed:', result.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[SessionManager] Archive error:", error);
|
console.error('[SessionManager] Archive error:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,7 +251,7 @@ export function SessionManager({
|
|||||||
const handleUnarchiveSession = async (sessionId: string) => {
|
const handleUnarchiveSession = async (sessionId: string) => {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api?.sessions) {
|
if (!api?.sessions) {
|
||||||
console.error("[SessionManager] Sessions API not available");
|
console.error('[SessionManager] Sessions API not available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,10 +260,10 @@ export function SessionManager({
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
await loadSessions();
|
await loadSessions();
|
||||||
} else {
|
} else {
|
||||||
console.error("[SessionManager] Unarchive failed:", result.error);
|
console.error('[SessionManager] Unarchive failed:', result.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[SessionManager] Unarchive error:", error);
|
console.error('[SessionManager] Unarchive error:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -324,8 +308,7 @@ export function SessionManager({
|
|||||||
|
|
||||||
const activeSessions = sessions.filter((s) => !s.isArchived);
|
const activeSessions = sessions.filter((s) => !s.isArchived);
|
||||||
const archivedSessions = sessions.filter((s) => s.isArchived);
|
const archivedSessions = sessions.filter((s) => s.isArchived);
|
||||||
const displayedSessions =
|
const displayedSessions = activeTab === 'active' ? activeSessions : archivedSessions;
|
||||||
activeTab === "active" ? activeSessions : archivedSessions;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="h-full flex flex-col rounded-none">
|
<Card className="h-full flex flex-col rounded-none">
|
||||||
@@ -337,8 +320,8 @@ export function SessionManager({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Switch to active tab if on archived tab
|
// Switch to active tab if on archived tab
|
||||||
if (activeTab === "archived") {
|
if (activeTab === 'archived') {
|
||||||
setActiveTab("active");
|
setActiveTab('active');
|
||||||
}
|
}
|
||||||
handleQuickCreateSession();
|
handleQuickCreateSession();
|
||||||
}}
|
}}
|
||||||
@@ -354,9 +337,7 @@ export function SessionManager({
|
|||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) => setActiveTab(value as 'active' | 'archived')}
|
||||||
setActiveTab(value as "active" | "archived")
|
|
||||||
}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<TabsList className="w-full">
|
<TabsList className="w-full">
|
||||||
@@ -372,10 +353,7 @@ export function SessionManager({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent
|
<CardContent className="flex-1 overflow-y-auto space-y-2" data-testid="session-list">
|
||||||
className="flex-1 overflow-y-auto space-y-2"
|
|
||||||
data-testid="session-list"
|
|
||||||
>
|
|
||||||
{/* Create new session */}
|
{/* Create new session */}
|
||||||
{isCreating && (
|
{isCreating && (
|
||||||
<div className="p-3 border rounded-lg bg-muted/50">
|
<div className="p-3 border rounded-lg bg-muted/50">
|
||||||
@@ -385,10 +363,10 @@ export function SessionManager({
|
|||||||
value={newSessionName}
|
value={newSessionName}
|
||||||
onChange={(e) => setNewSessionName(e.target.value)}
|
onChange={(e) => setNewSessionName(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") handleCreateSession();
|
if (e.key === 'Enter') handleCreateSession();
|
||||||
if (e.key === "Escape") {
|
if (e.key === 'Escape') {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setNewSessionName("");
|
setNewSessionName('');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -401,7 +379,7 @@ export function SessionManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setNewSessionName("");
|
setNewSessionName('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
@@ -411,7 +389,7 @@ export function SessionManager({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Delete All Archived button - shown at the top of archived sessions */}
|
{/* Delete All Archived button - shown at the top of archived sessions */}
|
||||||
{activeTab === "archived" && archivedSessions.length > 0 && (
|
{activeTab === 'archived' && archivedSessions.length > 0 && (
|
||||||
<div className="pb-2 border-b mb-2">
|
<div className="pb-2 border-b mb-2">
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -431,9 +409,9 @@ export function SessionManager({
|
|||||||
<div
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-3 border rounded-lg cursor-pointer transition-colors hover:bg-accent/50",
|
'p-3 border rounded-lg cursor-pointer transition-colors hover:bg-accent/50',
|
||||||
currentSessionId === session.id && "bg-primary/10 border-primary",
|
currentSessionId === session.id && 'bg-primary/10 border-primary',
|
||||||
session.isArchived && "opacity-60"
|
session.isArchived && 'opacity-60'
|
||||||
)}
|
)}
|
||||||
onClick={() => !session.isArchived && onSelectSession(session.id)}
|
onClick={() => !session.isArchived && onSelectSession(session.id)}
|
||||||
data-testid={`session-item-${session.id}`}
|
data-testid={`session-item-${session.id}`}
|
||||||
@@ -446,10 +424,10 @@ export function SessionManager({
|
|||||||
value={editingName}
|
value={editingName}
|
||||||
onChange={(e) => setEditingName(e.target.value)}
|
onChange={(e) => setEditingName(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") handleRenameSession(session.id);
|
if (e.key === 'Enter') handleRenameSession(session.id);
|
||||||
if (e.key === "Escape") {
|
if (e.key === 'Escape') {
|
||||||
setEditingSessionId(null);
|
setEditingSessionId(null);
|
||||||
setEditingName("");
|
setEditingName('');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
@@ -472,7 +450,7 @@ export function SessionManager({
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setEditingSessionId(null);
|
setEditingSessionId(null);
|
||||||
setEditingName("");
|
setEditingName('');
|
||||||
}}
|
}}
|
||||||
className="h-7"
|
className="h-7"
|
||||||
>
|
>
|
||||||
@@ -483,16 +461,14 @@ export function SessionManager({
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
{/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */}
|
{/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */}
|
||||||
{(currentSessionId === session.id &&
|
{(currentSessionId === session.id && isCurrentSessionThinking) ||
|
||||||
isCurrentSessionThinking) ||
|
|
||||||
runningSessions.has(session.id) ? (
|
runningSessions.has(session.id) ? (
|
||||||
<Loader2 className="w-4 h-4 text-primary animate-spin shrink-0" />
|
<Loader2 className="w-4 h-4 text-primary animate-spin shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" />
|
<MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||||
)}
|
)}
|
||||||
<h3 className="font-medium truncate">{session.name}</h3>
|
<h3 className="font-medium truncate">{session.name}</h3>
|
||||||
{((currentSessionId === session.id &&
|
{((currentSessionId === session.id && isCurrentSessionThinking) ||
|
||||||
isCurrentSessionThinking) ||
|
|
||||||
runningSessions.has(session.id)) && (
|
runningSessions.has(session.id)) && (
|
||||||
<span className="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
<span className="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
||||||
thinking...
|
thinking...
|
||||||
@@ -500,9 +476,7 @@ export function SessionManager({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{session.preview && (
|
{session.preview && (
|
||||||
<p className="text-xs text-muted-foreground truncate">
|
<p className="text-xs text-muted-foreground truncate">{session.preview}</p>
|
||||||
{session.preview}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-2 mt-2">
|
<div className="flex items-center gap-2 mt-2">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
@@ -519,10 +493,7 @@ export function SessionManager({
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
{!session.isArchived && (
|
{!session.isArchived && (
|
||||||
<div
|
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
|
||||||
className="flex gap-1"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -547,10 +518,7 @@ export function SessionManager({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{session.isArchived && (
|
{session.isArchived && (
|
||||||
<div
|
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
|
||||||
className="flex gap-1"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -578,14 +546,12 @@ export function SessionManager({
|
|||||||
<div className="text-center py-8 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
<MessageSquare className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<MessageSquare className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{activeTab === "active"
|
{activeTab === 'active' ? 'No active sessions' : 'No archived sessions'}
|
||||||
? "No active sessions"
|
|
||||||
: "No archived sessions"}
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
{activeTab === "active"
|
{activeTab === 'active'
|
||||||
? "Create your first session to get started"
|
? 'Create your first session to get started'
|
||||||
: "Archive sessions to see them here"}
|
: 'Archive sessions to see them here'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export { AgentInfoPanel } from './agent-info-panel';
|
||||||
|
export { CardActions } from './card-actions';
|
||||||
|
export { CardBadges, PriorityBadges } from './card-badges';
|
||||||
|
export { CardContentSections } from './card-content-sections';
|
||||||
|
export { CardHeaderSection } from './card-header';
|
||||||
|
export { KanbanCard } from './kanban-card';
|
||||||
|
export { SummaryDialog } from './summary-dialog';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { useApiKeyManagement } from './use-api-key-management';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { ApiKeyField } from './api-key-field';
|
||||||
|
export { ApiKeysSection } from './api-keys-section';
|
||||||
|
export { AuthenticationStatusDisplay } from './authentication-status-display';
|
||||||
|
export { SecurityNotice } from './security-notice';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AppearanceSection } from './appearance-section';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AudioSection } from './audio-section';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ClaudeCliStatus } from './claude-cli-status';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { DeleteProjectDialog } from './delete-project-dialog';
|
||||||
|
export { KeyboardMapDialog } from './keyboard-map-dialog';
|
||||||
|
export { SettingsHeader } from './settings-header';
|
||||||
|
export { SettingsNavigation } from './settings-navigation';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { NAV_ITEMS } from './navigation';
|
||||||
|
export type { NavigationItem } from './navigation';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { DangerZoneSection } from './danger-zone-section';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { FeatureDefaultsSection } from './feature-defaults-section';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { KeyboardShortcutsSection } from './keyboard-shortcuts-section';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export type { Theme } from '@/config/theme-options';
|
||||||
|
export type { CliStatus, KanbanDetailLevel, Project, ApiKeys } from './types';
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
import { useState, useCallback } from "react";
|
import { Button } from '@/components/ui/button';
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -8,10 +7,10 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from '@/components/ui/dialog';
|
||||||
import { useAppStore, type ThemeMode } from "@/store/app-store";
|
import { useAppStore, type ThemeMode } from '@/store/app-store';
|
||||||
import { getElectronAPI, type Project } from "@/lib/electron";
|
import { getElectronAPI, type Project } from '@/lib/electron';
|
||||||
import { initializeProject } from "@/lib/project-init";
|
import { initializeProject } from '@/lib/project-init';
|
||||||
import {
|
import {
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -21,19 +20,19 @@ import {
|
|||||||
MessageSquare,
|
MessageSquare,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Loader2,
|
Loader2,
|
||||||
} from "lucide-react";
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { toast } from "sonner";
|
import { toast } from 'sonner';
|
||||||
import { WorkspacePickerModal } from "@/components/workspace-picker-modal";
|
import { WorkspacePickerModal } from '@/components/dialogs/workspace-picker-modal';
|
||||||
import { NewProjectModal } from "@/components/new-project-modal";
|
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
|
||||||
import { getHttpApiClient } from "@/lib/http-api-client";
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
import type { StarterTemplate } from "@/lib/templates";
|
import type { StarterTemplate } from '@/lib/templates';
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
|
|
||||||
export function WelcomeView() {
|
export function WelcomeView() {
|
||||||
const {
|
const {
|
||||||
@@ -66,24 +65,24 @@ export function WelcomeView() {
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
|
||||||
if (!api.autoMode?.analyzeProject) {
|
if (!api.autoMode?.analyzeProject) {
|
||||||
console.log("[Welcome] Auto mode API not available, skipping analysis");
|
console.log('[Welcome] Auto mode API not available, skipping analysis');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsAnalyzing(true);
|
setIsAnalyzing(true);
|
||||||
try {
|
try {
|
||||||
console.log("[Welcome] Starting project analysis for:", projectPath);
|
console.log('[Welcome] Starting project analysis for:', projectPath);
|
||||||
const result = await api.autoMode.analyzeProject(projectPath);
|
const result = await api.autoMode.analyzeProject(projectPath);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success("Project analyzed", {
|
toast.success('Project analyzed', {
|
||||||
description: "AI agent has analyzed your project structure",
|
description: 'AI agent has analyzed your project structure',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("[Welcome] Project analysis failed:", result.error);
|
console.error('[Welcome] Project analysis failed:', result.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Welcome] Failed to analyze project:", error);
|
console.error('[Welcome] Failed to analyze project:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsAnalyzing(false);
|
setIsAnalyzing(false);
|
||||||
}
|
}
|
||||||
@@ -100,8 +99,8 @@ export function WelcomeView() {
|
|||||||
const initResult = await initializeProject(path);
|
const initResult = await initializeProject(path);
|
||||||
|
|
||||||
if (!initResult.success) {
|
if (!initResult.success) {
|
||||||
toast.error("Failed to initialize project", {
|
toast.error('Failed to initialize project', {
|
||||||
description: initResult.error || "Unknown error occurred",
|
description: initResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -126,26 +125,23 @@ export function WelcomeView() {
|
|||||||
setShowInitDialog(true);
|
setShowInitDialog(true);
|
||||||
|
|
||||||
// Kick off agent to analyze the project and update app_spec.txt
|
// Kick off agent to analyze the project and update app_spec.txt
|
||||||
console.log(
|
console.log('[Welcome] Project initialized, created files:', initResult.createdFiles);
|
||||||
"[Welcome] Project initialized, created files:",
|
console.log('[Welcome] Kicking off project analysis agent...');
|
||||||
initResult.createdFiles
|
|
||||||
);
|
|
||||||
console.log("[Welcome] Kicking off project analysis agent...");
|
|
||||||
|
|
||||||
// Start analysis in background (don't await, let it run async)
|
// Start analysis in background (don't await, let it run async)
|
||||||
analyzeProject(path);
|
analyzeProject(path);
|
||||||
} else {
|
} else {
|
||||||
toast.success("Project opened", {
|
toast.success('Project opened', {
|
||||||
description: `Opened ${name}`,
|
description: `Opened ${name}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to the board view
|
// Navigate to the board view
|
||||||
navigate({ to: "/board" });
|
navigate({ to: '/board' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Welcome] Failed to open project:", error);
|
console.error('[Welcome] Failed to open project:', error);
|
||||||
toast.error("Failed to open project", {
|
toast.error('Failed to open project', {
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
description: error instanceof Error ? error.message : 'Unknown error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsOpening(false);
|
setIsOpening(false);
|
||||||
@@ -178,21 +174,19 @@ export function WelcomeView() {
|
|||||||
if (!result.canceled && result.filePaths[0]) {
|
if (!result.canceled && result.filePaths[0]) {
|
||||||
const path = result.filePaths[0];
|
const path = result.filePaths[0];
|
||||||
// Extract folder name from path (works on both Windows and Mac/Linux)
|
// Extract folder name from path (works on both Windows and Mac/Linux)
|
||||||
const name =
|
const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project';
|
||||||
path.split(/[/\\]/).filter(Boolean).pop() || "Untitled Project";
|
|
||||||
await initializeAndOpenProject(path, name);
|
await initializeAndOpenProject(path, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Welcome] Failed to check workspace config:", error);
|
console.error('[Welcome] Failed to check workspace config:', error);
|
||||||
// Fall back to current behavior on error
|
// Fall back to current behavior on error
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const result = await api.openDirectory();
|
const result = await api.openDirectory();
|
||||||
|
|
||||||
if (!result.canceled && result.filePaths[0]) {
|
if (!result.canceled && result.filePaths[0]) {
|
||||||
const path = result.filePaths[0];
|
const path = result.filePaths[0];
|
||||||
const name =
|
const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project';
|
||||||
path.split(/[/\\]/).filter(Boolean).pop() || "Untitled Project";
|
|
||||||
await initializeAndOpenProject(path, name);
|
await initializeAndOpenProject(path, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,16 +218,13 @@ export function WelcomeView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleInteractiveMode = () => {
|
const handleInteractiveMode = () => {
|
||||||
navigate({ to: "/interview" });
|
navigate({ to: '/interview' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a blank project with just .automaker directory structure
|
* Create a blank project with just .automaker directory structure
|
||||||
*/
|
*/
|
||||||
const handleCreateBlankProject = async (
|
const handleCreateBlankProject = async (projectName: string, parentDir: string) => {
|
||||||
projectName: string,
|
|
||||||
parentDir: string
|
|
||||||
) => {
|
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
@@ -242,7 +233,7 @@ export function WelcomeView() {
|
|||||||
// Validate that parent directory exists
|
// Validate that parent directory exists
|
||||||
const parentExists = await api.exists(parentDir);
|
const parentExists = await api.exists(parentDir);
|
||||||
if (!parentExists) {
|
if (!parentExists) {
|
||||||
toast.error("Parent directory does not exist", {
|
toast.error('Parent directory does not exist', {
|
||||||
description: `Cannot create project in non-existent directory: ${parentDir}`,
|
description: `Cannot create project in non-existent directory: ${parentDir}`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -251,7 +242,7 @@ export function WelcomeView() {
|
|||||||
// Verify parent is actually a directory
|
// Verify parent is actually a directory
|
||||||
const parentStat = await api.stat(parentDir);
|
const parentStat = await api.stat(parentDir);
|
||||||
if (parentStat && !parentStat.isDirectory) {
|
if (parentStat && !parentStat.isDirectory) {
|
||||||
toast.error("Parent path is not a directory", {
|
toast.error('Parent path is not a directory', {
|
||||||
description: `${parentDir} is not a directory`,
|
description: `${parentDir} is not a directory`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -260,8 +251,8 @@ export function WelcomeView() {
|
|||||||
// Create project directory
|
// Create project directory
|
||||||
const mkdirResult = await api.mkdir(projectPath);
|
const mkdirResult = await api.mkdir(projectPath);
|
||||||
if (!mkdirResult.success) {
|
if (!mkdirResult.success) {
|
||||||
toast.error("Failed to create project directory", {
|
toast.error('Failed to create project directory', {
|
||||||
description: mkdirResult.error || "Unknown error occurred",
|
description: mkdirResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -270,8 +261,8 @@ export function WelcomeView() {
|
|||||||
const initResult = await initializeProject(projectPath);
|
const initResult = await initializeProject(projectPath);
|
||||||
|
|
||||||
if (!initResult.success) {
|
if (!initResult.success) {
|
||||||
toast.error("Failed to initialize project", {
|
toast.error('Failed to initialize project', {
|
||||||
description: initResult.error || "Unknown error occurred",
|
description: initResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -313,7 +304,7 @@ export function WelcomeView() {
|
|||||||
setCurrentProject(project);
|
setCurrentProject(project);
|
||||||
setShowNewProjectModal(false);
|
setShowNewProjectModal(false);
|
||||||
|
|
||||||
toast.success("Project created", {
|
toast.success('Project created', {
|
||||||
description: `Created ${projectName} with .automaker directory`,
|
description: `Created ${projectName} with .automaker directory`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,9 +317,9 @@ export function WelcomeView() {
|
|||||||
});
|
});
|
||||||
setShowInitDialog(true);
|
setShowInitDialog(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create project:", error);
|
console.error('Failed to create project:', error);
|
||||||
toast.error("Failed to create project", {
|
toast.error('Failed to create project', {
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
description: error instanceof Error ? error.message : 'Unknown error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
@@ -356,8 +347,8 @@ export function WelcomeView() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!cloneResult.success || !cloneResult.projectPath) {
|
if (!cloneResult.success || !cloneResult.projectPath) {
|
||||||
toast.error("Failed to clone template", {
|
toast.error('Failed to clone template', {
|
||||||
description: cloneResult.error || "Unknown error occurred",
|
description: cloneResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -368,8 +359,8 @@ export function WelcomeView() {
|
|||||||
const initResult = await initializeProject(projectPath);
|
const initResult = await initializeProject(projectPath);
|
||||||
|
|
||||||
if (!initResult.success) {
|
if (!initResult.success) {
|
||||||
toast.error("Failed to initialize project", {
|
toast.error('Failed to initialize project', {
|
||||||
description: initResult.error || "Unknown error occurred",
|
description: initResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -387,15 +378,11 @@ export function WelcomeView() {
|
|||||||
</overview>
|
</overview>
|
||||||
|
|
||||||
<technology_stack>
|
<technology_stack>
|
||||||
${template.techStack
|
${template.techStack.map((tech) => `<technology>${tech}</technology>`).join('\n ')}
|
||||||
.map((tech) => `<technology>${tech}</technology>`)
|
|
||||||
.join("\n ")}
|
|
||||||
</technology_stack>
|
</technology_stack>
|
||||||
|
|
||||||
<core_capabilities>
|
<core_capabilities>
|
||||||
${template.features
|
${template.features.map((feature) => `<capability>${feature}</capability>`).join('\n ')}
|
||||||
.map((feature) => `<capability>${feature}</capability>`)
|
|
||||||
.join("\n ")}
|
|
||||||
</core_capabilities>
|
</core_capabilities>
|
||||||
|
|
||||||
<implemented_features>
|
<implemented_features>
|
||||||
@@ -415,7 +402,7 @@ export function WelcomeView() {
|
|||||||
setCurrentProject(project);
|
setCurrentProject(project);
|
||||||
setShowNewProjectModal(false);
|
setShowNewProjectModal(false);
|
||||||
|
|
||||||
toast.success("Project created from template", {
|
toast.success('Project created from template', {
|
||||||
description: `Created ${projectName} from ${template.name}`,
|
description: `Created ${projectName} from ${template.name}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -431,9 +418,9 @@ export function WelcomeView() {
|
|||||||
// Kick off project analysis
|
// Kick off project analysis
|
||||||
analyzeProject(projectPath);
|
analyzeProject(projectPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create project from template:", error);
|
console.error('Failed to create project from template:', error);
|
||||||
toast.error("Failed to create project", {
|
toast.error('Failed to create project', {
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
description: error instanceof Error ? error.message : 'Unknown error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
@@ -454,15 +441,11 @@ export function WelcomeView() {
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
|
||||||
// Clone the repository
|
// Clone the repository
|
||||||
const cloneResult = await httpClient.templates.clone(
|
const cloneResult = await httpClient.templates.clone(repoUrl, projectName, parentDir);
|
||||||
repoUrl,
|
|
||||||
projectName,
|
|
||||||
parentDir
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cloneResult.success || !cloneResult.projectPath) {
|
if (!cloneResult.success || !cloneResult.projectPath) {
|
||||||
toast.error("Failed to clone repository", {
|
toast.error('Failed to clone repository', {
|
||||||
description: cloneResult.error || "Unknown error occurred",
|
description: cloneResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -473,8 +456,8 @@ export function WelcomeView() {
|
|||||||
const initResult = await initializeProject(projectPath);
|
const initResult = await initializeProject(projectPath);
|
||||||
|
|
||||||
if (!initResult.success) {
|
if (!initResult.success) {
|
||||||
toast.error("Failed to initialize project", {
|
toast.error('Failed to initialize project', {
|
||||||
description: initResult.error || "Unknown error occurred",
|
description: initResult.error || 'Unknown error occurred',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -516,7 +499,7 @@ export function WelcomeView() {
|
|||||||
setCurrentProject(project);
|
setCurrentProject(project);
|
||||||
setShowNewProjectModal(false);
|
setShowNewProjectModal(false);
|
||||||
|
|
||||||
toast.success("Project created from repository", {
|
toast.success('Project created from repository', {
|
||||||
description: `Created ${projectName} from ${repoUrl}`,
|
description: `Created ${projectName} from ${repoUrl}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -532,9 +515,9 @@ export function WelcomeView() {
|
|||||||
// Kick off project analysis
|
// Kick off project analysis
|
||||||
analyzeProject(projectPath);
|
analyzeProject(projectPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create project from custom URL:", error);
|
console.error('Failed to create project from custom URL:', error);
|
||||||
toast.error("Failed to create project", {
|
toast.error('Failed to create project', {
|
||||||
description: error instanceof Error ? error.message : "Unknown error",
|
description: error instanceof Error ? error.message : 'Unknown error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
@@ -587,12 +570,9 @@ export function WelcomeView() {
|
|||||||
<Plus className="w-6 h-6 text-white" />
|
<Plus className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-lg font-semibold text-foreground mb-1.5">
|
<h3 className="text-lg font-semibold text-foreground mb-1.5">New Project</h3>
|
||||||
New Project
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Create a new project from scratch with AI-powered
|
Create a new project from scratch with AI-powered development
|
||||||
development
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -608,10 +588,7 @@ export function WelcomeView() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-56">
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem onClick={handleNewProject} data-testid="quick-setup-option">
|
||||||
onClick={handleNewProject}
|
|
||||||
data-testid="quick-setup-option"
|
|
||||||
>
|
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
Quick Setup
|
Quick Setup
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -640,9 +617,7 @@ export function WelcomeView() {
|
|||||||
<FolderOpen className="w-6 h-6 text-muted-foreground group-hover:text-blue-500 transition-colors duration-300" />
|
<FolderOpen className="w-6 h-6 text-muted-foreground group-hover:text-blue-500 transition-colors duration-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-lg font-semibold text-foreground mb-1.5">
|
<h3 className="text-lg font-semibold text-foreground mb-1.5">Open Project</h3>
|
||||||
Open Project
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
Open an existing project folder to continue working
|
Open an existing project folder to continue working
|
||||||
</p>
|
</p>
|
||||||
@@ -667,9 +642,7 @@ export function WelcomeView() {
|
|||||||
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center">
|
||||||
<Clock className="w-4 h-4 text-muted-foreground" />
|
<Clock className="w-4 h-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-foreground">Recent Projects</h2>
|
||||||
Recent Projects
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{recentProjects.map((project, index) => (
|
{recentProjects.map((project, index) => (
|
||||||
@@ -695,9 +668,7 @@ export function WelcomeView() {
|
|||||||
</p>
|
</p>
|
||||||
{project.lastOpened && (
|
{project.lastOpened && (
|
||||||
<p className="text-xs text-muted-foreground mt-1.5">
|
<p className="text-xs text-muted-foreground mt-1.5">
|
||||||
{new Date(
|
{new Date(project.lastOpened).toLocaleDateString()}
|
||||||
project.lastOpened
|
|
||||||
).toLocaleDateString()}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -715,9 +686,7 @@ export function WelcomeView() {
|
|||||||
<div className="w-20 h-20 rounded-2xl bg-muted/50 border border-border flex items-center justify-center mb-5">
|
<div className="w-20 h-20 rounded-2xl bg-muted/50 border border-border flex items-center justify-center mb-5">
|
||||||
<Sparkles className="w-10 h-10 text-muted-foreground/50" />
|
<Sparkles className="w-10 h-10 text-muted-foreground/50" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold text-foreground mb-2">
|
<h3 className="text-xl font-semibold text-foreground mb-2">No projects yet</h3>
|
||||||
No projects yet
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-muted-foreground max-w-md leading-relaxed">
|
<p className="text-sm text-muted-foreground max-w-md leading-relaxed">
|
||||||
Get started by creating a new project or opening an existing one
|
Get started by creating a new project or opening an existing one
|
||||||
</p>
|
</p>
|
||||||
@@ -747,9 +716,7 @@ export function WelcomeView() {
|
|||||||
<div className="w-8 h-8 rounded-lg bg-brand-500/10 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-lg bg-brand-500/10 flex items-center justify-center">
|
||||||
<Sparkles className="w-4 h-4 text-brand-500" />
|
<Sparkles className="w-4 h-4 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
{initStatus?.isNewProject
|
{initStatus?.isNewProject ? 'Project Initialized' : 'Project Updated'}
|
||||||
? "Project Initialized"
|
|
||||||
: "Project Updated"}
|
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-muted-foreground mt-1">
|
<DialogDescription className="text-muted-foreground mt-1">
|
||||||
{initStatus?.isNewProject
|
{initStatus?.isNewProject
|
||||||
@@ -759,9 +726,7 @@ export function WelcomeView() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-sm text-foreground font-medium">
|
<p className="text-sm text-foreground font-medium">Created files:</p>
|
||||||
Created files:
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{initStatus?.createdFiles.map((file) => (
|
{initStatus?.createdFiles.map((file) => (
|
||||||
<li
|
<li
|
||||||
@@ -788,12 +753,12 @@ export function WelcomeView() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
<span className="text-brand-500 font-medium">Tip:</span> Edit the{" "}
|
<span className="text-brand-500 font-medium">Tip:</span> Edit the{' '}
|
||||||
<code className="text-xs bg-muted px-1.5 py-0.5 rounded font-mono">
|
<code className="text-xs bg-muted px-1.5 py-0.5 rounded font-mono">
|
||||||
app_spec.txt
|
app_spec.txt
|
||||||
</code>{" "}
|
</code>{' '}
|
||||||
file to describe your project. The AI agent will use this to
|
file to describe your project. The AI agent will use this to understand your
|
||||||
understand your project structure.
|
project structure.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -826,9 +791,7 @@ export function WelcomeView() {
|
|||||||
>
|
>
|
||||||
<div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl">
|
<div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl">
|
||||||
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" />
|
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" />
|
||||||
<p className="text-foreground font-medium">
|
<p className="text-foreground font-medium">Initializing project...</p>
|
||||||
Initializing project...
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user