diff --git a/app/api/app-idea-chat/route.ts b/app/api/app-idea-chat/route.ts new file mode 100644 index 0000000..5e2c64d --- /dev/null +++ b/app/api/app-idea-chat/route.ts @@ -0,0 +1,166 @@ +import { NextRequest, NextResponse } from 'next/server' +import { Anthropic } from '@anthropic-ai/sdk' +import { + getAnalysisById, + getRepositoriesForAnalysis, + getBlueprintsByAnalysis, + getFilesByRepository, +} from '@/lib/queries' +import { getAnthropicModel } from '@/lib/anthropic-model' +import { getCurrentUser } from '@/lib/auth' +import { deductCredits, CREDITS } from '@/lib/credits' + +const anthropic = new Anthropic() + +export interface AppIdeaSuggestion { + name: string + tagline: string + description: string + type: string + difficulty: 'easy' | 'medium' | 'hard' + estimatedEffort: string + suggestedStack: string[] + monetizationAngle: string + whyNow: string +} + +export interface AppIdeaChatResponse { + reply: string + suggestions: AppIdeaSuggestion[] + followUpQuestions: string[] +} + +export interface ChatMessage { + role: 'user' | 'assistant' + content: string +} + +export async function POST(request: NextRequest) { + try { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }) + } + + const { message, analysisId, history = [] } = (await request.json()) as { + message: string + analysisId?: string + history?: ChatMessage[] + } + + if (!message?.trim()) { + return NextResponse.json({ error: 'Message is required' }, { status: 400 }) + } + + const creditResult = await deductCredits( + user.id, + CREDITS.PATTERN_ANALYZER_COST, + 'pattern_analyzer', + { analysisId }, + ) + if (!creditResult.success) { + return NextResponse.json({ error: creditResult.error || 'Insufficient credits' }, { status: 402 }) + } + + // Optionally load codebase context + let codebaseContext = '' + if (analysisId) { + try { + const analysis = await getAnalysisById(analysisId) + if (analysis && analysis.status === 'complete') { + const [repositories, blueprints] = await Promise.all([ + getRepositoriesForAnalysis(analysisId), + getBlueprintsByAnalysis(analysisId), + ]) + + const allFiles = ( + await Promise.all(repositories.map((r) => getFilesByRepository(r.id))) + ).flat() + + const techCount: Record = {} + for (const file of allFiles) { + for (const tech of file.technologies) { + techCount[tech] = (techCount[tech] || 0) + 1 + } + } + const topTech = Object.entries(techCount) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([t]) => t) + + codebaseContext = ` +## Developer's codebase context +Repositories: ${repositories.map((r) => r.name).join(', ')} +Top technologies: ${topTech.join(', ')} +Total files: ${allFiles.length} +Existing blueprints: ${blueprints.slice(0, 5).map((b) => b.name).join(', ') || 'none yet'} +` + } + } catch { + // Codebase context optional — continue without it + } + } + + // Build conversation history for context + const conversationHistory = history.slice(-6).map((m) => ({ + role: m.role as 'user' | 'assistant', + content: m.content, + })) + + const systemPrompt = `You are an expert product strategist and startup advisor helping developers discover what apps to build. You're having a friendly, concise conversation to help them find the perfect project idea. + +${codebaseContext} + +When responding: +- Keep your reply conversational and under 100 words +- Suggest 2-4 concrete project ideas tailored to their request${codebaseContext ? ' and their codebase' : ''} +- Ask a relevant follow-up question to refine suggestions +- Be enthusiastic and actionable + +Always respond with valid JSON only (no markdown fences): +{ + "reply": "conversational response under 100 words", + "suggestions": [ + { + "name": "Project Name", + "tagline": "One punchy sentence", + "description": "2-3 sentences", + "type": "SaaS | CLI Tool | API | Dashboard | etc", + "difficulty": "easy | medium | hard", + "estimatedEffort": "e.g. 1–2 weeks", + "suggestedStack": ["tech1", "tech2"], + "monetizationAngle": "How to charge", + "whyNow": "Why this is timely" + } + ], + "followUpQuestions": ["Question 1?", "Question 2?"] +}` + + const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [ + ...conversationHistory, + { role: 'user', content: message }, + ] + + const response = await anthropic.messages.create({ + model: getAnthropicModel(), + max_tokens: 2048, + system: systemPrompt, + messages, + }) + + const raw = response.content[0].type === 'text' ? response.content[0].text.trim() : '' + const jsonText = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '').trim() + + let parsed: AppIdeaChatResponse + try { + parsed = JSON.parse(jsonText) + } catch { + return NextResponse.json({ error: 'Failed to parse AI response' }, { status: 500 }) + } + + return NextResponse.json(parsed) + } catch (error) { + console.error('[app-idea-chat] error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/app/dashboard/analyses/[id]/page.tsx b/app/dashboard/analyses/[id]/page.tsx index c862830..abce427 100644 --- a/app/dashboard/analyses/[id]/page.tsx +++ b/app/dashboard/analyses/[id]/page.tsx @@ -33,19 +33,22 @@ export default async function AnalysisDetailPage({ getRepositoriesForAnalysis(id), getBlueprintsByAnalysis(id), ]) + } catch { + notFound() + } - if (user) { + if (user) { + try { const [subscription, viewedIds] = await Promise.all([ getSubscriptionByGithubId(user.github_id), getUserViewedBlueprintIds(user.id), ]) userPlan = subscription?.plan || 'free' viewedBlueprintIds = viewedIds - // Check if in trial via Stripe (subscription status = 'trialing') isTrialing = subscription?.status === 'trialing' + } catch { + // Subscription/views table not available yet — use free defaults } - } catch { - notFound() } if (!analysis) { diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index 4d400cf..c5c8818 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -14,7 +14,7 @@ export default async function DashboardLayout({ } return ( -
+
{children} diff --git a/app/dashboard/templates/page.tsx b/app/dashboard/templates/page.tsx index 38d764b..c3949ba 100644 --- a/app/dashboard/templates/page.tsx +++ b/app/dashboard/templates/page.tsx @@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { TemplateAssemblyCard } from '@/components/template-assembly-card' +import { CreateTemplateModal } from '@/components/create-template-modal' import { getAllTemplates, getFeaturedTemplates, type Template } from '@/lib/queries' export const dynamic = 'force-dynamic' @@ -42,29 +43,33 @@ async function TemplateHubContent() { if (setupRequired || !all.length) { return (
-
- - - -
-

Template Assembly Hub

-

Pre-built project combinations ready to assemble

+
+
+ + + +
+

Template Assembly Hub

+

Pre-built project combinations ready to assemble

+
+
-

No templates available yet

+

No templates yet

- Templates will be generated once you run an analysis on your repositories. + Create a template by combining blueprints from your analyses, or run an analysis to discover new blueprints.

- +
+ + +
) @@ -85,18 +90,21 @@ async function TemplateHubContent() {
{/* Header */}
-
- - - -
-

Template Assembly Hub

-

- Start building today from code you already have. Pre-configured templates combine your best pieces. -

+
+
+ + + +
+

Template Assembly Hub

+

+ Start building today from code you already have. Pre-configured templates combine your best pieces. +

+
+
{/* Feature Cards */} diff --git a/app/page.tsx b/app/page.tsx index 7b1fd74..7a571e9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,8 +2,7 @@ import Link from 'next/link' import { Button } from '@/components/ui/button' import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d' import { NavDropdown } from '@/components/nav-dropdown' -import { Github, ArrowRight, AlertCircle, Shield, Zap, GitBranch, Rocket, Code2, Sparkles } from 'lucide-react' -import { LaunchSignupModal } from '@/components/launch-signup-modal' +import { Github, ArrowRight, AlertCircle, Zap } from 'lucide-react' const ERROR_MESSAGES: Record = { auth_required: 'You must sign in to access the dashboard.', @@ -22,7 +21,7 @@ export default async function HomePage({ searchParams }: { searchParams: Promise const errorMessage = error ? ERROR_MESSAGES[error] ?? 'An unexpected error occurred.' : null return ( -
+
{errorMessage && (
@@ -30,195 +29,132 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
)} - {/* Animated scanlines overlay */} -
- {/* Noise overlay */} -
- {/* Launch Day Banner - Compelling Headline */} -
-
-
-
- -
-
-

- FULL LAUNCH -

-

- 5/12/2026 -

-
-
-
-

- First 1,000 Developers Get: -

-

- 14 Days Free OR 3 Analyses + 1 Blueprint -

-

- Lock in lifetime pricing. Link GitHub or GitLab today. -

-
-
-
- {/* Header */} -
+
- - + + + RepoFuse - -
{/* Hero Section */}
-
- {/* Animated grid background */} -
+
+ {/* Subtle radial glow */} +
+
- {/* Glowing orbs */} -
-
-
+
+ {/* Trust badge */} +
+ + + AI-POWERED + + + The #1 Repo Intelligence Platform +
-
-
- {/* Left content */} -
- {/* Badge */} -
- - Now in Public Beta -
- - {/* Heading */} -
-

- Everything your - - repos - have been - - - waiting - for - -

-
- - {/* Subheading */} -

- RepoFuse scans your connected GitHub or GitLab repos and surfaces buildable project ideas, detects hidden potential, and turns scattered code into your next big launch — automatically. -

+ {/* Headline */} +

+ Your repos are hiding + buildable apps +

- {/* CTA Buttons */} -
- - - - -
+ {/* Subheading */} +

+ RepoFuse scans your GitHub and GitLab repos, surfaces project ideas, and turns scattered code into your next launch —{' '} + automatically. +

- {/* Social proof */} -
- 2,400+ developers already on the waitlist -
+ {/* Primary CTA */} + + + Scan My Repos Free + + + {/* Sub-text */} +

+ Connect in seconds. No credit card required. +

+ + {/* Social proof */} +
+
+ {['#22d3ee','#a78bfa','#fb923c','#4ade80'].map((c, i) => ( +
+ ))}
+ 2,400+ developers already scanning +
- {/* Right: Terminal window */} -
-
- {/* Terminal header */} -
-
-
-
- repofuse — repo-scanner -
+ {/* Terminal preview */} +
+ {/* Window chrome */} +
+
+
+
+ RepoFuse Dashboard +
+ + Online +
+
- {/* Terminal body */} -
-
- $ - repofuse scan --org DealPatrol -
-
-
▸ Connecting to GitHub API...
-
✓ Found 14 repositories
-
▸ Analyzing code patterns...
-
-
-
📦 repo-app-architect
-
⚡ 3 buildable ideas detected
-
→ SaaS: AI Code Review Tool
-
→ Tool: Repo Health Dashboard
-
→ API: Webhook Automation Kit
-
-
-
▸ Generating project briefs...
-
-
- $ - -
-
+ {/* Content */} +
+
+ $ + repofuse scan --org DealPatrol --all-repos +
+
+
▸ Connecting to GitHub API...
+
✓ Found 14 repositories
+
▸ Analyzing code patterns & dependencies...
+
+
+
📦 repo-app-architect
+
⚡ 3 buildable ideas detected
+
→ SaaS: AI Code Review Tool
+
→ Tool: Repo Health Dashboard
+
→ API: Webhook Automation Kit
+
+
▸ Generating full project briefs...
+
+ $ +
@@ -226,69 +162,49 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
{/* Metrics Strip */} -
+
-
-
-

12k+

-

Repos Scanned

-
-
-

4.1k

-

Ideas Found

-
-
-

89%

-

Code Reuse

-
-
-

<30s

-

Analysis Time

-
+
+ {[ + { val: '12k+', label: 'Repos Scanned', color: 'text-cyan-400' }, + { val: '4.1k', label: 'Ideas Found', color: 'text-orange-400' }, + { val: '89%', label: 'Code Reuse', color: 'text-purple-400' }, + { val: '<30s', label: 'Analysis Time', color: 'text-cyan-400' }, + ].map((m) => ( +
+

{m.val}

+

{m.label}

+
+ ))}
{/* How It Works */} -
-
-
-
- +
+
+
+
HOW IT WORKS
-

Four steps to
- - buildable blueprints - +

+ Four steps to
+ buildable blueprints

-
- {/* Connecting line */} -
- +
{[ { num: '01', title: 'Connect', icon: '🔗', desc: 'OAuth in one click. Read-only access to your repos.' }, { num: '02', title: 'Scan', icon: '⚡', desc: 'AI analyzes structure, patterns, and dependencies.' }, { num: '03', title: 'Discover', icon: '💡', desc: 'Ideas surface instantly, ranked by viability.' }, - { num: '04', title: 'Build', icon: '🚀', desc: 'Get full briefs, stack recs, and MVP roadmaps.' } + { num: '04', title: 'Build', icon: '🚀', desc: 'Get full briefs, stack recs, and MVP roadmaps.' }, ].map((step, i) => ( -
-
- {/* Step number background */} -
-
- {step.num} -
-
-

{step.title}

-

{step.desc}

-
+
+
{step.icon}
+
{step.num}
+

{step.title}

+

{step.desc}

))}
@@ -297,78 +213,71 @@ export default async function HomePage({ searchParams }: { searchParams: Promise {/* Feature Grid */}
-
-
-
- +
+
+
FEATURES
-

Everything your repos
- - have been waiting for - +

+ Everything your repos
+ have been waiting for

-
+
{[ { icon: '⚡', title: 'AI Repo Scanner', desc: 'Deep analysis of your codebase structure and patterns in seconds.' }, { icon: '💡', title: 'Idea Surfacer', desc: 'Turns existing code into ranked, buildable project ideas.' }, { icon: '🔗', title: 'Multi-Repo Fusion', desc: 'Cross-reference patterns across all your repos simultaneously.' }, { icon: '📊', title: 'Health Dashboard', desc: 'Live metrics on code quality and technical debt.' }, { icon: '📋', title: 'Launch Briefs', desc: 'AI-generated product briefs for every detected idea.' }, - { icon: '🔒', title: 'Private by Default', desc: 'Your code never leaves your control.' } - ].map((feature, i) => ( -
-
{feature.icon}
-

{feature.title}

-

{feature.desc}

-
+ { icon: '🔒', title: 'Private by Default', desc: 'Your code never leaves your control.' }, + ].map((f, i) => ( +
+
{f.icon}
+

{f.title}

+

{f.desc}

))}
- {/* CTA Section */} -
-
-

+ {/* Bottom CTA */} +
+
+

Your next product is
- - already in your repos - + already in your repos

-

+

Join 2,400+ developers who've stopped guessing and started shipping.

- -

- no credit card required · read-only access + + + Start Scanning Now + + +

+ no credit card · read-only access · cancel anytime

{/* Footer */} -