import { IStorage } from "./storage"; import { User } from "../shared/schema"; interface ChatMessage { role: "system" | "user" | "assistant"; content: string; } export class AiService { constructor(private storage: IStorage) { } async chat(messages: ChatMessage[], user: User, context: string): Promise { const provider = await this.storage.getSystemSettings("ai_provider") || "openai"; const apiKey = await this.storage.getSystemSettings("ai_api_key"); const model = await this.storage.getSystemSettings("ai_model") || "gpt-4o"; const baseUrl = await this.storage.getSystemSettings("ai_base_url"); if (!apiKey && provider !== "ollama") { throw new Error("AI API Key not configured"); } const systemPrompt = `You are TaskFlow AI, an intelligent assistant for the TaskFlow application. You have access to the user's current tasks and context. User Name: ${user.username} Current Context: ${context} Answer the user's questions based on this context. Be concise, helpful, and friendly. If needed, suggest they create tasks or manage their schedule (you cannot perform actions yet, only advise). `; const fullMessages = [ { role: "system", content: systemPrompt }, ...messages ]; try { if (provider === "openai" || provider === "ollama") { return await this.chatOpenAI(provider, apiKey || "", model, baseUrl, fullMessages); } else if (provider === "anthropic") { return await this.chatAnthropic(apiKey || "", model, fullMessages); } else if (provider === "google") { return await this.chatGemini(apiKey || "", model, fullMessages); } else { throw new Error(`Unsupported AI provider: ${provider}`); } } catch (error: any) { console.error("AI Chat Error:", error); throw new Error(`AI Service Error: ${error.message}`); } } private async chatOpenAI(provider: string, apiKey: string, model: string, baseUrl: string | undefined, messages: any[]): Promise { const url = baseUrl || (provider === "ollama" ? "http://localhost:11434/v1" : "https://api.openai.com/v1") + "/chat/completions"; // Clean URL const cleanUrl = url.replace(/([^:]\/)\/+/g, "$1"); // remove double slashes const response = await fetch(cleanUrl, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}`, }, body: JSON.stringify({ model: model, messages: messages, temperature: 0.7, }), }); if (!response.ok) { const err = await response.text(); throw new Error(`OpenAI/Ollama API Error ${response.status}: ${err}`); } const data = await response.json(); return data.choices[0]?.message?.content || "No response generated."; } private async chatAnthropic(apiKey: string, model: string, messages: any[]): Promise { // Anthropic doesn't support "system" role in messages list in the same way, need to extract it const systemMessage = messages.find(m => m.role === "system")?.content || ""; const userAssistantMessages = messages.filter(m => m.role !== "system"); const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01", }, body: JSON.stringify({ model: model, system: systemMessage, messages: userAssistantMessages, max_tokens: 1024, }), }); if (!response.ok) { const err = await response.text(); throw new Error(`Anthropic API Error ${response.status}: ${err}`); } const data = await response.json(); return data.content[0]?.text || "No response generated."; } private async chatGemini(apiKey: string, model: string, messages: any[]): Promise { // Google Generative AI (Gemini) // POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY // Mapping messages to Gemini format (contents: [{ role, parts: [{ text }] }]) // System instruction is supported in v1beta/models/...:generateContent? // Gemini 1.5 Pro supports systemInstructions. // For simplicity, I'll prepend system prompt to first user message. const systemMessage = messages.find(m => m.role === "system")?.content || ""; const contentMessages = messages.filter(m => m.role !== "system").map(m => ({ role: m.role === "user" ? "user" : "model", parts: [{ text: m.content }] })); if (contentMessages.length > 0 && contentMessages[0].role === "user") { contentMessages[0].parts[0].text = `[System Instruction: ${systemMessage}]\n\nWait for user input... User Input: ` + contentMessages[0].parts[0].text; } const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: contentMessages }), }); if (!response.ok) { const err = await response.text(); throw new Error(`Gemini API Error ${response.status}: ${err}`); } const data = await response.json(); return data.candidates?.[0]?.content?.parts?.[0]?.text || "No response generated."; } }