mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
feat(types): resolve TypeScript errors and enhance project analysis features
- Fixed TypeScript errors across the codebase, including updates to various files for improved type safety. - Introduced new `FileTreeNode` and `ProjectAnalysis` interfaces to support project analysis functionality. - Added `Badge` component for UI consistency and improved visual representation. - Enhanced `useElectronAgent` and `electron` API with additional methods for project management. - Updated `AnalysisView` to utilize new types and improve type annotations for better clarity. These changes contribute to a more robust and type-safe codebase, facilitating future development and analysis capabilities.
This commit is contained in:
@@ -262,5 +262,17 @@
|
||||
"summary": "Added git diff panel for in-progress and waiting approval features. Created GitDiffPanel component with themed syntax highlighting. Modified: git-diff-panel.tsx (new), agent-output-modal.tsx, worktree-manager.js, auto-mode-service.js, main.js, preload.js, electron.d.ts. The panel shows changed files with +/- stats and expandable unified diff view using CSS theme variables.",
|
||||
"model": "opus",
|
||||
"thinkingLevel": "ultrathink"
|
||||
},
|
||||
{
|
||||
"id": "feature-1765370691119-yz43i6m6a",
|
||||
"category": "Core",
|
||||
"description": "i want you to run 'npx tsc --noEmi' on our app and fix / get rid of typescript errors that are left ",
|
||||
"steps": [],
|
||||
"status": "verified",
|
||||
"imagePaths": [],
|
||||
"skipTests": false,
|
||||
"summary": "Fixed all TypeScript errors in the codebase. Modified: src/app/api/claude/test/route.ts (rewrote to use fetch instead of missing @anthropic-ai/sdk), src/lib/electron.ts (fixed saveImageToTemp signature, removed duplicate Window declaration), src/types/electron.d.ts (added deleteFile, resumeFeature, contextExists, analyzeProject methods), src/store/app-store.ts (added FileTreeNode, ProjectAnalysis types and analysis state/actions), src/components/views/analysis-view.tsx (added type annotations for implicit any), src/hooks/use-electron-agent.ts (fixed return type to include queue properties). Created: src/components/ui/badge.tsx (new component required by chat-history.tsx).",
|
||||
"model": "opus",
|
||||
"thinkingLevel": "ultrathink"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,11 @@
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
interface AnthropicResponse {
|
||||
content?: Array<{ type: string; text?: string }>;
|
||||
model?: string;
|
||||
error?: { message?: string };
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { apiKey } = await request.json();
|
||||
@@ -15,31 +20,60 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Create Anthropic client with the provided key
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: effectiveApiKey,
|
||||
// Send a simple test prompt to the Anthropic API
|
||||
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": effectiveApiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "claude-sonnet-4-20250514",
|
||||
max_tokens: 100,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Respond with exactly: 'Claude API connection successful!' and nothing else.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Send a simple test prompt
|
||||
const response = await anthropic.messages.create({
|
||||
model: "claude-sonnet-4-20250514",
|
||||
max_tokens: 100,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Respond with exactly: 'Claude SDK connection successful!' and nothing else.",
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = (await response.json()) as AnthropicResponse;
|
||||
const errorMessage = errorData.error?.message || `HTTP ${response.status}`;
|
||||
|
||||
if (response.status === 401) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Invalid API key. Please check your Anthropic API key." },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Rate limit exceeded. Please try again later." },
|
||||
{ status: 429 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `API error: ${errorMessage}` },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as AnthropicResponse;
|
||||
|
||||
// Check if we got a valid response
|
||||
if (response.content && response.content.length > 0) {
|
||||
const textContent = response.content.find((block) => block.type === "text");
|
||||
if (textContent && textContent.type === "text") {
|
||||
if (data.content && data.content.length > 0) {
|
||||
const textContent = data.content.find((block) => block.type === "text");
|
||||
if (textContent && textContent.type === "text" && textContent.text) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Connection successful! Response: "${textContent.text}"`,
|
||||
model: response.model,
|
||||
model: data.model,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -47,33 +81,11 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Connection successful! Claude responded.",
|
||||
model: response.model,
|
||||
model: data.model,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error("Claude API test error:", error);
|
||||
|
||||
// Handle specific Anthropic API errors
|
||||
if (error instanceof Anthropic.AuthenticationError) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Invalid API key. Please check your Anthropic API key." },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Anthropic.RateLimitError) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Rate limit exceeded. Please try again later." },
|
||||
{ status: 429 }
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Anthropic.APIError) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `API error: ${error.message}` },
|
||||
{ status: error.status || 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Failed to connect to Claude API";
|
||||
|
||||
|
||||
36
app/src/components/ui/badge.tsx
Normal file
36
app/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
@@ -326,8 +326,8 @@ export function AnalysisView() {
|
||||
const analyzeStructure = () => {
|
||||
const structure: string[] = [];
|
||||
const topLevelDirs = projectAnalysis.fileTree
|
||||
.filter((n) => n.isDirectory)
|
||||
.map((n) => n.name);
|
||||
.filter((n: FileTreeNode) => n.isDirectory)
|
||||
.map((n: FileTreeNode) => n.name);
|
||||
|
||||
for (const dir of topLevelDirs) {
|
||||
structure.push(` <directory name="${dir}" />`);
|
||||
@@ -350,14 +350,14 @@ export function AnalysisView() {
|
||||
<technology_stack>
|
||||
<languages>
|
||||
${Object.entries(projectAnalysis.filesByExtension)
|
||||
.filter(([ext]) =>
|
||||
.filter(([ext]: [string, number]) =>
|
||||
["ts", "tsx", "js", "jsx", "py", "go", "rs", "java", "cpp", "c"].includes(
|
||||
ext
|
||||
)
|
||||
)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.sort((a: [string, number], b: [string, number]) => b[1] - a[1])
|
||||
.slice(0, 5)
|
||||
.map(([ext, count]) => ` <language ext=".${ext}" count="${count}" />`)
|
||||
.map(([ext, count]: [string, number]) => ` <language ext=".${ext}" count="${count}" />`)
|
||||
.join("\n")}
|
||||
</languages>
|
||||
<frameworks>
|
||||
@@ -375,10 +375,10 @@ ${analyzeStructure()}
|
||||
|
||||
<file_breakdown>
|
||||
${Object.entries(projectAnalysis.filesByExtension)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.sort((a: [string, number], b: [string, number]) => b[1] - a[1])
|
||||
.slice(0, 10)
|
||||
.map(
|
||||
([ext, count]) =>
|
||||
([ext, count]: [string, number]) =>
|
||||
` <extension type="${
|
||||
ext.startsWith("(") ? ext : "." + ext
|
||||
}" count="${count}" />`
|
||||
@@ -465,11 +465,11 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
const detectFeatures = () => {
|
||||
const extensions = projectAnalysis.filesByExtension;
|
||||
const topLevelDirs = projectAnalysis.fileTree
|
||||
.filter((n) => n.isDirectory)
|
||||
.map((n) => n.name.toLowerCase());
|
||||
.filter((n: FileTreeNode) => n.isDirectory)
|
||||
.map((n: FileTreeNode) => n.name.toLowerCase());
|
||||
const topLevelFiles = projectAnalysis.fileTree
|
||||
.filter((n) => !n.isDirectory)
|
||||
.map((n) => n.name.toLowerCase());
|
||||
.filter((n: FileTreeNode) => !n.isDirectory)
|
||||
.map((n: FileTreeNode) => n.name.toLowerCase());
|
||||
|
||||
// Check for test directories and files
|
||||
const hasTests =
|
||||
@@ -840,7 +840,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
</div>
|
||||
{node.isDirectory && isExpanded && node.children && (
|
||||
<div>
|
||||
{node.children.map((child) => renderNode(child, depth + 1))}
|
||||
{node.children.map((child: FileTreeNode) => renderNode(child, depth + 1))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -964,9 +964,9 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{Object.entries(projectAnalysis.filesByExtension)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.sort((a: [string, number], b: [string, number]) => b[1] - a[1])
|
||||
.slice(0, 15)
|
||||
.map(([ext, count]) => (
|
||||
.map(([ext, count]: [string, number]) => (
|
||||
<div key={ext} className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground font-mono">
|
||||
{ext.startsWith("(") ? ext : `.${ext}`}
|
||||
@@ -1107,7 +1107,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
data-testid="analysis-file-tree"
|
||||
>
|
||||
<div className="p-2">
|
||||
{projectAnalysis.fileTree.map((node) => renderNode(node))}
|
||||
{projectAnalysis.fileTree.map((node: FileTreeNode) => renderNode(node))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -266,7 +266,8 @@ export function useElectronAgent({
|
||||
setIsProcessing(false);
|
||||
setError(event.error);
|
||||
if (event.message) {
|
||||
setMessages((prev) => [...prev, event.message]);
|
||||
const errorMessage = event.message;
|
||||
setMessages((prev) => [...prev, errorMessage]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -400,5 +401,8 @@ export function useElectronAgent({
|
||||
stopExecution,
|
||||
clearHistory,
|
||||
error,
|
||||
queuedMessages,
|
||||
isQueueProcessing: isProcessingQueue,
|
||||
clearMessageQueue: clearQueue,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export interface ElectronAPI {
|
||||
deleteFile: (filePath: string) => Promise<WriteResult>;
|
||||
trashItem?: (filePath: string) => Promise<WriteResult>;
|
||||
getPath: (name: string) => Promise<string>;
|
||||
saveImageToTemp?: (data: string, filename: string, mimeType: string) => Promise<SaveImageResult>;
|
||||
saveImageToTemp?: (data: string, filename: string, mimeType: string, projectPath?: string) => Promise<SaveImageResult>;
|
||||
autoMode?: AutoModeAPI;
|
||||
checkClaudeCli?: () => Promise<{
|
||||
success: boolean;
|
||||
@@ -132,12 +132,8 @@ export interface ElectronAPI {
|
||||
git?: GitAPI;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI?: ElectronAPI;
|
||||
isElectron?: boolean;
|
||||
}
|
||||
}
|
||||
// Note: Window interface is declared in @/types/electron.d.ts
|
||||
// Do not redeclare here to avoid type conflicts
|
||||
|
||||
// Mock data for web development
|
||||
const mockFeatures = [
|
||||
@@ -386,12 +382,13 @@ export const getElectronAPI = (): ElectronAPI => {
|
||||
},
|
||||
|
||||
// Save image to temp directory
|
||||
saveImageToTemp: async (data: string, filename: string, mimeType: string) => {
|
||||
// Generate a mock temp file path
|
||||
saveImageToTemp: async (data: string, filename: string, mimeType: string, projectPath?: string) => {
|
||||
// Generate a mock temp file path - use projectPath if provided
|
||||
const timestamp = Date.now();
|
||||
const ext = mimeType.split("/")[1] || "png";
|
||||
const safeName = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
|
||||
const tempFilePath = `/tmp/automaker-images/${timestamp}_${safeName}`;
|
||||
const tempFilePath = projectPath
|
||||
? `${projectPath}/.automaker/images/${timestamp}_${safeName}`
|
||||
: `/tmp/automaker-images/${timestamp}_${safeName}`;
|
||||
|
||||
// Store the image data in mock file system for testing
|
||||
mockFileSystem[tempFilePath] = data;
|
||||
|
||||
@@ -126,6 +126,24 @@ export interface Feature {
|
||||
branchName?: string; // Name of the feature branch
|
||||
}
|
||||
|
||||
// File tree node for project analysis
|
||||
export interface FileTreeNode {
|
||||
name: string;
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
extension?: string;
|
||||
children?: FileTreeNode[];
|
||||
}
|
||||
|
||||
// Project analysis result
|
||||
export interface ProjectAnalysis {
|
||||
fileTree: FileTreeNode[];
|
||||
totalFiles: number;
|
||||
totalDirectories: number;
|
||||
filesByExtension: Record<string, number>;
|
||||
analyzedAt: string;
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
// Project state
|
||||
projects: Project[];
|
||||
@@ -173,6 +191,10 @@ export interface AppState {
|
||||
|
||||
// AI Profiles
|
||||
aiProfiles: AIProfile[];
|
||||
|
||||
// Project Analysis
|
||||
projectAnalysis: ProjectAnalysis | null;
|
||||
isAnalyzing: boolean;
|
||||
}
|
||||
|
||||
export interface AutoModeActivity {
|
||||
@@ -267,6 +289,11 @@ export interface AppActions {
|
||||
removeAIProfile: (id: string) => void;
|
||||
reorderAIProfiles: (oldIndex: number, newIndex: number) => void;
|
||||
|
||||
// Project Analysis actions
|
||||
setProjectAnalysis: (analysis: ProjectAnalysis | null) => void;
|
||||
setIsAnalyzing: (analyzing: boolean) => void;
|
||||
clearAnalysis: () => void;
|
||||
|
||||
// Reset
|
||||
reset: () => void;
|
||||
}
|
||||
@@ -351,6 +378,8 @@ const initialState: AppState = {
|
||||
defaultSkipTests: false, // Default to TDD mode (tests enabled)
|
||||
useWorktrees: false, // Default to disabled (worktree feature is experimental)
|
||||
aiProfiles: DEFAULT_AI_PROFILES,
|
||||
projectAnalysis: null,
|
||||
isAnalyzing: false,
|
||||
};
|
||||
|
||||
export const useAppStore = create<AppState & AppActions>()(
|
||||
@@ -713,6 +742,11 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
set({ aiProfiles: profiles });
|
||||
},
|
||||
|
||||
// Project Analysis actions
|
||||
setProjectAnalysis: (analysis) => set({ projectAnalysis: analysis }),
|
||||
setIsAnalyzing: (analyzing) => set({ isAnalyzing: analyzing }),
|
||||
clearAnalysis: () => set({ projectAnalysis: null }),
|
||||
|
||||
// Reset
|
||||
reset: () => set(initialState),
|
||||
}),
|
||||
|
||||
22
app/src/types/electron.d.ts
vendored
22
app/src/types/electron.d.ts
vendored
@@ -241,6 +241,24 @@ export interface AutoModeAPI {
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
resumeFeature: (projectPath: string, featureId: string) => Promise<{
|
||||
success: boolean;
|
||||
passes?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
contextExists: (projectPath: string, featureId: string) => Promise<{
|
||||
success: boolean;
|
||||
exists?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
analyzeProject: (projectPath: string) => Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
followUpFeature: (projectPath: string, featureId: string, prompt: string, imagePaths?: string[]) => Promise<{
|
||||
success: boolean;
|
||||
passes?: boolean;
|
||||
@@ -302,6 +320,10 @@ export interface ElectronAPI {
|
||||
};
|
||||
error?: string;
|
||||
}>;
|
||||
deleteFile: (filePath: string) => Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
// App APIs
|
||||
getPath: (name: string) => Promise<string>;
|
||||
|
||||
Reference in New Issue
Block a user