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:
Kacper
2025-12-10 13:56:33 +01:00
parent 7ab2aaaa23
commit 55b8e6858e
8 changed files with 186 additions and 69 deletions

View File

@@ -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"
}
]

View File

@@ -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";

View 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 };

View File

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

View File

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

View File

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

View File

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

View File

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