Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions app/api/analyses/[id]/run/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,15 @@ export async function POST(
await updateAnalysisStatus(id, 'scanning')
await deleteBlueprintsByAnalysis(id)
send({ status: 'scanning', progress: 10 })
send({ thought: 'Connecting to GitHub repositories...' })

// Fetch file trees from GitHub for each repository
const allFiles: { repo: string; path: string; type: string }[] = []
const repoErrors: string[] = []

for (const repo of repositories) {
try {
send({ thought: `Scanning file tree for ${repo.full_name}...` })
let treeData: Awaited<ReturnType<typeof getGitHubRepositoryTreeFromBranch>>
try {
treeData = await getGitHubRepositoryTreeFromBranch(
Expand Down Expand Up @@ -261,16 +263,18 @@ export async function POST(
return
}

send({ status: 'scanning', progress: 40 })
send({ status: 'scanning', progress: 40, thought: `Found ${allFiles.length} source files across ${repositories.length} repositories` })

// Update to analyzing
await updateAnalysisStatus(id, 'analyzing', { total_files: allFiles.length })
send({ status: 'analyzing', progress: 50 })
send({ status: 'analyzing', progress: 50, thought: 'Evaluating architecture patterns and reusable modules...' })

// Build file summary for AI (cap at 400 files to keep prompt reasonable)
const filesToSend = allFiles.slice(0, 400)
const fileSummary = filesToSend.map(f => `- ${f.repo}: ${f.path}`).join('\n')

send({ thought: 'Identifying component boundaries and shared dependencies...' })

// Use Claude to analyze and discover app blueprints (structured tool output)
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })

Expand Down Expand Up @@ -374,7 +378,7 @@ Constraints:
],
})

send({ status: 'analyzing', progress: 80 })
send({ status: 'analyzing', progress: 80, thought: 'Ranking blueprints by opportunity score and feasibility...' })

