mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
refactor: streamline Electron API integration and enhance UI components
- Removed unused Electron API methods and simplified the main process. - Introduced a new workspace picker modal for improved project selection. - Enhanced error handling for authentication issues across various components. - Updated UI styles for dark mode support and added new CSS variables. - Refactored session management to utilize a centralized API access method. - Added server routes for workspace management, including directory listing and configuration checks.
This commit is contained in:
@@ -220,11 +220,17 @@ export class AutoModeService {
|
||||
projectPath,
|
||||
});
|
||||
} else {
|
||||
const errorMessage = (error as Error).message || "Unknown error";
|
||||
const isAuthError = errorMessage.includes("Authentication failed") ||
|
||||
errorMessage.includes("Invalid API key") ||
|
||||
errorMessage.includes("authentication_failed");
|
||||
|
||||
console.error(`[AutoMode] Feature ${featureId} failed:`, error);
|
||||
await this.updateFeatureStatus(projectPath, featureId, "failed");
|
||||
this.emitAutoModeEvent("auto_mode_error", {
|
||||
featureId,
|
||||
error: (error as Error).message,
|
||||
error: errorMessage,
|
||||
errorType: isAuthError ? "authentication" : "execution",
|
||||
projectPath,
|
||||
});
|
||||
}
|
||||
@@ -741,6 +747,17 @@ When done, summarize what you implemented and any notes for the developer.`;
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === "text") {
|
||||
responseText = block.text;
|
||||
|
||||
// Check for authentication errors in the response
|
||||
if (block.text.includes("Invalid API key") ||
|
||||
block.text.includes("authentication_failed") ||
|
||||
block.text.includes("Fix external API key")) {
|
||||
throw new Error(
|
||||
"Authentication failed: Invalid or expired API key. " +
|
||||
"Please check your ANTHROPIC_API_KEY or run 'claude login' to re-authenticate."
|
||||
);
|
||||
}
|
||||
|
||||
this.emitAutoModeEvent("auto_mode_progress", {
|
||||
featureId,
|
||||
content: block.text,
|
||||
@@ -753,7 +770,20 @@ When done, summarize what you implemented and any notes for the developer.`;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (msg.type === "assistant" && (msg as { error?: string }).error === "authentication_failed") {
|
||||
// Handle authentication error from the SDK
|
||||
throw new Error(
|
||||
"Authentication failed: Invalid or expired API key. " +
|
||||
"Please set a valid ANTHROPIC_API_KEY environment variable or run 'claude login' to authenticate."
|
||||
);
|
||||
} else if (msg.type === "result" && msg.subtype === "success") {
|
||||
// Check if result indicates an error
|
||||
if (msg.is_error && msg.result?.includes("Invalid API key")) {
|
||||
throw new Error(
|
||||
"Authentication failed: Invalid or expired API key. " +
|
||||
"Please set a valid ANTHROPIC_API_KEY environment variable or run 'claude login' to authenticate."
|
||||
);
|
||||
}
|
||||
responseText = msg.result || responseText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,123 @@ export class FeatureLoader {
|
||||
return path.join(projectPath, ".automaker", "features");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images directory path for a feature
|
||||
*/
|
||||
getFeatureImagesDir(projectPath: string, featureId: string): string {
|
||||
return path.join(this.getFeatureDir(projectPath, featureId), "images");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete images that were removed from a feature
|
||||
*/
|
||||
private async deleteOrphanedImages(
|
||||
projectPath: string,
|
||||
oldPaths: Array<string | { path: string; [key: string]: unknown }> | undefined,
|
||||
newPaths: Array<string | { path: string; [key: string]: unknown }> | undefined
|
||||
): Promise<void> {
|
||||
if (!oldPaths || oldPaths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build sets of paths for comparison
|
||||
const oldPathSet = new Set(
|
||||
oldPaths.map((p) => (typeof p === "string" ? p : p.path))
|
||||
);
|
||||
const newPathSet = new Set(
|
||||
(newPaths || []).map((p) => (typeof p === "string" ? p : p.path))
|
||||
);
|
||||
|
||||
// Find images that were removed
|
||||
for (const oldPath of oldPathSet) {
|
||||
if (!newPathSet.has(oldPath)) {
|
||||
try {
|
||||
const fullPath = path.isAbsolute(oldPath)
|
||||
? oldPath
|
||||
: path.join(projectPath, oldPath);
|
||||
|
||||
await fs.unlink(fullPath);
|
||||
console.log(`[FeatureLoader] Deleted orphaned image: ${oldPath}`);
|
||||
} catch (error) {
|
||||
// Ignore errors when deleting (file may already be gone)
|
||||
console.warn(`[FeatureLoader] Failed to delete image: ${oldPath}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy images from temp directory to feature directory and update paths
|
||||
*/
|
||||
private async migrateImages(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
imagePaths?: Array<string | { path: string; [key: string]: unknown }>
|
||||
): Promise<Array<string | { path: string; [key: string]: unknown }> | undefined> {
|
||||
if (!imagePaths || imagePaths.length === 0) {
|
||||
return imagePaths;
|
||||
}
|
||||
|
||||
const featureImagesDir = this.getFeatureImagesDir(projectPath, featureId);
|
||||
await fs.mkdir(featureImagesDir, { recursive: true });
|
||||
|
||||
const updatedPaths: Array<string | { path: string; [key: string]: unknown }> = [];
|
||||
|
||||
for (const imagePath of imagePaths) {
|
||||
try {
|
||||
const originalPath = typeof imagePath === "string" ? imagePath : imagePath.path;
|
||||
|
||||
// Skip if already in feature directory
|
||||
if (originalPath.includes(`/features/${featureId}/images/`)) {
|
||||
updatedPaths.push(imagePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve the full path
|
||||
const fullOriginalPath = path.isAbsolute(originalPath)
|
||||
? originalPath
|
||||
: path.join(projectPath, originalPath);
|
||||
|
||||
// Check if file exists
|
||||
try {
|
||||
await fs.access(fullOriginalPath);
|
||||
} catch {
|
||||
console.warn(`[FeatureLoader] Image not found, skipping: ${fullOriginalPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get filename and create new path
|
||||
const filename = path.basename(originalPath);
|
||||
const newPath = path.join(featureImagesDir, filename);
|
||||
const relativePath = `.automaker/features/${featureId}/images/${filename}`;
|
||||
|
||||
// Copy the file
|
||||
await fs.copyFile(fullOriginalPath, newPath);
|
||||
console.log(`[FeatureLoader] Copied image: ${originalPath} -> ${relativePath}`);
|
||||
|
||||
// Try to delete the original temp file
|
||||
try {
|
||||
await fs.unlink(fullOriginalPath);
|
||||
} catch {
|
||||
// Ignore errors when deleting temp file
|
||||
}
|
||||
|
||||
// Update the path in the result
|
||||
if (typeof imagePath === "string") {
|
||||
updatedPaths.push(relativePath);
|
||||
} else {
|
||||
updatedPaths.push({ ...imagePath, path: relativePath });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FeatureLoader] Failed to migrate image:`, error);
|
||||
// Keep original path if migration fails
|
||||
updatedPaths.push(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to a specific feature folder
|
||||
*/
|
||||
@@ -151,12 +268,20 @@ export class FeatureLoader {
|
||||
// Create feature directory
|
||||
await fs.mkdir(featureDir, { recursive: true });
|
||||
|
||||
// Migrate images from temp directory to feature directory
|
||||
const migratedImagePaths = await this.migrateImages(
|
||||
projectPath,
|
||||
featureId,
|
||||
featureData.imagePaths
|
||||
);
|
||||
|
||||
// Ensure feature has required fields
|
||||
const feature: Feature = {
|
||||
category: featureData.category || "Uncategorized",
|
||||
description: featureData.description || "",
|
||||
...featureData,
|
||||
id: featureId,
|
||||
imagePaths: migratedImagePaths,
|
||||
};
|
||||
|
||||
// Write feature.json
|
||||
@@ -179,8 +304,30 @@ export class FeatureLoader {
|
||||
throw new Error(`Feature ${featureId} not found`);
|
||||
}
|
||||
|
||||
// Handle image path changes
|
||||
let updatedImagePaths = updates.imagePaths;
|
||||
if (updates.imagePaths !== undefined) {
|
||||
// Delete orphaned images (images that were removed)
|
||||
await this.deleteOrphanedImages(
|
||||
projectPath,
|
||||
feature.imagePaths,
|
||||
updates.imagePaths
|
||||
);
|
||||
|
||||
// Migrate any new images
|
||||
updatedImagePaths = await this.migrateImages(
|
||||
projectPath,
|
||||
featureId,
|
||||
updates.imagePaths
|
||||
);
|
||||
}
|
||||
|
||||
// Merge updates
|
||||
const updatedFeature: Feature = { ...feature, ...updates };
|
||||
const updatedFeature: Feature = {
|
||||
...feature,
|
||||
...updates,
|
||||
...(updatedImagePaths !== undefined ? { imagePaths: updatedImagePaths } : {}),
|
||||
};
|
||||
|
||||
// Write back to file
|
||||
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
|
||||
|
||||
Reference in New Issue
Block a user