// Supabase Edge Function: writing-check-dbiz // Uses DBIZ LLM API (qwen-35b, OpenAI-compatible) to analyze English writing. // Deploy: supabase functions deploy writing-check-dbiz --no-verify-jwt // Secrets: supabase secrets set DBIZ_API_KEY= import OpenAI from "npm:openai@^4" const dbiz = new OpenAI({ apiKey: Deno.env.get("DBIZ_API_KEY") ?? "", baseURL: "https://ai-api.dbiz.com/v1", }) const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", } // Structure enforced via system prompt — qwen-35b via LiteLLM drops response_format params. const SYSTEM_PROMPT = `You are an expert English writing teacher specialising in TOEIC and IELTS assessment. Analyse the student's writing and respond ONLY with valid JSON — no markdown, no extra text: { "score": "", "grammar": [""], "vocabulary": [""], "structure": "<2–3 sentence structure assessment in Vietnamese>", "improved_version": "", "summary": "<2–3 sentence overall assessment in Vietnamese>" } All string values must use double quotes. Do not use single quotes or unquoted keys.` Deno.serve(async (req: Request) => { if (req.method === "OPTIONS") { return new Response("ok", { headers: CORS_HEADERS }) } try { const { content } = await req.json() as { content: string } if (!content || content.trim().length < 10) { return new Response( JSON.stringify({ error: "Bài viết quá ngắn. Vui lòng nhập ít nhất 10 ký tự." }), { status: 400, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } }, ) } const stream = await dbiz.chat.completions.create({ model: "qwen-35b", messages: [ { role: "system", content: SYSTEM_PROMPT }, { role: "user", content: `Analyse this writing:\n\n${content.slice(0, 2000)}` }, ], temperature: 0.3, max_tokens: 10000, stream: true, }) const encoder = new TextEncoder() const body = new ReadableStream({ async start(controller) { try { for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content ?? "" if (text) { controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text })}\n\n`)) } } } catch (err) { console.error("writing-check-dbiz stream error:", err) controller.enqueue( encoder.encode(`data: ${JSON.stringify({ error: "Đã có lỗi khi chấm bài. Vui lòng thử lại." })}\n\n`), ) } controller.enqueue(encoder.encode("data: [DONE]\n\n")) controller.close() }, }) return new Response(body, { headers: { ...CORS_HEADERS, "Content-Type": "text/event-stream", "Cache-Control": "no-cache", }, }) } catch (err) { console.error("writing-check-dbiz error:", err) return new Response( JSON.stringify({ error: "Đã có lỗi khi chấm bài. Vui lòng thử lại." }), { status: 500, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } }, ) } })