// Check if response was truncated (hit max_tokens)
if (aiResponse.stop_reason === 'max_tokens') {
Expand Down
12 changes: 5 additions & 7 deletions app/api/preview/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
// Stages: analyzing → fixing → deploying → live

import { NextRequest } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { getCurrentAccessToken } from "@/lib/auth";
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
function getAnthropic() { return new Anthropic() }

Comment on lines +9 to 10
// ─── Types ────────────────────────────────────────────────────────────────────

Expand All @@ -26,9 +25,8 @@
// ─── Main handler ─────────────────────────────────────────────────────────────

export async function POST(req: NextRequest) {
// 1. Auth — grab the GitHub access token from the session
const session = await getServerSession(authOptions);
const accessToken = (session as any)?.accessToken;
// 1. Auth — grab the GitHub access token from cookies
const accessToken = await getCurrentAccessToken();
if (!accessToken) {
return new Response("Unauthorized", { status: 401 });
}
Expand Down Expand Up @@ -109,7 +107,7 @@
// ── LIVE ──────────────────────────────────────────────────────────────
send({ stage: "live", url: `https://${previewUrl}` });

} catch (err: any) {

Check failure on line 110 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
console.error("[preview] Error:", err);
send({ stage: "error", message: err.message ?? "An unexpected error occurred." });
} finally {
Expand Down Expand Up @@ -152,7 +150,7 @@
".html", ".md", ".env.example", ".gitignore", "Dockerfile",
];

const textFiles = (tree.tree ?? []).filter((item: any) => {

Check failure on line 153 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
if (item.type !== "blob") return false;
if (SKIP.some((s) => item.path.includes(s))) return false;
if (item.size > 100_000) return false; // skip large files
Expand All @@ -164,7 +162,7 @@
const sliced = textFiles.slice(0, 80);

const results = await Promise.allSettled(
sliced.map(async (item: any) => {

Check failure on line 165 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
const res = await ghFetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${item.path}`,
token
Expand Down Expand Up @@ -195,19 +193,19 @@
// ─── Claude dep-fixer ────────────────────────────────────────────────────────

interface FixResult {
fixedPackageJson: Record<string, any>;

Check failure on line 196 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
changes: string[];
}

async function fixDependencies(rawPackageJson: string): Promise<FixResult> {
let parsed: Record<string, any>;

Check failure on line 201 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
try {
parsed = JSON.parse(rawPackageJson);
} catch {
return { fixedPackageJson: {}, changes: ["Could not parse package.json"] };
}

const response = await anthropic.messages.create({
const response = await getAnthropic().messages.create({
model: "claude-opus-4-5",
max_tokens: 2048,
messages: [
Expand Down Expand Up @@ -245,7 +243,7 @@

function buildDeployFiles(
files: GitHubFile[],
fixedPackageJson: Record<string, any>

Check failure on line 246 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
) {
return files.map((f) => ({
file: f.path,
Expand All @@ -256,7 +254,7 @@
}));
}

function detectFramework(pkg: Record<string, any>): string | null {

Check failure on line 257 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
if (deps["next"]) return "nextjs";
if (deps["react-scripts"]) return "create-react-app";
Expand All @@ -273,7 +271,7 @@
files: { file: string; data: string }[],
framework: string | null
): Promise<string> {
const body: Record<string, any> = {

Check failure on line 274 in app/api/preview/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Unexpected any. Specify a different type
name,
files,
target: "preview",
Expand Down
201 changes: 7 additions & 194 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { getAllRepositories, getAllAnalyses, getGapSummary, type Analysis, type Repository } from '@/lib/queries'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { FolderGit2, Sparkles, Code2, Plus, ArrowRight, Zap, AlertCircle, Lightbulb } from 'lucide-react'
import Link from 'next/link'
import { DashboardClient } from '@/components/dashboard-client'

export const dynamic = 'force-dynamic'

Expand All @@ -22,195 +19,11 @@ export default async function DashboardPage() {
const completedAnalyses = analyses.filter((analysis) => analysis.status === 'complete')

return (
<div className="space-y-10">
{/* Header */}
<div className="space-y-2">
<h1 className="text-3xl font-bold text-foreground tracking-tight">Dashboard</h1>
<p className="text-muted-foreground text-lg">Your code intelligence overview</p>
</div>

{/* Quick stats */}
<div className="grid gap-4 md:grid-cols-3">
<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Repositories</p>
<p className="text-3xl font-bold text-foreground mt-1 tabular-nums">{repositories.length}</p>
</div>
<div className="h-12 w-12 rounded-xl bg-chart-1/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<FolderGit2 className="h-6 w-6 text-chart-1" />
</div>
</div>
</Card>
<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Analyses Run</p>
<p className="text-3xl font-bold text-foreground mt-1 tabular-nums">{analyses.length}</p>
</div>
<div className="h-12 w-12 rounded-xl bg-chart-2/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Sparkles className="h-6 w-6 text-chart-2" />
</div>
</div>
</Card>
<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Completed</p>
<p className="text-3xl font-bold text-foreground mt-1 tabular-nums">{completedAnalyses.length}</p>
</div>
<div className="h-12 w-12 rounded-xl bg-chart-4/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Code2 className="h-6 w-6 text-chart-4" />
</div>
</div>
</Card>
</div>

{/* Quick Actions */}
<section className="space-y-5">
<h2 className="text-xl font-semibold text-foreground">Quick Actions</h2>

{repositories.length === 0 ? (
<Card className="border-dashed border-2 p-10 text-center hover:border-border transition-colors">
<div className="h-16 w-16 rounded-2xl bg-muted/50 flex items-center justify-center mx-auto mb-4">
<FolderGit2 className="h-8 w-8 text-muted-foreground/50" />
</div>
<h3 className="text-lg font-semibold text-foreground mb-2">Connect your first repository</h3>
<p className="text-sm text-muted-foreground mb-6 max-w-md mx-auto">
Start by adding your GitHub repositories. We&apos;ll scan all files and prepare them for AI analysis.
</p>
<Button size="lg" asChild>
<Link href="/dashboard/repositories">
<Plus className="h-4 w-4 mr-2" />
Add Repository
</Link>
</Button>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-start justify-between mb-5">
<div className="h-12 w-12 rounded-xl bg-chart-1/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<FolderGit2 className="h-6 w-6 text-chart-1" />
</div>
<span className="text-xs font-semibold text-muted-foreground bg-muted px-2.5 py-1 rounded-full">
{repositories.length} connected
</span>
</div>
<h3 className="font-semibold text-lg text-foreground mb-1">Repositories</h3>
<p className="text-sm text-muted-foreground mb-5">
Manage your connected GitHub repositories and add new ones.
</p>
<Button variant="outline" size="sm" className="group-hover:bg-foreground/5" asChild>
<Link href="/dashboard/repositories">
Manage Repos
<ArrowRight className="h-4 w-4 ml-2" />
</Link>
</Button>
</Card>

<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-start justify-between mb-5">
<div className="h-12 w-12 rounded-xl bg-chart-2/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Zap className="h-6 w-6 text-chart-2" />
</div>
<span className="text-xs font-semibold text-muted-foreground bg-muted px-2.5 py-1 rounded-full">
{completedAnalyses.length} complete
</span>
</div>
<h3 className="font-semibold text-lg text-foreground mb-1">Run Analysis</h3>
<p className="text-sm text-muted-foreground mb-5">
Let AI discover what apps you can build from your existing code.
</p>
<Button size="sm" asChild>
<Link href="/dashboard/analyses">
<Sparkles className="h-4 w-4 mr-2" />
Start Analysis
</Link>
</Button>
</Card>

<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border">
<div className="flex items-start justify-between mb-5">
<div className="h-12 w-12 rounded-xl bg-chart-3/10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<Lightbulb className="h-6 w-6 text-chart-3" />
</div>
<span className="text-xs font-semibold text-muted-foreground bg-muted px-2.5 py-1 rounded-full">
Quick ideas
</span>
</div>
<h3 className="font-semibold text-lg text-foreground mb-1">Template Hub</h3>
<p className="text-sm text-muted-foreground mb-5">
Pre-configured combinations you can assemble into products today.
</p>
<Button variant="outline" size="sm" className="group-hover:bg-foreground/5" asChild>
<Link href="/dashboard/templates/browse">
Explore Templates
<ArrowRight className="h-4 w-4 ml-2" />
</Link>
</Button>
</Card>

{gapSummary.total_gaps > 0 && (
<Card className="group p-6 hover:shadow-lg hover:shadow-black/5 transition-all duration-300 hover:border-border border-amber-200 bg-amber-50/50">
<div className="flex items-start justify-between mb-5">
<div className="h-12 w-12 rounded-xl bg-amber-100 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<AlertCircle className="h-6 w-6 text-amber-600" />
</div>
<span className="text-xs font-semibold text-amber-700 bg-amber-100 px-2.5 py-1 rounded-full">
{gapSummary.total_gaps} open
</span>
</div>
<h3 className="font-semibold text-lg text-foreground mb-1">Missing Code</h3>
<p className="text-sm text-muted-foreground mb-5">
{Math.round(gapSummary.total_hours)} hours of features ready to build
</p>
<Button size="sm" asChild>
<Link href="/dashboard/gaps">
<AlertCircle className="h-4 w-4 mr-2" />
View Dashboard
</Link>
</Button>
</Card>
)}
</div>
)}
</section>

{/* Recent Repositories */}
{repositories.length > 0 && (
<section className="space-y-5">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-foreground">Recent Repositories</h2>
<Button variant="ghost" size="sm" asChild>
<Link href="/dashboard/repositories">
View All
<ArrowRight className="h-4 w-4 ml-1" />
</Link>
</Button>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{repositories.slice(0, 6).map((repo) => (
<Card key={repo.id} className="group p-5 hover:shadow-md hover:shadow-black/5 transition-all duration-200 hover:border-border">
<div className="flex items-start gap-3">
<div className="h-10 w-10 rounded-xl bg-muted/50 flex items-center justify-center flex-shrink-0 group-hover:bg-muted transition-colors">
<FolderGit2 className="h-5 w-5 text-muted-foreground" />
</div>
<div className="min-w-0 flex-1">
<h3 className="font-semibold text-foreground truncate">{repo.name}</h3>
<p className="text-xs text-muted-foreground truncate mt-0.5">{repo.full_name}</p>
{repo.language && (
<span className="inline-block mt-2 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground font-medium">
{repo.language}
</span>
)}
</div>
</div>
</Card>
))}
</div>
</section>
)}
</div>
<DashboardClient
repositories={repositories}
analyses={analyses}
completedAnalyses={completedAnalyses}
gapSummary={gapSummary}
/>
)
}
Loading
Loading