Untuk chatbot Next.js yang memerlukan beberapa penyedia model, backend paling berguna ialah yang memusatkan pengesahan sambil membenarkan aplikasi memilih model bagi setiap permintaan. CometAPI menyediakan corak tersebut: pelayan menyimpan satu kunci API dan URL asas serasi OpenAI https://api.cometapi.com/v1, manakala medan model dalam permintaan memilih GPT, Claude, Gemini atau model sembang lain yang tersedia.
Tutorial ini membina projek App Router yang berfungsi dengan senarai dibenarkan model di sisi pelayan, Pengendali Laluan penstriman, pemilih model pada pelayar, pembolehubah persekitaran yang dilindungi, antara muka sembang lengkap, dan panduan penerapan. Kunci CometAPI tidak pernah sampai ke pelayar.
Apakah Aplikasi Sembang AI Multi-Model?
Seni bina sembang AI multi-model meletakkan satu backend yang dikawal aplikasi antara antara muka pengguna dan beberapa penyedia model. Dalam projek ini, pelayar menghantar perbualan ke /api/chat, manakala pelayan menyimpan kelayakan CometAPI secara peribadi, mengesahkan ID model yang diminta, dan meneruskan permintaan melalui satu API serasi.
Bagaimana Penukaran Model Berfungsi dalam Next.js?
Menukar model ialah keputusan perutean. Pelayar menghantar ID model yang diluluskan bersama perbualan ke /api/chat. Pengendali Laluan menambah kelayakan CometAPI, memanggil POST /v1/chat/completions, dan menstrim respons model terpilih kembali ke pelayar.
Hujung bersama tidak menjadikan setiap model sama. Gaya output, gelagat alat, parameter yang disokong, had konteks, dan harga boleh berbeza. Kekalkan ID model dalam senarai dibenarkan di sisi pelayan dan uji setiap laluan dengan prompt aplikasi yang sama sebelum menawarkannya kepada pengguna.
Apa yang Anda Akan Bina
Aplikasi siap mempunyai satu backend Next.js dan tiga laluan sembang boleh pilih:
| Model ID | Contoh peranan | Harga CometAPI |
|---|---|---|
| gemini-3.7-flash | Sembang volum tinggi yang menjimatkan kos | $0.60 input / $3.00 output |
| claude-opus-5 | Penaakulan premium yang fokus | $4 input / $20 output |
| gpt-5.6 | gpt-5.6 ialah laluan CometAPI generik untuk GPT-5.6 dan kini memetakan ke tier Sol. | $3.2 input / $16 output dalam tier konteks pendek |
Nota harga: Harga yang ditunjukkan di bawah adalah dalam USD per sejuta token, diperiksa pada August 21, 2026, dan mungkin berubah. Sentiasa sahkan kadar semasa pada halaman model sebelum penggunaan produksi. Jadual ini ialah contoh perutean, bukan penarafan kualiti. GPT-5.6 menggunakan tier harga lebih tinggi di atas 272,000 token. Lihat halaman model Gemini 3.7 Flash, Claude Opus 5, dan GPT-5.6 yang dipaut bagi kadar bertarikh yang digunakan di sini.
CometAPI menetapkan harga model dengan telus berbanding API rasmi setiap penyedia. Model dengan harga rasmi yang disatukan — OpenAI, Claude, Gemini, dan seumpamanya — dibilkan mengikut token pada nisbah 0.8:1 berbanding harga rasmi, diskaun 20%; model tanpa API rasmi (MidJourney, Kling, Luma) dibilkan setiap panggilan pada kadar yang ditetapkan CometAPI, juga didiskaunkan 20%. Lihat CometAPI Pricing Guide untuk formula markup, kadar setiap model, dan unit pengebilan.
Tiga laluan ini meliputi pertukaran nilai berbeza. Gemini 3.7 Flash ialah “workhorse” agen cekap Google, dengan input multimodal dan tetingkap konteks 1,048,576 token untuk sembang volum tinggi, pengaturcaraan, dan aliran kerja pengetahuan. Claude Opus 5 ialah model penaakulan frontier Anthropic, kukuh pada analisis berbilang langkah, kod, dan penulisan panjang yang berhati-hati pada harga premium. GPT-5.6 ialah model tujuan umum OpenAI, mengimbangi penaakulan, penggunaan alat, dan draf merentas tetingkap konteks luas untuk trafik produksi harian.
Sebelum Anda Bermula
Anda memerlukan Node.js, npm, akaun CometAPI, dan kunci API sisi pelayan daripada CometAPI Quick Start. Tiga ID model tepat di atas tersedia dalam katalog langsung pada August 21, 2026, tidak ditanda akan datang, dan mendedahkan POST /v1/chat/completions.
Tutorial menggunakan tetapan bersama berikut:
- API key:
COMETAPI_API_KEY - Base URL:
https://api.cometapi.com/v1 - Catalog endpoint:
GEThttps://api.cometapi.com/api/models
Langkah 1: Cipta Aplikasi Next.js
npx create-next-app@latest multi-model-chat --ts --app --eslintcd multi-model-chatnpm run dev
Versi ini tidak memerlukan SDK penyedia model. Pelayan menggunakan API fetch terbina dalam dan meneruskan aliran Server-Sent Events huluan.
Langkah 2: Kekalkan Kunci CometAPI di Pelayan
Cipta .env.local dalam akar projek:
COMETAPI_API_KEY=replace_with_your_cometapi_keyCOMETAPI_BASE_URL=https://api.cometapi.com/v1
Jangan awali kunci dengan NEXT_PUBLIC_. Next.js hanya mendedahkan pembolehubah dengan awalan tersebut kepada bundel pelayar; kunci API berada dalam Pengendali Laluan sisi pelayan.
Langkah 3: Takrifkan Polisi Model
Letakkan ID yang dibenarkan di backend, bukan hanya dalam menu lungsur. Pengguna boleh memintas kawalan pelayar dan memanggil laluan anda secara terus, jadi pelayan mesti menolak nilai model yang tidak diketahui.
const ALLOWED_MODELS = [ "gemini-3.7-flash", "claude-opus-5", "gpt-5.6",] as const;type ModelId = (typeof ALLOWED_MODELS)[number];
Klien akan menggunakan tiga ID yang sama untuk pilihannya, manakala pelayan kekal sebagai sumber kebenaran.
Langkah 4: Strim CometAPI melalui Pengendali Laluan
Cipta app/api/chat/route.ts. Laluan ini mengesahkan payload sembang teks sahaja, memanggil CometAPI dengan stream: true, dan mengembalikan aliran acara huluan tanpa mendedahkan kelayakan.
export const runtime = "nodejs";export const dynamic = "force-dynamic";const ALLOWED_MODELS = [ "gemini-3.7-flash", "claude-opus-5", "gpt-5.6",] as const;type ModelId = (typeof ALLOWED_MODELS)[number];type ChatMessage = { role: "system" | "user" | "assistant"; content: string;};function isModelId(value: unknown): value is ModelId { return ( typeof value === "string" && (ALLOWED_MODELS as readonly string[]).includes(value) );}function isChatMessage(value: unknown): value is ChatMessage { if (typeof value !== "object" || value === null) return false; const message = value as Record<string, unknown>; return ( ["system", "user", "assistant"].includes(String(message.role)) && typeof message.content === "string" && message.content.length > 0 && message.content.length <= 20_000 );}export async function POST(request: Request) { const apiKey = process.env.COMETAPI_API_KEY; const baseUrl = ( process.env.COMETAPI_BASE_URL || "https://api.cometapi.com/v1" ).replace(/\/$/, ""); if (!apiKey) { return Response.json( { error: "COMETAPI_API_KEY tidak dikonfigurasikan." }, { status: 500 }, ); } let payload: { model?: unknown; messages?: unknown }; try { payload = await request.json(); } catch { return Response.json({ error: "Badan JSON tidak sah." }, { status: 400 }); } if (!isModelId(payload.model)) { return Response.json({ error: "ID model tidak disokong." }, { status: 400 }); } if ( !Array.isArray(payload.messages) || payload.messages.length === 0 || payload.messages.length > 50 || !payload.messages.every(isChatMessage) ) { return Response.json( { error: "messages mesti mengandungi 1 hingga 50 mesej teks yang sah." }, { status: 400 }, ); } const upstream = await fetch(`${baseUrl}/chat/completions`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: payload.model, messages: payload.messages, stream: true, }), cache: "no-store", signal: request.signal, }); if (!upstream.ok) { const requestId = upstream.headers.get("x-request-id"); console.error("CometAPI request failed", { status: upstream.status, requestId, }); return Response.json( { error: "Permintaan model yang dipilih gagal.", status: upstream.status, requestId, }, { status: upstream.status }, ); } if (!upstream.body) { return Response.json( { error: "Model tidak mengembalikan badan respons." }, { status: 502 }, ); } return new Response(upstream.body, { status: 200, headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", }, });}
Pengendali menghantar isyarat nyahsambung pelayar ke huluan, jadi menutup permintaan boleh menghentikan penjanaan yang tidak perlu. Ia juga memulangkan ralat yang disanitasi kepada klien sambil mengekalkan butiran diagnostik dalam log pelayan.
Langkah 5: Tambah Pemilihan Model dan Huraian Strim
Cipta komponen klien dalam app/page.tsx. Ia menghantar model yang dipilih bersama perbualan, menghuraikan setiap acara data:, dan menambah teks delta.content bertambah kepada mesej pembantu terakhir.
Pelayar tidak pernah memanggil CometAPI secara terus. Destinasi satu-satunya ialah laluan /api/chat anda, yang menyimpan kunci secara peribadi dan menguatkuasakan senarai dibenarkan.
Kod Projek Teras
Pengendali Laluan di atas ialah keseluruhan fail app/api/chat/route.ts. Tambah halaman, susun atur, dan helaian gaya berikut untuk menyiapkan projek yang boleh dijalankan.
app/page.tsx
"use client";import { FormEvent, useState } from "react";const MODEL_OPTIONS = [ { id: "gemini-3.7-flash", label: "Gemini 3.7 Flash" }, { id: "claude-opus-5", label: "Claude Opus 5" }, { id: "gpt-5.6", label: "GPT-5.6" },] as const;type Role = "user" | "assistant";type Message = { role: Role; content: string };function textFromSseLine(line: string): string { const trimmed = line.trim(); if (!trimmed.startsWith("data:")) return ""; const data = trimmed.slice(5).trim(); if (!data || data === "[DONE]") return ""; try { const event = JSON.parse(data); return event.choices?.[0]?.delta?.content ?? ""; } catch { return ""; }}export default function Home() { const [model, setModel] = useState("gemini-3.7-flash"); const [messages, setMessages] = useState<Message[]>([]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); function appendAssistantText(text: string) { if (!text) return; setMessages((current) => { const next = [...current]; const lastIndex = next.length - 1; if (lastIndex >= 0 && next[lastIndex].role === "assistant") { next[lastIndex] = { ...next[lastIndex], content: next[lastIndex].content + text, }; } return next; }); } async function sendMessage(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const content = input.trim(); if (!content || loading) return; const outgoing: Message[] = [...messages, { role: "user", content }]; setMessages([...outgoing, { role: "assistant", content: "" }]); setInput(""); setError(""); setLoading(true); try { const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model, messages: outgoing }), }); if (!response.ok) { const body = await response.json().catch(() => ({})); throw new Error(body.error || `Request failed with ${response.status}`); } if (!response.body) throw new Error("Penstriman tidak tersedia."); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { value, done } = await reader.read(); buffer += decoder.decode(value, { stream: !done }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { appendAssistantText(textFromSseLine(line)); } if (done) { appendAssistantText(textFromSseLine(buffer)); break; } } } catch (requestError) { setError( requestError instanceof Error ? requestError.message : "Permintaan gagal.", ); } finally { setLoading(false); } } return ( <main className="shell"> <section className="chat"> <header> <p className="eyebrow">Next.js + CometAPI</p> <h1>Sembang multi-model</h1> <label> Model <select value={model} onChange={(event) => setModel(event.target.value)} disabled={loading} > {MODEL_OPTIONS.map((option) => ( <option key={option.id} value={option.id}> {option.label} </option> ))} </select> </label> </header> <div className="messages" aria-live="polite"> {messages.length === 0 ? ( <p className="empty">Pilih model dan hantar mesej.</p> ) : ( messages.map((message, index) => ( <article className={message.role} key={`${message.role}-${index}`}> <b>{message.role === "user" ? "Anda" : "Pembantu"}</b> <p>{message.content || "…"}</p> </article> )) )} </div> <form onSubmit={sendMessage}> <textarea value={input} onChange={(event) => setInput(event.target.value)} placeholder="Tanya sesuatu…" rows={3} maxLength={20_000} /> <button disabled={loading || !input.trim()} type="submit"> {loading ? "Menstrim…" : "Hantar"} </button> </form> {error ? <p className="error">{error}</p> : null} </section> </main> );}
app/layout.tsx
import type { Metadata } from "next";import "./globals.css";export const metadata: Metadata = { title: "Sembang Multi-Model", description: "Aplikasi sembang Next.js dengan penstriman dikuasakan oleh CometAPI.",};export default function RootLayout({ children,}: Readonly<{ children: React.ReactNode }>) { return ( <html lang="en"> <body>{children}</body> </html> );}
app/globals.css
:root { color-scheme: dark; font-family: Arial, sans-serif; background: #07111f; color: #eef4ff;}* { box-sizing: border-box; }body { margin: 0; }button, select, textarea { font: inherit; }/* Layout shell and chat card */.shell { min-height: 100vh; display: grid; place-items: center; padding: 32px 16px; }.chat { width: min(820px, 100%); background: #0d1b2e; border: 1px solid #223957; border-radius: 20px; padding: 24px; }/* Message list and bubbles */.messages { min-height: 360px; display: grid; align-content: start; gap: 12px; margin: 24px 0; }.messages article { max-width: 85%; padding: 12px 14px; border-radius: 14px; white-space: pre-wrap; }.user { justify-self: end; background: #164f8f; }.assistant { justify-self: start; background: #182a42; }/* Form controls, buttons, and error states follow the same dark theme. */
Langkah 6: Jalankan dan Terap Aplikasi
npm run dev
Buka http://localhost:3000, pilih model, dan hantar mesej. Untuk penerapan Node.js produksi, tambah COMETAPI_API_KEY dan COMETAPI_BASE_URL ke tetapan persekitaran sisi pelayan hos, kemudian jalankan:
npm run buildnpm run start
Gunakan laluan hosting yang menyokong respons penstriman. Eksport statik tidak boleh menjalankan Pengendali Laluan /api/chat.
Uji Laluan Penstriman
Dengan pelayan pembangunan berjalan, panggil backend anda secara terus:
curl -N http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "gemini-3.7-flash", "messages": [ {"role": "user", "content": "Terangkan perutean model dalam dua ayat."} ] }'
Permintaan berjaya mengembalikan Server-Sent Events. ID dan teks tepat berbeza, tetapi strim mengikuti bentuk ini:
data: {"choices":[{"delta":{"content":"Model"}}]}data: {"choices":[{"delta":{"content":" routing"}}]}data: [DONE]
Pengecer pelayar membaca setiap acara SSE, mengekstrak choices[0].delta.content, dan menambah teks yang distrim ke mesej pembantu sebagai cebisan yang tiba.
Ralat Integrasi Biasa
| Simptom | Punca | Baiki |
|---|---|---|
| Ralat pengesahan 401 | Kunci sisi pelayan hilang atau tidak sah | Tetapkan COMETAPI_API_KEY; jangan dedahkan dengan NEXT_PUBLIC_. |
| 404 atau laluan salah | URL asas tiada /v1 | Gunakan https://api.cometapi.com/v1. |
| 400 model tidak disokong | ID tidak dalam senarai dibenarkan backend | Gunakan ID model teks langsung yang tepat dan kemas kini kedua-dua pemilih. |
| Jawapan muncul sekaligus | Hos atau proksi mempamerkan aliran acara | Nyahdayakan transformasi respons dan gunakan penerapan Node.js yang menyokong strim. |
Sediakan Backend Sembang untuk Produksi
- Autentik pengguna anda sendiri. Jangan dedahkan laluan awam yang membelanjakan kredit untuk trafik tanpa nama.
- Hadkan kadar mengikut pengguna dan IP. Hadkan strim serentak, permintaan per minit, kiraan mesej, dan panjang mesej.
- Kekalkan senarai dibenarkan model di sisi pelayan. Pemilih pelayar hanyalah kemudahan, bukan sempadan keselamatan.
- Sahkan katalog langsung semasa penerapan. Kuery
GEThttps://api.cometapi.com/api/modelsdan kegagalan pelepasan jika ID yang dikonfigurasi akan datang, tidak tersedia, atau tiada hujung chat-completions. - Jejak kos mengikut laluan. Log model terpilih, ID permintaan, kependaman, penggunaan token, dan ID pengguna. Tetapkan kuota kunci atau had perbelanjaan dalam papan pemuka CometAPI jika perlu.
- Tangani nyahsambung dan had masa. Kekalkan
request.signal, tetapkan had masa aplikasi, dan hentikan kerja apabila klien keluar. - Jangan sembunyi ralat konfigurasi dengan kejatuhan. Pamerkan respons 400 dan 401. Gunakan model lain hanya untuk set terhad kegagalan yang boleh dicuba semula dan hanya apabila skema permintaan serasi; lihat CometAPI Model Fallback Guide untuk rantaian kejatuhan dua lapis (CometAPI primer → model kejatuhan CometAPI → penyedia rasmi).
- Redaksi log. Kekalkan kunci API, prompt penuh, dan output model sensitif di luar log ralat produksi.
Satu Backend Next.js, Beberapa Pilihan Model
Suis model berada dalam polisi pelayan anda, bukan dalam akaun penyedia berasingan. Pengendali Laluan Next.js boleh menyimpan satu kunci CometAPI secara peribadi, menerima ID model yang diluluskan bagi setiap permintaan, dan menstrim model terpilih melalui satu hujung serasi OpenAI. Frontend kekal ringkas, sementara backend mengekalkan kawalan ke atas akses, pengesahan, kebolehcermatan, dan kos. Akaun dan kunci yang sama juga mencapai API imej dan video asli CometAPI — seperti Flux untuk penjanaan imej dan Kling untuk penjanaan video — jadi backend sembang ini boleh dikembangkan kepada aliran kerja multimodal tanpa integrasi kedua.
Gunakan direktori model awam CometAPI untuk penemuan model dan GET https://api.cometapi.com/api/models untuk pengesahan perutean automatik.
Soalan Lazim
Bolehkah saya menggunakan berbilang model AI dalam satu aplikasi Next.js?
Ya. Kekalkan satu laluan sisi pelayan dan hantarkan ID model yang dalam senarai dibenarkan dengan setiap permintaan. UI pelayar boleh menawarkan pilihan model, manakala pelayan mengawal ID yang diterima.
Bagaimana saya menukar antara GPT, Claude, dan Gemini?
Hantar ID model yang dipilih dalam badan permintaan. Pelayan mengesahkannya terhadap senarai dibenarkan dan meneruskan payload sembang yang sama ke model yang dipilih melalui hujung serasi OpenAI.
Di mana saya perlu menyimpan kunci API CometAPI saya?
Simpan dalam pembolehubah persekitaran sisi pelayan seperti COMETAPI_API_KEY. Jangan dedahkannya melalui pembolehubah NEXT_PUBLIC_ atau kod sisi klien.
Adakah API serasi OpenAI menjadikan semua model boleh saling ditukar ganti?
Tidak. Bentuk permintaan adalah mudah alih, tetapi model boleh berbeza dalam parameter yang disokong, had konteks, gelagat alat, gaya output, kependaman, dan harga. Uji setiap model dalam senarai dibenarkan dengan prompt produksi anda.
Bolehkah Pengendali Laluan Next.js menstrim respons AI?
Ya. Pengendali Laluan boleh memulangkan aliran Server-Sent Events huluan dengan jenis kandungan text/event-stream, asalkan platform penerapan dan mana-mana proksi di hadapannya tidak mempamerkan respons.
Bolehkah saya menerapkan aplikasi ini sebagai laman statik Next.js?
Tidak. Eksport statik tidak boleh menjalankan Pengendali Laluan sisi pelayan /api/chat atau melindungi kunci API. Gunakan Node.js atau runtime pelayan serasi lain yang menyokong respons penstriman.
