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
9 changes: 5 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions app/api/admin/reward-config/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
Expand All @@ -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 });
}
2 changes: 1 addition & 1 deletion app/api/analytics/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
3 changes: 3 additions & 0 deletions app/api/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const authOptions: NextAuthOptions = {
return {
id: `guest-${crypto.randomUUID()}`,
name: 'Guest',
} as any;
};
},
}),
],
Expand Down
7 changes: 5 additions & 2 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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<string, number> = {}, latency: number): number {
// Default weights if not provided
const defaultWeights = {
accuracy: 1.0,
Expand All @@ -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') ||
Expand Down
4 changes: 2 additions & 2 deletions app/api/placeholder/[width]/[height]/route.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
3 changes: 1 addition & 2 deletions app/api/rl/train-reward/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 20 additions & 21 deletions app/api/status/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
);
}
}
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(),
});
}
2 changes: 1 addition & 1 deletion app/components/CodeSandbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export default function CodeSandbox() {
Result
</h4>
<div className="text-slate-600 dark:text-slate-300 text-sm">
Click "Run Analysis" to see the AI response...
Click &quot;Run Analysis&quot; to see the AI response...
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/CompetitorComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ const CompetitorComparison: React.FC = () => {
Calculate Your Savings
</h3>
<p className="text-gray-600 mb-6">
See how much you'll save by switching from OpenAI to DreamForge for your vision AI needs.
See how much you&apos;ll save by switching from OpenAI to DreamForge for your vision AI needs.
</p>
<div className="space-y-4">
<div className="flex items-center">
Expand Down
2 changes: 1 addition & 1 deletion app/components/EmbeddedPlayground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
🌔 Moondream Playground
</h2>
<p className="text-slate-600 dark:text-slate-400">
Try Moondream's vision-language capabilities instantly
Try Moondream&apos;s vision-language capabilities instantly
</p>
</div>
<button
Expand Down Expand Up @@ -215,7 +215,7 @@
>
<div className="flex items-center space-x-3 p-3">
<div className="relative">
<img

Check warning on line 218 in app/components/EmbeddedPlayground.tsx

View workflow job for this annotation

GitHub Actions / build-test

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={image.url}
alt={image.alt}
className="w-16 h-16 object-cover rounded-lg"
Expand Down Expand Up @@ -318,7 +318,7 @@
{selectedImage ? (
<div className="space-y-3">
<div className="relative">
<img

Check warning on line 321 in app/components/EmbeddedPlayground.tsx

View workflow job for this annotation

GitHub Actions / build-test

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={selectedImage}
alt="Selected for analysis"
className="w-full h-48 object-cover rounded-lg border border-slate-200 dark:border-slate-600"
Expand Down
2 changes: 1 addition & 1 deletion app/components/EnterpriseInfrastructure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface InfrastructureFeature {
id: string;
title: string;
description: string;
icon: React.ComponentType<any>;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
metrics: {
label: string;
value: string;
Expand Down
9 changes: 2 additions & 7 deletions app/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@

import { useState } from 'react';
import { motion } from 'framer-motion';
import { PlayIcon, CodeBracketIcon, DocumentTextIcon, BeakerIcon } from '@heroicons/react/24/outline';
import { PlayIcon, CodeBracketIcon, DocumentTextIcon } from '@heroicons/react/24/outline';
import InteractiveBenchmark from './InteractiveBenchmark';
import EmbeddedPlayground from './EmbeddedPlayground';

export default function Hero() {
const [isPlaygroundOpen, setIsPlaygroundOpen] = useState(false);

const realTimeMetrics = [
{ label: 'Models Deployed', value: '8.2k+', icon: '' },
{ label: 'Avg Latency', value: '15ms', icon: '' },
{ label: 'Uptime', value: '99.9%', icon: '' },
{ label: 'GitHub Stars', value: '8.2k', icon: '' }
];


return (
<>
Expand Down
4 changes: 1 addition & 3 deletions app/components/InteractiveBenchmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ export default function InteractiveBenchmark() {
};

const currentBenchmark = benchmarks[selectedTask];
const maxScore = selectedTask === 'cost'
? Math.min(...currentBenchmark.models.map(m => m.score)) // For cost, use min as baseline
: Math.max(...currentBenchmark.models.map(m => m.score)); // For others, use max
// For others, use max

return (
<div className="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-xl border border-slate-200 dark:border-slate-700">
Expand Down
4 changes: 2 additions & 2 deletions app/components/ModelGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default function ModelGallery() {
Moondream Model Variants
</h2>
<p className="text-xl text-slate-600 dark:text-slate-300 max-w-3xl mx-auto">
Deploy Moondream's 1.6B parameter vision-language model with different quantization levels. AI-powered insights included.
Deploy Moondream&apos;s 1.6B parameter vision-language model with different quantization levels. AI-powered insights included.
</p>
</motion.div>

Expand Down Expand Up @@ -207,7 +207,7 @@ export default function ModelGallery() {
</tr>
</thead>
<tbody>
{models.map((model, index) => (
{models.map((model) => (
<tr
key={model.id}
className={`border-b border-slate-100 dark:border-slate-700 ${
Expand Down
2 changes: 1 addition & 1 deletion app/components/RLPipeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface PipelineStep {
id: string;
title: string;
description: string;
icon: React.ComponentType<any>;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
metrics: {
label: string;
value: string;
Expand Down
8 changes: 3 additions & 5 deletions app/components/RewardFunctionBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { motion } from 'framer-motion';
import { useState } from 'react';
import {
PlusIcon,
TrashIcon,
ArrowUpIcon,
ArrowDownIcon
TrashIcon
} from '@heroicons/react/24/outline';

interface RewardMetric {
Expand Down Expand Up @@ -67,7 +65,7 @@ export default function RewardFunctionBuilder() {
setMetrics(metrics.filter(m => m.id !== id));
};

const updateMetric = (id: string, field: keyof RewardMetric, value: any) => {
const updateMetric = <K extends keyof RewardMetric>(id: string, field: K, value: RewardMetric[K]) => {
setMetrics(metrics.map(m =>
m.id === id ? { ...m, [field]: value } : m
));
Expand Down Expand Up @@ -195,7 +193,7 @@ export default function RewardFunctionBuilder() {
</label>
<select
value={metric.operator}
onChange={(e) => updateMetric(metric.id, 'operator', e.target.value)}
onChange={(e) => updateMetric(metric.id, 'operator', e.target.value === 'minimize' ? 'minimize' : 'maximize')}
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white"
>
<option value="maximize">Maximize</option>
Expand Down
4 changes: 2 additions & 2 deletions app/components/Roadmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface RoadmapItem {
description: string;
status: 'completed' | 'in-progress' | 'planned';
quarter: string;
icon: React.ComponentType<any>;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
features: string[];
}

Expand Down Expand Up @@ -118,7 +118,7 @@ export default function Roadmap() {
Product Roadmap
</h2>
<p className="text-xl text-slate-600 dark:text-slate-300 max-w-3xl mx-auto">
Our development roadmap shows what's coming next. We're building the future of vision-language AI.
Our development roadmap shows what&apos;s coming next. We&apos;re building the future of vision-language AI.
</p>
</motion.div>

Expand Down
2 changes: 1 addition & 1 deletion app/components/SimpleRLPipeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ const SimpleRLPipeline: React.FC = () => {
Ready to Get Started?
</h3>
<p className="text-blue-100 mb-6 max-w-2xl mx-auto">
Join thousands of developers who've already made the switch to faster, cheaper, and more accurate vision AI.
Join thousands of developers who&apos;ve already made the switch to faster, cheaper, and more accurate vision AI.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button className="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition-colors">
Expand Down
6 changes: 3 additions & 3 deletions app/components/features/ABTestView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useEffect } from 'react';
import { useState } from 'react';
import { motion } from 'framer-motion';
import {
ArrowsPointingOutIcon,
Expand All @@ -27,7 +27,7 @@ interface ABTestViewProps {
onOutputSelect?: (output: ModelOutput) => void;
}

export default function ABTestView({ prompt, imageUrl, onOutputSelect }: ABTestViewProps) {
export default function ABTestView({ prompt, onOutputSelect }: ABTestViewProps) {
const [outputs, setOutputs] = useState<ModelOutput[]>([]);
const [selectedOutput, setSelectedOutput] = useState<number | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
Expand Down Expand Up @@ -245,7 +245,7 @@ export default function ABTestView({ prompt, imageUrl, onOutputSelect }: ABTestV
</div>
) : (
<div className="text-gray-400 text-sm">
Click "Run A/B Test" to generate responses
Click &quot;Run A/B Test&quot; to generate responses
</div>
)}
</div>
Expand Down
Loading
Loading