| import { |
| streamWithOllaBridge, |
| chatWithOllaBridge, |
| isOllaBridgeConfigured, |
| } from './ollabridge'; |
| import { |
| streamWithHuggingFace, |
| chatWithHuggingFace, |
| } from './huggingface-direct'; |
| import { getCachedFAQResponse } from './cached-faq'; |
|
|
| export interface ChatMessage { |
| role: 'system' | 'user' | 'assistant'; |
| content: string; |
| } |
|
|
| export interface ProviderResponse { |
| content: string; |
| provider: string; |
| model: string; |
| } |
|
|
| |
| |
| |
| |
| function log(stage: string, details?: Record<string, unknown>) { |
| const payload = details ? ` ${JSON.stringify(details)}` : ''; |
| console.log(`[Chat] ${stage}${payload}`); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function streamWithFallback( |
| messages: ChatMessage[], |
| model: string = 'qwen2.5:1.5b' |
| ): Promise<ReadableStream> { |
| const requestId = Math.random().toString(36).slice(2, 10); |
| const startedAt = Date.now(); |
| const userTurn = messages.filter((m) => m.role === 'user').pop(); |
| log('request.start', { |
| requestId, |
| model, |
| turns: messages.length, |
| userChars: userTurn?.content.length ?? 0, |
| }); |
|
|
| |
| |
| |
| if (isOllaBridgeConfigured()) { |
| const t0 = Date.now(); |
| try { |
| const stream = await streamWithOllaBridge(messages, model); |
| log('provider.ollabridge.ok', { |
| requestId, |
| latencyMs: Date.now() - t0, |
| totalMs: Date.now() - startedAt, |
| }); |
| return stream; |
| } catch (error: any) { |
| log('provider.ollabridge.fail', { |
| requestId, |
| latencyMs: Date.now() - t0, |
| error: String(error?.message || error).slice(0, 200), |
| }); |
| } |
| } else { |
| log('provider.ollabridge.skipped', { requestId, reason: 'not configured' }); |
| } |
|
|
| |
| const t1 = Date.now(); |
| try { |
| const stream = await streamWithHuggingFace(messages); |
| log('provider.huggingface.ok', { |
| requestId, |
| latencyMs: Date.now() - t1, |
| totalMs: Date.now() - startedAt, |
| }); |
| return stream; |
| } catch (error: any) { |
| log('provider.huggingface.fail', { |
| requestId, |
| latencyMs: Date.now() - t1, |
| error: String(error?.message || error).slice(0, 200), |
| }); |
| } |
|
|
| |
| const faqResponse = getCachedFAQResponse(userTurn?.content || ''); |
| log('provider.cached-faq.used', { |
| requestId, |
| totalMs: Date.now() - startedAt, |
| faqChars: faqResponse.length, |
| }); |
|
|
| return new ReadableStream({ |
| start(controller) { |
| const encoder = new TextEncoder(); |
| controller.enqueue( |
| encoder.encode( |
| `data: ${JSON.stringify({ |
| choices: [{ delta: { content: faqResponse } }], |
| provider: 'cached-faq', |
| model: 'offline', |
| })}\n\n`, |
| ), |
| ); |
| controller.enqueue(encoder.encode('data: [DONE]\n\n')); |
| controller.close(); |
| }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| export async function chatWithFallback( |
| messages: ChatMessage[], |
| model: string = 'qwen2.5:1.5b' |
| ): Promise<ProviderResponse> { |
| const requestId = Math.random().toString(36).slice(2, 10); |
| log('request.start.nonstream', { requestId, model }); |
|
|
| if (isOllaBridgeConfigured()) { |
| try { |
| const resp = await chatWithOllaBridge(messages, model); |
| log('provider.ollabridge.ok.nonstream', { requestId }); |
| return resp; |
| } catch (error: any) { |
| log('provider.ollabridge.fail.nonstream', { |
| requestId, |
| error: String(error?.message || error).slice(0, 200), |
| }); |
| } |
| } else { |
| log('provider.ollabridge.skipped.nonstream', { requestId }); |
| } |
|
|
| try { |
| const resp = await chatWithHuggingFace(messages); |
| log('provider.huggingface.ok.nonstream', { requestId }); |
| return resp; |
| } catch (error: any) { |
| log('provider.huggingface.fail.nonstream', { |
| requestId, |
| error: String(error?.message || error).slice(0, 200), |
| }); |
| } |
|
|
| const lastUserMessage = messages.filter((m) => m.role === 'user').pop(); |
| log('provider.cached-faq.used.nonstream', { requestId }); |
| return { |
| content: getCachedFAQResponse(lastUserMessage?.content || ''), |
| provider: 'cached-faq', |
| model: 'offline', |
| }; |
| } |
|
|