Files
english/supabase/functions/writing-check/index.ts
2026-04-12 18:54:59 +07:00

74 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Supabase Edge Function: writing-check
// Uses GLM API (OpenAI-compatible) to analyze English writing submissions.
// Deploy: supabase functions deploy writing-check
// Secrets: supabase secrets set GLM_API_KEY=<your_key>
import OpenAI from "npm:openai@^4"
const glm = new OpenAI({
apiKey: Deno.env.get("GLM_API_KEY") ?? "",
baseURL: "https://open.bigmodel.cn/api/paas/v4/",
})
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
"Content-Type": "application/json",
}
// Instructs the model to return a strict JSON structure with Vietnamese feedback.
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": "<estimated band score, e.g. 6.5>",
"grammar": ["<issue 1 with correction, mix English example + Vietnamese explanation>", ...],
"vocabulary": ["<vocabulary observation in Vietnamese>", ...],
"structure": "<23 sentence structure assessment in Vietnamese>",
"improved_version": "<the full improved text in English>",
"summary": "<23 sentence overall assessment in Vietnamese>"
}`
Deno.serve(async (req: Request) => {
// Handle CORS pre-flight
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 },
)
}
const completion = await glm.chat.completions.create({
// GLM-4-32B-0414-128K: cheapest paid model at $0.1/$0.1 per 1M tokens.
// Override via: supabase secrets set GLM_MODEL=<other-model>
model: Deno.env.get("GLM_MODEL") ?? "GLM-4-32B-0414-128K",
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: `Analyse this writing:\n\n${content.slice(0, 2000)}` },
],
temperature: 0.3,
max_tokens: 1500,
})
const raw = completion.choices[0]?.message?.content ?? "{}"
// Strip markdown code fences if the model adds them despite instructions
const cleaned = raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim()
const feedback = JSON.parse(cleaned)
return new Response(JSON.stringify(feedback), { headers: CORS_HEADERS })
} catch (err) {
console.error("writing-check 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 },
)
}
})