1+ import type { Request , Response } from "express" ;
12import type { Logger } from "pino" ;
2- import { askAi } from "../query/askAi" ;
3- import type { AppConfig } from "../config/types" ;
4- import type { LLMClientBundle } from "../llm/types" ;
5- import type { SupabaseVectorStore } from "../supabase/client" ;
6-
7- export interface StreamAskOptions {
8- question : string ;
9- matchCount ?: number ;
10- similarityThreshold ?: number ;
11- systemPrompt ?: string ;
12- }
3+ import { askAi } from "../../query/askAi" ;
4+ import type { AppConfig } from "../../config/types" ;
5+ import type { LLMClientBundle } from "../../llm/types" ;
6+ import type { SupabaseVectorStore } from "../../supabase/client" ;
137
14- export interface StreamAskContext {
8+ export interface AskRouteContext {
159 config : AppConfig ;
1610 llm : LLMClientBundle ;
1711 store : SupabaseVectorStore ;
1812}
1913
20- export function isStreamingRequest ( req : any ) : boolean {
14+ function isStreamingRequest ( req : Request ) : boolean {
2115 const acceptHeader = typeof req ?. headers ?. accept === "string" ? req . headers . accept . toLowerCase ( ) : "" ;
2216 if ( acceptHeader . includes ( "text/event-stream" ) ) {
2317 return true ;
@@ -39,12 +33,35 @@ export function isStreamingRequest(req: any): boolean {
3933 return normalize ( req ?. query ?. stream ) || normalize ( req ?. body ?. stream ) ;
4034}
4135
42- export async function streamAskResponse (
43- req : any ,
44- res : any ,
45- context : StreamAskContext ,
36+ function initializeEventStream ( res : Response ) : void {
37+ res . setHeader ( "Content-Type" , "text/event-stream" ) ;
38+ res . setHeader ( "Cache-Control" , "no-cache, no-transform" ) ;
39+ res . setHeader ( "Connection" , "keep-alive" ) ;
40+ if ( typeof res . flushHeaders === "function" ) {
41+ res . flushHeaders ( ) ;
42+ }
43+ }
44+
45+ function pushStreamEvent ( res : Response , event : string , payload : unknown ) : void {
46+ if ( res . writableEnded ) {
47+ return ;
48+ }
49+
50+ res . write ( `event: ${ event } \n` ) ;
51+ res . write ( `data: ${ JSON . stringify ( payload ) } \n\n` ) ;
52+ }
53+
54+ async function handleStreamingAsk (
55+ req : Request ,
56+ res : Response ,
57+ context : AskRouteContext ,
4658 logger : Logger ,
47- options : StreamAskOptions
59+ options : {
60+ question : string ;
61+ matchCount ?: number ;
62+ similarityThreshold ?: number ;
63+ systemPrompt ?: string ;
64+ }
4865) : Promise < void > {
4966 initializeEventStream ( res ) ;
5067 const abortController = new AbortController ( ) ;
@@ -104,20 +121,55 @@ export async function streamAskResponse(
104121 }
105122}
106123
107- function initializeEventStream ( res : any ) : void {
108- res . setHeader ( "Content-Type" , "text/event-stream" ) ;
109- res . setHeader ( "Cache-Control" , "no-cache, no-transform" ) ;
110- res . setHeader ( "Connection" , "keep-alive" ) ;
111- if ( typeof res . flushHeaders === "function" ) {
112- res . flushHeaders ( ) ;
124+ export async function handleAskRequest (
125+ req : Request ,
126+ res : Response ,
127+ context : AskRouteContext ,
128+ logger : Logger
129+ ) : Promise < void > {
130+ const { question, matchCount, similarityThreshold, systemPrompt } = req . body ?? { } ;
131+
132+ if ( typeof question !== "string" || question . trim ( ) . length === 0 ) {
133+ res . status ( 400 ) . json ( {
134+ status : "error" ,
135+ message : "Request body must include a non-empty 'question' field." ,
136+ } ) ;
137+ return ;
113138 }
114- }
115139
116- function pushStreamEvent ( res : any , event : string , payload : unknown ) : void {
117- if ( res . writableEnded ) {
140+ if ( isStreamingRequest ( req ) ) {
141+ await handleStreamingAsk ( req , res , context , logger , {
142+ question,
143+ matchCount,
144+ similarityThreshold,
145+ systemPrompt,
146+ } ) ;
118147 return ;
119148 }
120149
121- res . write ( `event: ${ event } \n` ) ;
122- res . write ( `data: ${ JSON . stringify ( payload ) } \n\n` ) ;
150+ try {
151+ const response = await askAi (
152+ context . llm ,
153+ context . store ,
154+ {
155+ question,
156+ matchCount,
157+ similarityThreshold,
158+ systemPrompt,
159+ } ,
160+ {
161+ logger,
162+ config : context . config ,
163+ }
164+ ) ;
165+
166+ res . json ( {
167+ status : "ok" ,
168+ answer : response . answer ,
169+ sources : response . sources ,
170+ } ) ;
171+ } catch ( error ) {
172+ logger . error ( { err : error } , "Ask endpoint failed." ) ;
173+ res . status ( 500 ) . json ( { status : "error" , message : ( error as Error ) . message } ) ;
174+ }
123175}
0 commit comments