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:
SuperComboGamer
2025-12-12 00:23:43 -05:00
parent 02a1af3314
commit 4b9bd2641f
44 changed files with 8287 additions and 703 deletions

View File

@@ -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) => {

View File

@@ -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 = [

View File

@@ -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(

View File

@@ -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,

View File

@@ -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);
}
}
});

View File

@@ -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>
</>

View File

@@ -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);
}

View File

@@ -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 () => {