feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -0,0 +1,67 @@
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { AIProvider, type AIProviderConfig } from '../types';
const DEFAULT_MODELS: Record<AIProvider, string> = {
[AIProvider.OPENAI]: 'gpt-4o',
[AIProvider.ANTHROPIC]: 'claude-sonnet-4-20250514',
[AIProvider.GOOGLE]: 'gemini-2.5-flash'
};
const PROVIDER_API_KEY_ENV: Record<AIProvider, string> = {
[AIProvider.OPENAI]: 'OPENAI_API_KEY',
[AIProvider.ANTHROPIC]: 'ANTHROPIC_API_KEY',
[AIProvider.GOOGLE]: 'GOOGLE_API_KEY'
};
/**
* Creates an AI SDK LanguageModel from provider configuration.
* Normalizes OpenAI, Anthropic, and Google into a single interface for streamText().
*/
export function createModel(config: AIProviderConfig) {
const modelName = config.model || DEFAULT_MODELS[config.provider];
switch (config.provider) {
case AIProvider.OPENAI: {
const openai = createOpenAI({ apiKey: config.apiKey });
return openai(modelName);
}
case AIProvider.ANTHROPIC: {
const anthropic = createAnthropic({ apiKey: config.apiKey });
return anthropic(modelName);
}
case AIProvider.GOOGLE: {
const google = createGoogleGenerativeAI({ apiKey: config.apiKey });
return google(modelName);
}
default:
throw new Error(`Unsupported AI provider: ${config.provider}`);
}
}
/**
* Reads the API key for a specific provider from its dedicated env var.
* Returns null when the key is unset, so callers can decide whether to 503.
*/
export function getProviderConfigForProvider(provider: AIProvider): AIProviderConfig | null {
const apiKey = process.env[PROVIDER_API_KEY_ENV[provider]];
if (!apiKey) return null;
return { provider, apiKey };
}
/**
* Returns the first provider that has a key configured, in preference order.
* Used by routes that don't take an explicit model (status check, title generation).
*/
export function pickAnyConfiguredProvider(): AIProviderConfig | null {
const order: AIProvider[] = [AIProvider.GOOGLE, AIProvider.OPENAI, AIProvider.ANTHROPIC];
for (const provider of order) {
const config = getProviderConfigForProvider(provider);
if (config) return config;
}
return null;
}