1010using Documentation . Builder . Diagnostics . LiveMode ;
1111using Elastic . Documentation . Api . Core ;
1212using Elastic . Documentation . Api . Core . AskAi ;
13+ using Elastic . Documentation . Api . Infrastructure ;
1314using Elastic . Documentation . Api . Infrastructure . Adapters . AskAi ;
1415using Elastic . Documentation . Api . Infrastructure . Gcp ;
1516using Elastic . Documentation . Configuration ;
@@ -33,16 +34,14 @@ public class DocumentationWebHost
3334
3435 private readonly IHostedService _hostedService ;
3536 private readonly IFileSystem _writeFileSystem ;
36- private readonly ILoggerFactory _logFactory ;
3737
3838 public DocumentationWebHost ( ILoggerFactory logFactory , string ? path , int port , IFileSystem readFs , IFileSystem writeFs ,
3939 VersionsConfiguration versionsConfig )
4040 {
41- _logFactory = logFactory ;
4241 _writeFileSystem = writeFs ;
4342 var builder = WebApplication . CreateSlimBuilder ( ) ;
43+ builder . Services . AddApiUsecases ( "dev" ) ;
4444 DocumentationTooling . CreateServiceCollection ( builder . Services , LogLevel . Information ) ;
45-
4645 _ = builder . Logging
4746 . AddFilter ( "Microsoft.AspNetCore.Hosting.Diagnostics" , LogLevel . Error )
4847 . AddFilter ( "Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware" , LogLevel . Error )
@@ -101,6 +100,23 @@ private void SetUpRoutes()
101100 _ = _webApplication
102101 . UseLiveReloadWithManualScriptInjection ( _webApplication . Lifetime )
103102 . UseDeveloperExceptionPage ( new DeveloperExceptionPageOptions ( ) )
103+ . Use ( async ( context , next ) =>
104+ {
105+ try
106+ {
107+ await next ( context ) ;
108+ }
109+ catch ( Exception ex )
110+ {
111+ Console . WriteLine ( $ "[UNHANDLED EXCEPTION] { ex . GetType ( ) . Name } : { ex . Message } ") ;
112+ Console . WriteLine ( $ "[STACK TRACE] { ex . StackTrace } ") ;
113+ if ( ex . InnerException != null )
114+ {
115+ Console . WriteLine ( $ "[INNER EXCEPTION] { ex . InnerException . GetType ( ) . Name } : { ex . InnerException . Message } ") ;
116+ }
117+ throw ; // Re-throw to let ASP.NET Core handle it
118+ }
119+ } )
104120 . UseStaticFiles (
105121 new StaticFileOptions
106122 {
@@ -118,8 +134,7 @@ private void SetUpRoutes()
118134 _ = _webApplication . MapGet ( "/api/{**slug}" , ( string slug , ReloadableGeneratorState holder , Cancel ctx ) =>
119135 ServeApiFile ( holder , slug , ctx ) ) ;
120136
121- _ = _webApplication . MapPost ( "/chat" , async ( HttpContext context , Cancel ctx ) =>
122- await ProxyChatRequest ( context , ctx ) ) ;
137+ _ = _webApplication . MapPost ( "/_api/v1/ask-ai/stream" , ProxyChatRequest ) ;
123138
124139 _ = _webApplication . MapGet ( "{**slug}" , ( string slug , ReloadableGeneratorState holder , Cancel ctx ) =>
125140 ServeDocumentationFile ( holder , slug , ctx ) ) ;
@@ -227,52 +242,9 @@ private static IResult LiveReloadHtml(string content, Encoding? encoding = null,
227242 return Results . Content ( content , "text/html" , encoding , statusCode ) ;
228243 }
229244
230- private async Task < IResult > ProxyChatRequest ( HttpContext context , CancellationToken ctx )
245+ private static async Task < IResult > ProxyChatRequest ( AskAiRequest request , AskAiUsecase usecase , Cancel ctx )
231246 {
232- // Read the frontend request body
233- var requestBody = await new StreamReader ( context . Request . Body ) . ReadToEndAsync ( ctx ) ;
234- var askAiRequest = JsonSerializer . Deserialize < AskAiRequest > ( requestBody , ApiJsonContext . Default . AskAiRequest ) ;
235-
236- // Load GCP service account credentials
237- var serviceAccountKeyPath = Environment . GetEnvironmentVariable ( "LLM_GATEWAY_SERVICE_ACCOUNT_KEY_PATH" )
238- ?? "service-account-key.json" ;
239-
240- if ( ! File . Exists ( serviceAccountKeyPath ) )
241- {
242- context . Response . StatusCode = 500 ;
243- await context . Response . WriteAsync ( "GCP credentials not configured" , cancellationToken : ctx ) ;
244- return Results . Empty ;
245- }
246-
247- // Get GCP function URL
248- var gcpFunctionUrl = Environment . GetEnvironmentVariable ( "LLM_GATEWAY_FUNCTION_URL" ) ;
249- if ( string . IsNullOrEmpty ( gcpFunctionUrl ) )
250- {
251- context . Response . StatusCode = 500 ;
252- await context . Response . WriteAsync ( "GCP function URL not configured" , cancellationToken : ctx ) ;
253- return Results . Empty ;
254- }
255-
256- var functionUri = new Uri ( gcpFunctionUrl ) ;
257- var audienceUrl = $ "{ functionUri . Scheme } ://{ functionUri . Host } ";
258- var httpClient = new HttpClient ( ) ;
259- var gcpIdTokenProvider = new GcpIdTokenProvider (
260- httpClient ,
261- await File . ReadAllTextAsync ( serviceAccountKeyPath , ctx ) ,
262- audienceUrl
263- ) ;
264- var llmGatewayAskAiGateway = new LlmGatewayAskAiGateway (
265- httpClient ,
266- gcpIdTokenProvider ,
267- gcpFunctionUrl
268- ) ;
269-
270- var askAiUsecase = new AskAiUsecase ( llmGatewayAskAiGateway , _logFactory . CreateLogger < AskAiUsecase > ( ) ) ;
271-
272- if ( askAiRequest == null )
273- return Results . BadRequest ( "Invalid chat request." ) ;
274-
275- var responseStream = await askAiUsecase . AskAi ( askAiRequest , ctx ) ;
276- return Results . Stream ( responseStream , "text/event-stream" ) ;
247+ var stream = await usecase . AskAi ( request , ctx ) ;
248+ return Results . Stream ( stream , "text/event-stream" ) ;
277249 }
278250}
0 commit comments