Add KI/LLM integration with OpenAI-compatible API support
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 30s
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 30s
Features: - /ai setup - Setup wizard via private chat for security - /ai status - Show current config (API key masked) - /ai on/off - Enable/disable AI per chat - /ai random <1-10> - Set random trigger probability (admins) - /ai help - Detailed help - /ask-ai [text] - Generate AI response Security: - API keys only entered in private chat - API keys masked in status display - Admin-only configuration Architecture: - OpenAI-compatible client (OpenAI, Ollama, OpenRouter) - Global system prompt (.env) + group prompt (DB) - 50 message context per chat - SQLite tables: ai_settings, message_context, setup_sessions Defaults: - AI disabled by default - Trigger word: ask-ai - Random probability: 1% Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
106
src/ai.ts
Normal file
106
src/ai.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* OpenAI-compatible AI client for multiple providers.
|
||||
* Supports OpenAI, Ollama, OpenRouter, and any OpenAI-compatible API.
|
||||
*/
|
||||
|
||||
export interface AIConfig {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
systemPrompt: string;
|
||||
groupPrompt?: string;
|
||||
}
|
||||
|
||||
export interface AIMessage {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask an API key for safe display
|
||||
* sk-proj-abc123xyz... → sk-***...***xyz
|
||||
*/
|
||||
export function maskApiKey(key: string): string {
|
||||
if (!key || key.length < 8) return '***';
|
||||
const start = key.substring(0, 3);
|
||||
const end = key.substring(key.length - 3);
|
||||
return `${start}***...***${end}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate AI response using OpenAI-compatible API
|
||||
*/
|
||||
export async function generateAIResponse(
|
||||
config: AIConfig,
|
||||
context: AIMessage[],
|
||||
userMessage: string
|
||||
): Promise<string> {
|
||||
const messages: AIMessage[] = [];
|
||||
|
||||
// Build system prompt (global + group)
|
||||
let systemPrompt = config.systemPrompt;
|
||||
if (config.groupPrompt) {
|
||||
systemPrompt += '\n\n' + config.groupPrompt;
|
||||
}
|
||||
messages.push({ role: 'system', content: systemPrompt });
|
||||
|
||||
// Add context messages (last 50)
|
||||
messages.push(...context.slice(-50));
|
||||
|
||||
// Add user message
|
||||
messages.push({ role: 'user', content: userMessage });
|
||||
|
||||
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${config.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
messages,
|
||||
max_tokens: 500,
|
||||
temperature: 0.7,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`AI API error: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as { choices: Array<{ message?: { content?: string } }> };
|
||||
return data.choices[0]?.message?.content || 'Keine Antwort erhalten.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default base URL for a provider
|
||||
*/
|
||||
export function getDefaultBaseUrl(provider: string): string {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
return 'https://api.openai.com/v1';
|
||||
case 'ollama':
|
||||
return 'http://localhost:11434/v1';
|
||||
case 'openrouter':
|
||||
return 'https://openrouter.ai/api/v1';
|
||||
default:
|
||||
return 'https://api.openai.com/v1';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available models for a provider
|
||||
*/
|
||||
export function getProviderModels(provider: string): string[] {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
return ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'];
|
||||
case 'ollama':
|
||||
return ['llama3', 'llama3.1', 'mistral', 'codellama'];
|
||||
case 'openrouter':
|
||||
return ['openai/gpt-4o-mini', 'anthropic/claude-3.5-sonnet', 'meta-llama/llama-3.1-8b-instruct'];
|
||||
default:
|
||||
return ['gpt-4o-mini'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user