feat: PC7 chatbot V1 onglet centre HAUT + endpoint Astro proxy SSR
- ChatbotV2.vue : Vue island, thread chat (input + messages bot/user), persistance sessionStorage, bandeau beta '120 fiches AEP, RAG-PE bientot', gestion erreurs 429/502/504 ; pas de streaming ni markdown V1 - /api/chatbot.ts : endpoint Astro server proxy POST vers CHATBOT_UPSTREAM (default https://aep.trans-former.fr/api/chatbot), timeout 25s, body { question, history } -> upstream classique chatbot AEP Mistral Small - astro.config.mjs : output 'server' + adapter @astrojs/node standalone (Astro 6 a supprime mode hybrid ; on opt-in prerender sur les pages) - Toutes les pages publiques (index, manifeste, manifeste/commander, a-propos, mentions-legales) ont 'export const prerender = true' - ColCentre.astro : remplace ChatbotPlaceholder par ChatbotV2 dans le tab - .env.example : ajoute CHATBOT_UPSTREAM (V1.5 = switch LightRAG-PE 1 ligne) Decision V1 : endpoint AEP /api/chatbot (classique, repond bien) au lieu de /api/chatbot-v2 qui retourne v2_ready=false ('base vectorielle en cours'). Bandeau beta reste valide ; switch v2 quand ready cote AEP via env var. Note PC8 deploy : Coolify doit booter avec 'node ./dist/server/entry.mjs' (SSR Node standalone) au lieu de servir dist/client/ static. Test end-to-end OK : SSR boot port 4399 + curl POST /api/chatbot -> reponse_texte 800+ chars de l'AEP backend.
This commit is contained in:
65
src/pages/api/chatbot.ts
Normal file
65
src/pages/api/chatbot.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// PC7 — Endpoint proxy POST /api/chatbot
|
||||
// Forward la question vers CHATBOT_UPSTREAM (default : aep.trans-former.fr/api/chatbot).
|
||||
// Tunnel CORS (l'upstream ne sert pas de header CORS pour trans-former.fr) + timeout 25s.
|
||||
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
export const prerender = false
|
||||
|
||||
const UPSTREAM =
|
||||
import.meta.env.CHATBOT_UPSTREAM || 'https://aep.trans-former.fr/api/chatbot'
|
||||
const TIMEOUT_MS = 25_000
|
||||
|
||||
const jsonResponse = (status: number, payload: unknown) =>
|
||||
new Response(JSON.stringify(payload), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
||||
})
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
let body: { question?: string; q?: string; history?: unknown }
|
||||
|
||||
try {
|
||||
body = await request.json()
|
||||
} catch {
|
||||
return jsonResponse(400, { error: 'invalid_json' })
|
||||
}
|
||||
|
||||
const question = (body.question || body.q || '').toString().trim()
|
||||
if (!question) {
|
||||
return jsonResponse(400, { error: 'missing_question' })
|
||||
}
|
||||
|
||||
const ctrl = new AbortController()
|
||||
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS)
|
||||
|
||||
try {
|
||||
const upstream = await fetch(UPSTREAM, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
question,
|
||||
history: Array.isArray(body.history) ? body.history : [],
|
||||
}),
|
||||
signal: ctrl.signal,
|
||||
})
|
||||
clearTimeout(timer)
|
||||
|
||||
const text = await upstream.text()
|
||||
const contentType =
|
||||
upstream.headers.get('content-type') || 'application/json; charset=utf-8'
|
||||
|
||||
return new Response(text, {
|
||||
status: upstream.status,
|
||||
headers: { 'Content-Type': contentType },
|
||||
})
|
||||
} catch (e) {
|
||||
clearTimeout(timer)
|
||||
const err = e as Error
|
||||
const aborted = err.name === 'AbortError'
|
||||
return jsonResponse(aborted ? 504 : 502, {
|
||||
error: aborted ? 'upstream_timeout' : 'upstream_failed',
|
||||
detail: err.message || 'unknown',
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user