Initial commit — AI-powered coding tutor (Professor)
Next.js 16, React 19, Monaco editor, Anthropic SDK, multi-provider AI, Wandbox Python execution, iframe HTML preview, SQLite auth + session persistence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
84
app/api/models/route.ts
Normal file
84
app/api/models/route.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import type { ProviderId } from '@/types';
|
||||
import { PROVIDER_MAP } from '@/lib/providers';
|
||||
|
||||
interface ModelsRequestBody {
|
||||
provider: ProviderId;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
let body: ModelsRequestBody;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { provider, apiKey, baseUrl } = body;
|
||||
const def = PROVIDER_MAP[provider];
|
||||
const base = baseUrl?.trim() || def.defaultBaseUrl;
|
||||
|
||||
try {
|
||||
let models: string[] = [];
|
||||
|
||||
if (provider === 'anthropic') {
|
||||
const key = apiKey?.trim() || process.env.ANTHROPIC_API_KEY;
|
||||
if (!key) {
|
||||
return NextResponse.json({ error: 'No API key — enter one above or set ANTHROPIC_API_KEY in .env.local' }, { status: 400 });
|
||||
}
|
||||
const res = await fetch('https://api.anthropic.com/v1/models?limit=100', {
|
||||
headers: {
|
||||
'x-api-key': key,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
return NextResponse.json({ error: `Anthropic: ${res.status} ${text}` }, { status: res.status });
|
||||
}
|
||||
const data = await res.json();
|
||||
models = (data.data ?? []).map((m: { id: string }) => m.id);
|
||||
}
|
||||
|
||||
else if (provider === 'openrouter') {
|
||||
const headers: Record<string, string> = {};
|
||||
if (apiKey?.trim()) headers['Authorization'] = `Bearer ${apiKey.trim()}`;
|
||||
|
||||
const res = await fetch('https://openrouter.ai/api/v1/models', { headers });
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
return NextResponse.json({ error: `OpenRouter: ${res.status} ${text}` }, { status: res.status });
|
||||
}
|
||||
const data = await res.json();
|
||||
// Sort by id for easier browsing
|
||||
models = (data.data ?? [])
|
||||
.map((m: { id: string }) => m.id)
|
||||
.sort((a: string, b: string) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
else {
|
||||
// LM Studio and Ollama — OpenAI-compatible /v1/models
|
||||
const res = await fetch(`${base}/models`, {
|
||||
headers: { Authorization: 'Bearer none' },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
return NextResponse.json({ error: `${def.label}: ${res.status} ${text || 'Connection refused'}` }, { status: res.status });
|
||||
}
|
||||
const data = await res.json();
|
||||
models = (data.data ?? []).map((m: { id: string }) => m.id);
|
||||
}
|
||||
|
||||
return NextResponse.json({ models });
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Unknown error';
|
||||
const isTimeout = msg.includes('timeout') || msg.includes('abort');
|
||||
return NextResponse.json(
|
||||
{ error: isTimeout ? `Could not reach ${def.label} — is it running?` : msg },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user