mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
chore: update project management and API integration
- Added new scripts for server development and full application startup in package.json. - Enhanced project management by checking for existing projects to avoid duplicates. - Improved API integration with better error handling and connection checks in the Electron API. - Updated UI components to reflect changes in project and session management. - Refactored authentication status display to include more detailed information on methods used.
This commit is contained in:
@@ -413,14 +413,33 @@ export function Sidebar() {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = {
|
||||
id: `project-${Date.now()}`,
|
||||
name,
|
||||
path,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
// Check if project already exists (by path) to preserve theme and other settings
|
||||
const existingProject = projects.find((p) => p.path === path);
|
||||
|
||||
let project: Project;
|
||||
if (existingProject) {
|
||||
// Update existing project, preserving theme and other properties
|
||||
project = {
|
||||
...existingProject,
|
||||
name, // Update name in case it changed
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
// Update the project in the store (this will update the existing entry)
|
||||
const updatedProjects = projects.map((p) =>
|
||||
p.id === existingProject.id ? project : p
|
||||
);
|
||||
useAppStore.setState({ projects: updatedProjects });
|
||||
} else {
|
||||
// Create new project
|
||||
project = {
|
||||
id: `project-${Date.now()}`,
|
||||
name,
|
||||
path,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
addProject(project);
|
||||
}
|
||||
|
||||
addProject(project);
|
||||
setCurrentProject(project);
|
||||
|
||||
// Check if app_spec.txt exists
|
||||
@@ -455,7 +474,7 @@ export function Sidebar() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [addProject, setCurrentProject]);
|
||||
}, [projects, addProject, setCurrentProject]);
|
||||
|
||||
const handleRestoreProject = useCallback(
|
||||
(projectId: string) => {
|
||||
|
||||
@@ -4,14 +4,13 @@ import { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Plus,
|
||||
MessageSquare,
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { SessionListItem } from "@/types/electron";
|
||||
import { useKeyboardShortcutsConfig } from "@/hooks/use-keyboard-shortcuts";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
|
||||
// Random session name generator
|
||||
const adjectives = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useCallback, useEffect } from "react";
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ImageIcon, X, Loader2 } from "lucide-react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
@@ -98,7 +98,7 @@ export function DescriptionImageDropZone({
|
||||
});
|
||||
};
|
||||
|
||||
const saveImageToTemp = async (
|
||||
const saveImageToTemp = useCallback(async (
|
||||
base64Data: string,
|
||||
filename: string,
|
||||
mimeType: string
|
||||
@@ -107,8 +107,8 @@ export function DescriptionImageDropZone({
|
||||
const api = getElectronAPI();
|
||||
// Check if saveImageToTemp method exists
|
||||
if (!api.saveImageToTemp) {
|
||||
// Fallback for mock API - return a mock path in .automaker/images
|
||||
console.log("[DescriptionImageDropZone] Using mock path for image");
|
||||
// Fallback path when saveImageToTemp is not available
|
||||
console.log("[DescriptionImageDropZone] Using fallback path for image");
|
||||
return `.automaker/images/${Date.now()}_${filename}`;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export function DescriptionImageDropZone({
|
||||
console.error("[DescriptionImageDropZone] Error saving image:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}, [currentProject?.path]);
|
||||
|
||||
const processFiles = useCallback(
|
||||
async (files: FileList) => {
|
||||
@@ -193,7 +193,7 @@ export function DescriptionImageDropZone({
|
||||
|
||||
setIsProcessing(false);
|
||||
},
|
||||
[disabled, isProcessing, images, maxFiles, maxFileSize, onImagesChange, previewImages]
|
||||
[disabled, isProcessing, images, maxFiles, maxFileSize, onImagesChange, previewImages, saveImageToTemp]
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
|
||||
@@ -149,12 +149,12 @@ export function AgentToolsView() {
|
||||
setTerminalResult(null);
|
||||
|
||||
try {
|
||||
// Simulate agent requesting terminal command execution
|
||||
console.log(`[Agent Tool] Requesting to run command: ${terminalCommand}`);
|
||||
// Terminal command simulation for demonstration purposes
|
||||
console.log(`[Agent Tool] Simulating command: ${terminalCommand}`);
|
||||
|
||||
// In mock mode, simulate terminal output
|
||||
// In real Electron mode, this would use child_process
|
||||
const mockOutputs: Record<string, string> = {
|
||||
// Simulated outputs for common commands (preview mode)
|
||||
// In production, the agent executes commands via Claude SDK
|
||||
const simulatedOutputs: Record<string, string> = {
|
||||
ls: "app_spec.txt\nfeatures\nnode_modules\npackage.json\nsrc\ntests\ntsconfig.json",
|
||||
pwd: currentProject?.path || "/Users/demo/project",
|
||||
"echo hello": "hello",
|
||||
@@ -168,8 +168,8 @@ export function AgentToolsView() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
const output =
|
||||
mockOutputs[terminalCommand.toLowerCase()] ||
|
||||
`Command executed: ${terminalCommand}\n(Mock output - real execution requires Electron mode)`;
|
||||
simulatedOutputs[terminalCommand.toLowerCase()] ||
|
||||
`[Preview] ${terminalCommand}\n(Terminal commands are executed by the agent during feature implementation)`;
|
||||
|
||||
setTerminalResult({
|
||||
success: true,
|
||||
|
||||
@@ -394,22 +394,25 @@ export function BoardView() {
|
||||
}, []);
|
||||
|
||||
// Load features using features API
|
||||
// IMPORTANT: Do NOT add 'features' to dependency array - it would cause infinite reload loop
|
||||
const loadFeatures = useCallback(async () => {
|
||||
if (!currentProject) return;
|
||||
|
||||
const currentPath = currentProject.path;
|
||||
const previousPath = prevProjectPathRef.current;
|
||||
const isProjectSwitch = previousPath !== null && currentPath !== previousPath;
|
||||
|
||||
// If project switched, clear features first to prevent cross-contamination
|
||||
// Also treat this as an initial load for the new project
|
||||
if (previousPath !== null && currentPath !== previousPath) {
|
||||
// Get cached features from store (without adding to dependencies)
|
||||
const cachedFeatures = useAppStore.getState().features;
|
||||
|
||||
// If project switched, mark it but don't clear features yet
|
||||
// We'll clear after successful API load to prevent data loss
|
||||
if (isProjectSwitch) {
|
||||
console.log(
|
||||
`[BoardView] Project switch detected: ${previousPath} -> ${currentPath}, clearing features`
|
||||
`[BoardView] Project switch detected: ${previousPath} -> ${currentPath}`
|
||||
);
|
||||
isSwitchingProjectRef.current = true;
|
||||
isInitialLoadRef.current = true;
|
||||
setFeatures([]);
|
||||
setPersistedCategories([]); // Also clear categories
|
||||
}
|
||||
|
||||
// Update the ref to track current project
|
||||
@@ -424,6 +427,7 @@ export function BoardView() {
|
||||
const api = getElectronAPI();
|
||||
if (!api.features) {
|
||||
console.error("[BoardView] Features API not available");
|
||||
// Keep cached features if API is unavailable
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -441,10 +445,31 @@ export function BoardView() {
|
||||
thinkingLevel: f.thinkingLevel || "none",
|
||||
})
|
||||
);
|
||||
// Successfully loaded features - now safe to set them
|
||||
setFeatures(featuresWithIds);
|
||||
|
||||
// Only clear categories on project switch AFTER successful load
|
||||
if (isProjectSwitch) {
|
||||
setPersistedCategories([]);
|
||||
}
|
||||
} else if (!result.success && result.error) {
|
||||
console.error("[BoardView] API returned error:", result.error);
|
||||
// If it's a new project or the error indicates no features found,
|
||||
// that's expected - start with empty array
|
||||
if (isProjectSwitch) {
|
||||
setFeatures([]);
|
||||
setPersistedCategories([]);
|
||||
}
|
||||
// Otherwise keep cached features
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load features:", error);
|
||||
// On error, keep existing cached features for the current project
|
||||
// Only clear on project switch if we have no features from server
|
||||
if (isProjectSwitch && cachedFeatures.length === 0) {
|
||||
setFeatures([]);
|
||||
setPersistedCategories([]);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
isInitialLoadRef.current = false;
|
||||
@@ -1475,8 +1500,14 @@ export function BoardView() {
|
||||
if (isRunning) {
|
||||
map.in_progress.push(f);
|
||||
} else {
|
||||
// Otherwise, use the feature's status
|
||||
map[f.status].push(f);
|
||||
// Otherwise, use the feature's status (fallback to backlog for unknown statuses)
|
||||
const status = f.status as ColumnId;
|
||||
if (map[status]) {
|
||||
map[status].push(f);
|
||||
} else {
|
||||
// Unknown status, default to backlog
|
||||
map.backlog.push(f);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -61,12 +61,14 @@ export function AuthenticationStatusDisplay({
|
||||
{claudeAuthStatus.method === "oauth_token_env"
|
||||
? "Using CLAUDE_CODE_OAUTH_TOKEN"
|
||||
: claudeAuthStatus.method === "oauth_token"
|
||||
? "Using stored OAuth token"
|
||||
? "Using stored OAuth token (claude login)"
|
||||
: claudeAuthStatus.method === "api_key_env"
|
||||
? "Using ANTHROPIC_API_KEY"
|
||||
: claudeAuthStatus.method === "api_key"
|
||||
? "Using stored API key"
|
||||
: "Unknown method"}
|
||||
: claudeAuthStatus.method === "credentials_file"
|
||||
? "Using credentials file"
|
||||
: `Using ${claudeAuthStatus.method || "detected"} authentication`}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
@@ -107,14 +109,16 @@ export function AuthenticationStatusDisplay({
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Info className="w-3 h-3 shrink-0" />
|
||||
<span>
|
||||
{codexAuthStatus.method === "cli_verified" ||
|
||||
codexAuthStatus.method === "cli_tokens"
|
||||
{codexAuthStatus.method === "subscription"
|
||||
? "Using Codex subscription (Plus/Team)"
|
||||
: codexAuthStatus.method === "cli_verified" ||
|
||||
codexAuthStatus.method === "cli_tokens"
|
||||
? "Using CLI login (OpenAI account)"
|
||||
: codexAuthStatus.method === "api_key"
|
||||
? "Using stored API key"
|
||||
: codexAuthStatus.method === "env"
|
||||
? "Using OPENAI_API_KEY"
|
||||
: "Unknown method"}
|
||||
: `Using ${codexAuthStatus.method || "unknown"} authentication`}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -68,19 +68,24 @@ export function useCliStatus() {
|
||||
try {
|
||||
const result = await api.setup.getClaudeStatus();
|
||||
if (result.success && result.auth) {
|
||||
const auth = result.auth;
|
||||
// Validate method is one of the expected values, default to "none"
|
||||
const validMethods = ["oauth_token_env", "oauth_token", "api_key", "api_key_env", "none"] as const;
|
||||
// Cast to extended type that includes server-added fields
|
||||
const auth = result.auth as typeof result.auth & {
|
||||
oauthTokenValid?: boolean;
|
||||
apiKeyValid?: boolean;
|
||||
};
|
||||
// Map server method names to client method types
|
||||
// Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, none
|
||||
const validMethods = ["oauth_token_env", "oauth_token", "api_key", "api_key_env", "credentials_file", "none"] as const;
|
||||
type AuthMethod = typeof validMethods[number];
|
||||
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
|
||||
? (auth.method as AuthMethod)
|
||||
: "none";
|
||||
: auth.authenticated ? "api_key" : "none"; // Default authenticated to api_key, not none
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasCredentialsFile: auth.hasCredentialsFile ?? false,
|
||||
oauthTokenValid: auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
oauthTokenValid: auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.apiKeyValid || auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
hasEnvOAuthToken: auth.hasEnvOAuthToken,
|
||||
hasEnvApiKey: auth.hasEnvApiKey,
|
||||
};
|
||||
@@ -96,27 +101,30 @@ export function useCliStatus() {
|
||||
try {
|
||||
const result = await api.setup.getCodexStatus();
|
||||
if (result.success && result.auth) {
|
||||
const auth = result.auth;
|
||||
// Determine method - prioritize cli_verified and cli_tokens over auth_file
|
||||
const method =
|
||||
auth.method === "cli_verified" || auth.method === "cli_tokens"
|
||||
? auth.method === "cli_verified"
|
||||
? ("cli_verified" as const)
|
||||
: ("cli_tokens" as const)
|
||||
: auth.method === "auth_file"
|
||||
? ("api_key" as const)
|
||||
: auth.method === "env_var"
|
||||
? ("env" as const)
|
||||
: ("none" as const);
|
||||
// Cast to extended type that includes server-added fields
|
||||
const auth = result.auth as typeof result.auth & {
|
||||
hasSubscription?: boolean;
|
||||
cliLoggedIn?: boolean;
|
||||
hasEnvApiKey?: boolean;
|
||||
};
|
||||
// Map server method names to client method types
|
||||
// Server returns: subscription, cli_verified, cli_tokens, api_key, env, none
|
||||
const validMethods = ["subscription", "cli_verified", "cli_tokens", "api_key", "env", "none"] as const;
|
||||
type CodexMethod = typeof validMethods[number];
|
||||
const method: CodexMethod = validMethods.includes(auth.method as CodexMethod)
|
||||
? (auth.method as CodexMethod)
|
||||
: auth.authenticated ? "api_key" : "none"; // Default authenticated to api_key
|
||||
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
// Only set apiKeyValid for actual API key methods, not CLI login
|
||||
// Only set apiKeyValid for actual API key methods, not CLI login or subscription
|
||||
apiKeyValid:
|
||||
method === "cli_verified" || method === "cli_tokens"
|
||||
method === "cli_verified" || method === "cli_tokens" || method === "subscription"
|
||||
? undefined
|
||||
: auth.hasAuthFile || auth.hasEnvKey,
|
||||
: auth.hasAuthFile || auth.hasEnvKey || auth.hasEnvApiKey,
|
||||
hasSubscription: auth.hasSubscription,
|
||||
cliLoggedIn: auth.cliLoggedIn,
|
||||
};
|
||||
setCodexAuthStatus(authStatus);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import { getElectronAPI, type Project } from "@/lib/electron";
|
||||
import { initializeProject } from "@/lib/project-init";
|
||||
import {
|
||||
FolderOpen,
|
||||
@@ -105,14 +105,34 @@ export function WelcomeView() {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = {
|
||||
id: `project-${Date.now()}`,
|
||||
name,
|
||||
path,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
// Check if project already exists (by path) to preserve theme and other settings
|
||||
const existingProject = projects.find((p) => p.path === path);
|
||||
|
||||
let project: Project;
|
||||
if (existingProject) {
|
||||
// Update existing project, preserving theme and other properties
|
||||
project = {
|
||||
...existingProject,
|
||||
name, // Update name in case it changed
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
// Update the project in the store (this will update the existing entry)
|
||||
const updatedProjects = projects.map((p) =>
|
||||
p.id === existingProject.id ? project : p
|
||||
);
|
||||
// We need to manually update projects since addProject would create a duplicate
|
||||
useAppStore.setState({ projects: updatedProjects });
|
||||
} else {
|
||||
// Create new project
|
||||
project = {
|
||||
id: `project-${Date.now()}`,
|
||||
name,
|
||||
path,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
addProject(project);
|
||||
}
|
||||
|
||||
addProject(project);
|
||||
setCurrentProject(project);
|
||||
|
||||
// Show initialization dialog if files were created
|
||||
@@ -148,7 +168,7 @@ export function WelcomeView() {
|
||||
setIsOpening(false);
|
||||
}
|
||||
},
|
||||
[addProject, setCurrentProject, analyzeProject]
|
||||
[projects, addProject, setCurrentProject, analyzeProject]
|
||||
);
|
||||
|
||||
const handleOpenProject = useCallback(async () => {
|
||||
|
||||
Reference in New Issue
Block a user