add dbiz, add history
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Supabase Edge Function: writing-check
|
||||
// Uses GLM API (OpenAI-compatible) to analyze English writing submissions.
|
||||
// Deploy: supabase functions deploy writing-check
|
||||
// Deploy: supabase functions deploy writing-check --no-verify-jwt
|
||||
// Secrets: supabase secrets set GLM_API_KEY=<your_key>
|
||||
|
||||
import OpenAI from "npm:openai@^4"
|
||||
@@ -13,10 +13,8 @@ const glm = new OpenAI({
|
||||
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:
|
||||
{
|
||||
@@ -29,7 +27,6 @@ Analyse the student's writing and respond ONLY with valid JSON — no markdown,
|
||||
}`
|
||||
|
||||
Deno.serve(async (req: Request) => {
|
||||
// Handle CORS pre-flight
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: CORS_HEADERS })
|
||||
}
|
||||
@@ -40,13 +37,11 @@ Deno.serve(async (req: Request) => {
|
||||
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 },
|
||||
{ status: 400, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } },
|
||||
)
|
||||
}
|
||||
|
||||
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>
|
||||
const stream = await glm.chat.completions.create({
|
||||
model: Deno.env.get("GLM_MODEL") ?? "GLM-4.5-Flash",
|
||||
messages: [
|
||||
{ role: "system", content: SYSTEM_PROMPT },
|
||||
@@ -54,20 +49,42 @@ Deno.serve(async (req: Request) => {
|
||||
],
|
||||
temperature: 0.3,
|
||||
max_tokens: 1500,
|
||||
stream: true,
|
||||
})
|
||||
|
||||
const raw = completion.choices[0]?.message?.content ?? "{}"
|
||||
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 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()
|
||||
},
|
||||
})
|
||||
|
||||
// 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 })
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
...CORS_HEADERS,
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
})
|
||||
} 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 },
|
||||
{ status: 500, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } },
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user