diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca88d8e..fa5008d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,14 @@ jobs: run: npm run lint --if-present - name: Unit & Integration Tests run: npm test + - name: Install Playwright browsers + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: 0 - name: E2E Tests run: npm run e2e env: + PLAYWRIGHT_BROWSERS_PATH: 0 MOONDREAM_KEY: ${{ secrets.MOONDREAM_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} MONGODB_URI: ${{ secrets.MONGODB_URI }} @@ -30,7 +35,3 @@ jobs: ADMIN_EMAIL: ${{ secrets.ADMIN_EMAIL }} - name: Build run: npm run build - - name: Install Playwright browsers - run: npx playwright install --with-deps - env: - PLAYWRIGHT_BROWSERS_PATH: 0 \ No newline at end of file diff --git a/README.md b/README.md index 55a2de2..e2714d3 100644 --- a/README.md +++ b/README.md @@ -190,3 +190,17 @@ The project includes a comprehensive `vercel.json` configuration for: 4. **Production**: Deploy to Vercel with environment variables This implementation showcases the complete "Vercel of VLMs" experience with enterprise-ready features for model development, testing, and deployment. + +## Build without inference credentials + +`npm ci && npm run build` can build the frontend without `MOONDREAM_KEY`. +Provider clients initialize when inference is requested. `/api/analyze` and +`/api/generate` return HTTP 503 with setup guidance when that key is missing; +no synthetic inference answer replaces a missing provider. + +Run `npm test -- tests/moondream-config.test.ts` for the request-time credential +regression. A compiled frontend is not proof of live inference: configure a real +Moondream key and verify upload → inference → displayed response before recording +that workflow. Existing reward scores use heuristics and are not measured model +accuracy. ESLint is pinned to the major supported by Next14's lint command. Lint now runs and +reports existing source violations; passing compilation does not mean lint is clean. diff --git a/app/api/admin/reward-config/route.ts b/app/api/admin/reward-config/route.ts index 91e834e..038a636 100644 --- a/app/api/admin/reward-config/route.ts +++ b/app/api/admin/reward-config/route.ts @@ -18,7 +18,7 @@ export async function GET(req: NextRequest) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } await db(); - const RewardModel = RewardConfig as any; + const RewardModel = RewardConfig; const doc = await RewardModel.findOne({}).sort({ updatedAt: -1 }); return NextResponse.json({ data: doc }); } @@ -33,7 +33,7 @@ export async function PUT(req: NextRequest) { return NextResponse.json({ error: parse.error.flatten() }, { status: 400 }); } await db(); - const RewardModel = RewardConfig as any; + const RewardModel = RewardConfig; const doc = await RewardModel.findOneAndUpdate({}, parse.data, { upsert: true, new: true }); return NextResponse.json({ data: doc }); } \ No newline at end of file diff --git a/app/api/analytics/route.ts b/app/api/analytics/route.ts index c7ab38d..44f4c5a 100644 --- a/app/api/analytics/route.ts +++ b/app/api/analytics/route.ts @@ -52,7 +52,7 @@ export async function GET(request: NextRequest) { function generateRecentCalls(branch: string, count: number) { const endpoints = ['/api/generate', '/api/retrain', '/api/analyze']; const models = ['moondream2', 'moondream2-text', 'moondream2-detailed']; - const calls: any[] = []; + const calls: Array<{ timestamp: number; endpoint: string; model: string; latency: number; cost: number; status: string; branch: string }> = []; for (let i = 0; i < count; i++) { calls.push({ diff --git a/app/api/analyze/route.ts b/app/api/analyze/route.ts index 774af85..6107075 100644 --- a/app/api/analyze/route.ts +++ b/app/api/analyze/route.ts @@ -39,6 +39,9 @@ export async function POST(request: NextRequest) { } } + if (!process.env.MOONDREAM_KEY || process.env.MOONDREAM_KEY === 'your-real-moondream-api-key') { + return NextResponse.json({ success: false, error: 'Configure MOONDREAM_KEY to enable image inference.' }, { status: 503 }); + } // Analyze the image const result = await moondream.analyzeImage(image, prompt); diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 8e7f104..ad19a59 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -20,7 +20,7 @@ const authOptions: NextAuthOptions = { return { id: `guest-${crypto.randomUUID()}`, name: 'Guest', - } as any; + }; }, }), ], diff --git a/app/api/generate/route.ts b/app/api/generate/route.ts index b3291f5..a4495b7 100644 --- a/app/api/generate/route.ts +++ b/app/api/generate/route.ts @@ -14,6 +14,9 @@ export async function POST(request: NextRequest) { const startTime = Date.now(); + if (!process.env.MOONDREAM_KEY || process.env.MOONDREAM_KEY === 'your-real-moondream-api-key') { + return NextResponse.json({ success: false, error: 'Configure MOONDREAM_KEY to enable image inference.' }, { status: 503 }); + } // Generate response using Moondream const response = await moondream.analyzeImage(image, prompt); @@ -43,7 +46,7 @@ export async function POST(request: NextRequest) { } } -function calculateRewardScore(response: string, weights: any = {}, latency: number): number { +function calculateRewardScore(response: string, weights: Record = {}, latency: number): number { // Default weights if not provided const defaultWeights = { accuracy: 1.0, @@ -57,7 +60,7 @@ function calculateRewardScore(response: string, weights: any = {}, latency: numb const w = { ...defaultWeights, ...weights }; // Simple heuristics for scoring - const responseLength = response.length; + const wordCount = response.split(' ').length; const hasDetails = wordCount > 20; const isCreative = response.toLowerCase().includes('unique') || diff --git a/app/api/placeholder/[width]/[height]/route.ts b/app/api/placeholder/[width]/[height]/route.ts index f0d90e2..966950b 100644 --- a/app/api/placeholder/[width]/[height]/route.ts +++ b/app/api/placeholder/[width]/[height]/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; -export async function GET(req: NextRequest, { params }: { params: { width: string; height: string } }) { - const { width, height } = params; +export async function GET(req: NextRequest, { params }: { params: Promise<{ width: string; height: string }> }) { + const { width, height } = await params; const text = req.nextUrl.searchParams.get('text') || 'Placeholder'; const url = `https://via.placeholder.com/${width}x${height}.png?text=${encodeURIComponent(text)}`; return NextResponse.redirect(url, 302); diff --git a/app/api/rl/train-reward/route.ts b/app/api/rl/train-reward/route.ts index 71fe62d..7644e26 100644 --- a/app/api/rl/train-reward/route.ts +++ b/app/api/rl/train-reward/route.ts @@ -6,8 +6,7 @@ export async function POST(request: NextRequest) { const { learningRate = 1e-5, batchSize = 32, - epochs = 10, - feedbackId + epochs = 10 } = body; // Simulate reward model training diff --git a/app/api/status/route.ts b/app/api/status/route.ts index b2d40f4..8d1cc69 100644 --- a/app/api/status/route.ts +++ b/app/api/status/route.ts @@ -1,24 +1,23 @@ import { NextResponse } from 'next/server'; -export async function GET() { - try { - const status = { - status: 'healthy', - timestamp: new Date().toISOString(), - version: '1.0.0', - services: { - api: 'operational', - database: 'operational', - ai: 'operational', - }, - uptime: process.uptime(), - }; +export const dynamic = 'force-dynamic'; - return NextResponse.json(status); - } catch (error) { - return NextResponse.json( - { status: 'error', error: 'Service unavailable' }, - { status: 503 } - ); - } -} \ No newline at end of file +export async function GET() { + // Configuration is observable here; remote service availability is not. + const databaseConfigured = Boolean(process.env.MONGODB_URI?.trim()) && + !process.env.MONGODB_URI?.includes('your-'); + const visionConfigured = Boolean(process.env.MOONDREAM_KEY?.trim()) && + process.env.MOONDREAM_KEY !== 'your-real-moondream-api-key'; + return NextResponse.json({ + status: databaseConfigured && visionConfigured ? 'unverified' : 'degraded', + timestamp: new Date().toISOString(), + version: '1.0.0', + services: { + api: 'operational', + database: databaseConfigured ? 'configured_unverified' : 'not_configured', + ai: visionConfigured ? 'configured_unverified' : 'not_configured', + }, + dependenciesVerified: false, + uptime: process.uptime(), + }); +} diff --git a/app/components/CodeSandbox.tsx b/app/components/CodeSandbox.tsx index e460b3b..bd2807a 100644 --- a/app/components/CodeSandbox.tsx +++ b/app/components/CodeSandbox.tsx @@ -253,7 +253,7 @@ export default function CodeSandbox() { Result
- Click "Run Analysis" to see the AI response... + Click "Run Analysis" to see the AI response...
diff --git a/app/components/CompetitorComparison.tsx b/app/components/CompetitorComparison.tsx index b905c2c..39e8117 100644 --- a/app/components/CompetitorComparison.tsx +++ b/app/components/CompetitorComparison.tsx @@ -176,7 +176,7 @@ const CompetitorComparison: React.FC = () => { Calculate Your Savings

- See how much you'll save by switching from OpenAI to DreamForge for your vision AI needs. + See how much you'll save by switching from OpenAI to DreamForge for your vision AI needs.

diff --git a/app/components/EmbeddedPlayground.tsx b/app/components/EmbeddedPlayground.tsx index 329b9a8..b6bc0e7 100644 --- a/app/components/EmbeddedPlayground.tsx +++ b/app/components/EmbeddedPlayground.tsx @@ -164,7 +164,7 @@ export default function EmbeddedPlayground({ isOpen, onClose }: EmbeddedPlaygrou 🌔 Moondream Playground

- Try Moondream's vision-language capabilities instantly + Try Moondream's vision-language capabilities instantly