From a61bf86851c3129a6e118b8088cced40ee5bd44b Mon Sep 17 00:00:00 2001 From: maskeen Date: Sun, 5 Apr 2026 22:12:06 +0530 Subject: [PATCH] added multi-llm support --- prisma/schema.prisma | 2 + src/components/app/ChatWindow.tsx | 24 +++++++---- src/components/app/Problem.tsx | 4 ++ src/components/app/ProblemModal.tsx | 2 + src/components/app/ProblemsQueue.tsx | 8 +++- src/components/app/Settings.tsx | 64 ++++++++++++++++++++++++++-- src/pages/api/getUserSettings.ts | 2 + src/pages/api/openai.ts | 7 +-- 8 files changed, 97 insertions(+), 16 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 06fea5e..367c150 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,6 +18,8 @@ model User { subscriptionStart DateTime? subscriptionEnd DateTime? apiKey String? @default("null") + apiBaseUrl String? @default("https://api.openai.com/v1") + modelId String? @default("gpt-4o") collections Collection[] activityDays ActivityDay[] learnSteps String @default("10m 1d") diff --git a/src/components/app/ChatWindow.tsx b/src/components/app/ChatWindow.tsx index 9d8ac8b..e2c2448 100644 --- a/src/components/app/ChatWindow.tsx +++ b/src/components/app/ChatWindow.tsx @@ -4,10 +4,12 @@ import 'highlight.js/styles/atom-one-dark-reasonable.css'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; -const ChatWindow = ({ - problem, - editorContent, - apiKey, +const ChatWindow = ({ + problem, + editorContent, + apiKey, + baseUrl, + modelId, isTab = false, externalMessages, setExternalMessages, @@ -19,10 +21,12 @@ const ChatWindow = ({ setExternalIsTyping, externalShowQuickQuestions, setExternalShowQuickQuestions -}: { - problem: any, - editorContent: string, - apiKey: any, +}: { + problem: any, + editorContent: string, + apiKey: any, + baseUrl?: any, + modelId?: any, isTab?: boolean, externalMessages?: Array<{ text: string, sender: string }>, setExternalMessages?: React.Dispatch>>, @@ -90,6 +94,8 @@ const ChatWindow = ({ userSolution: editorContent, userMessage: "analyze", // Special flag to just analyze the code apiKey: apiKey, + baseUrl: baseUrl, + modelId: modelId, mode: "analyze" // Tell the API we're just loading context }), }); @@ -156,6 +162,8 @@ const ChatWindow = ({ userSolution: editorContent, userMessage: initialMessage || input, apiKey: apiKey, + baseUrl: baseUrl, + modelId: modelId, mode: "chat" // Specify we're in chat mode now }), }); diff --git a/src/components/app/Problem.tsx b/src/components/app/Problem.tsx index e925af4..6cf3196 100644 --- a/src/components/app/Problem.tsx +++ b/src/components/app/Problem.tsx @@ -285,6 +285,8 @@ const Problem = ({ problem, contentActive, setContentActive, editorContent, setE userSolution: editorContent, userMessage: "analyze", apiKey: data?.apiKey, + baseUrl: data?.apiBaseUrl, + modelId: data?.modelId, mode: "analyze" }), }); @@ -398,6 +400,8 @@ const Problem = ({ problem, contentActive, setContentActive, editorContent, setE problem={localProblem} editorContent={editorContent} apiKey={data?.apiKey} + baseUrl={data?.apiBaseUrl} + modelId={data?.modelId} isTab={true} externalMessages={chatMessages} setExternalMessages={setChatMessages} diff --git a/src/components/app/ProblemModal.tsx b/src/components/app/ProblemModal.tsx index 643e25e..8355e76 100644 --- a/src/components/app/ProblemModal.tsx +++ b/src/components/app/ProblemModal.tsx @@ -286,6 +286,8 @@ const ProblemModal = ({ userSolution: '', userMessage: `Generate a complete solution for this problem in ${language}. Strictly only provide the code without any explanations, comments, or markdown formatting.`, apiKey: theUser.apiKey, + baseUrl: theUser.apiBaseUrl, + modelId: theUser.modelId, mode: 'chat' }), }); diff --git a/src/components/app/ProblemsQueue.tsx b/src/components/app/ProblemsQueue.tsx index 9597c46..66d400a 100644 --- a/src/components/app/ProblemsQueue.tsx +++ b/src/components/app/ProblemsQueue.tsx @@ -248,6 +248,8 @@ const ProblemsQueue = ({ problems, userSettings, refetchProblems }: {problems:an userSolution: editorContent, userMessage: "analyze", apiKey: data?.apiKey, + baseUrl: data?.apiBaseUrl, + modelId: data?.modelId, mode: "analyze" }), }); @@ -1280,9 +1282,11 @@ const ProblemsQueue = ({ problems, userSettings, refetchProblems }: {problems:an /> ) : content === 'ai-assistant' ? ( { const [isApiKeyVisible, setIsApiKeyVisible] = useState(false); const [isSaving, setIsSaving] = useState(false); const [lastSaved, setLastSaved] = useState(null); + const [isBaseUrlVisible, setIsBaseUrlVisible] = useState(false); + const [isModelIdVisible, setIsModelIdVisible] = useState(false); const fetchUserSettings = async () => { if (!user) throw new Error('No user found'); @@ -123,6 +125,8 @@ const Settings = () => { ), // API Settings apiKey: (document.getElementById('apiKey') as HTMLInputElement)?.value, + apiBaseUrl: (document.getElementById('apiBaseUrl') as HTMLInputElement)?.value, + modelId: (document.getElementById('modelId') as HTMLInputElement)?.value, }; // Validations (same as before) @@ -357,11 +361,11 @@ const Settings = () => {

API Settings

- +
@@ -387,7 +391,61 @@ const Settings = () => {

- Enter your OpenAI API key to enable AI-based features. + Enter your API key to enable AI-based features. +

+
+ +
+ +
+ + +
+
+

+ Custom API endpoint for OpenAI-compatible providers (e.g., Ollama, Together AI, Groq). Leave empty for default OpenAI. +

+
+ +
+ +
+ + +
+
+

+ Model identifier to use (e.g., gpt-4o, llama3.2, mixtral-8x7b). Leave empty for default gpt-4o.

diff --git a/src/pages/api/getUserSettings.ts b/src/pages/api/getUserSettings.ts index 10e3778..9d040dd 100644 --- a/src/pages/api/getUserSettings.ts +++ b/src/pages/api/getUserSettings.ts @@ -28,6 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) maximumInterval: true, maximumNewPerDay: true, apiKey: true, + apiBaseUrl: true, + modelId: true, contributionHistory: true, overdueWarningThreshold: true, currentStreak: true, diff --git a/src/pages/api/openai.ts b/src/pages/api/openai.ts index e5a641b..48bce82 100644 --- a/src/pages/api/openai.ts +++ b/src/pages/api/openai.ts @@ -2,10 +2,11 @@ import { NextApiRequest, NextApiResponse } from 'next'; import OpenAI from 'openai'; export default async (req: NextApiRequest, res: NextApiResponse) => { - const { question, solution, userSolution, userMessage, apiKey, mode = "chat" } = req.body; + const { question, solution, userSolution, userMessage, apiKey, baseUrl, modelId, mode = "chat" } = req.body; const openai = new OpenAI({ apiKey: apiKey, + baseURL: baseUrl || "https://api.openai.com/v1", }); let messages: any = []; @@ -45,9 +46,9 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { } const completion = await openai.chat.completions.create({ - model: "gpt-4o", + model: modelId || "gpt-4o", messages, - max_tokens: 1500, + max_tokens: 1500, }); if (completion.choices && completion.choices.length > 0) {