93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
// 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=<your_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": "<estimated band score, e.g. 6.5>",
|
||
"grammar": ["<issue 1 with correction, mix English example + Vietnamese explanation>"],
|
||
"vocabulary": ["<vocabulary observation in Vietnamese>"],
|
||
"structure": "<2–3 sentence structure assessment in Vietnamese>",
|
||
"improved_version": "<the full improved text in English>",
|
||
"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" } },
|
||
)
|
||
}
|
||
})
|