Skip to content

Add support for OpenAI-compatible API provider and optional base URL #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions electron/ConfigHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

interface Config {
apiKey: string;
apiProvider: "openai" | "gemini" | "anthropic"; // Added provider selection
apiProvider: "openai" | "gemini" | "anthropic" | "openai-compatible"; // Added provider selection
baseUrl?: string; // Optional custom API URL
extractionModel: string;
solutionModel: string;
debuggingModel: string;
Expand All @@ -20,6 +21,7 @@
private defaultConfig: Config = {
apiKey: "",
apiProvider: "gemini", // Default to Gemini
baseUrl: "", // Optional custom API URL
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this change
seems like it beats the point of the default model

extractionModel: "gemini-2.0-flash", // Default to Flash for faster responses
solutionModel: "gemini-2.0-flash",
debuggingModel: "gemini-2.0-flash",
Expand Down Expand Up @@ -58,7 +60,7 @@
/**
* Validate and sanitize model selection to ensure only allowed models are used
*/
private sanitizeModelSelection(model: string, provider: "openai" | "gemini" | "anthropic"): string {
private sanitizeModelSelection(model: string, provider: "openai" | "gemini" | "anthropic" | "openai-compatible"): string {
if (provider === "openai") {
// Only allow gpt-4o and gpt-4o-mini for OpenAI
const allowedModels = ['gpt-4o', 'gpt-4o-mini'];
Expand Down Expand Up @@ -95,7 +97,7 @@
const config = JSON.parse(configData);

// Ensure apiProvider is a valid value
if (config.apiProvider !== "openai" && config.apiProvider !== "gemini" && config.apiProvider !== "anthropic") {
if (config.apiProvider !== "openai" && config.apiProvider !== "gemini" && config.apiProvider !== "anthropic" && config.apiProvider !== "openai-compatible") {
config.apiProvider = "gemini"; // Default to Gemini if invalid
}

Expand Down Expand Up @@ -175,6 +177,10 @@
updates.solutionModel = "gpt-4o";
updates.debuggingModel = "gpt-4o";
} else if (updates.apiProvider === "anthropic") {
updates.extractionModel = "Claude#claude-3-7-sonnet-20250219";
updates.solutionModel = "Claude#claude-3-7-sonnet-20250219";
updates.debuggingModel = "Claude#claude-3-7-sonnet-20250219";
} else if (updates.apiProvider === "openai-compatible") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If updates.apiProvider === "openai-compatible"
why are the models for it claude-3-7-sonnet-20250219

updates.extractionModel = "claude-3-7-sonnet-20250219";
updates.solutionModel = "claude-3-7-sonnet-20250219";
updates.debuggingModel = "claude-3-7-sonnet-20250219";
Expand Down Expand Up @@ -311,6 +317,8 @@
return this.testGeminiKey(apiKey);
} else if (provider === "anthropic") {
return this.testAnthropicKey(apiKey);
} else if (provider === "openai-compatible") {
return this.testOpenAIKey(apiKey);
}

return { valid: false, error: "Unknown API provider" };
Expand All @@ -325,7 +333,7 @@
// Make a simple API call to test the key
await openai.models.list();
return { valid: true };
} catch (error: any) {

Check failure on line 336 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 336 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 336 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
console.error('OpenAI API key test failed:', error);

// Determine the specific error type for better error messages
Expand Down Expand Up @@ -358,7 +366,7 @@
return { valid: true };
}
return { valid: false, error: 'Invalid Gemini API key format.' };
} catch (error: any) {

Check failure on line 369 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 369 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 369 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
console.error('Gemini API key test failed:', error);
let errorMessage = 'Unknown error validating Gemini API key';

Expand All @@ -383,7 +391,7 @@
return { valid: true };
}
return { valid: false, error: 'Invalid Anthropic API key format.' };
} catch (error: any) {

Check failure on line 394 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 394 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 394 in electron/ConfigHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
console.error('Anthropic API key test failed:', error);
let errorMessage = 'Unknown error validating Anthropic API key';

Expand All @@ -396,5 +404,6 @@
}
}


// Export a singleton instance
export const configHelper = new ConfigHelper();
16 changes: 10 additions & 6 deletions electron/ProcessingHelper.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// ProcessingHelper.ts
import fs from "node:fs"
import path from "node:path"

Check failure on line 3 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

'path' is defined but never used

Check failure on line 3 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'path' is defined but never used

Check failure on line 3 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

'path' is defined but never used
import { ScreenshotHelper } from "./ScreenshotHelper"
import { IProcessingHelperDeps } from "./main"
import * as axios from "axios"
import { app, BrowserWindow, dialog } from "electron"

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

'dialog' is defined but never used

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

'app' is defined but never used

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'dialog' is defined but never used

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'app' is defined but never used

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

'dialog' is defined but never used

Check failure on line 7 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

'app' is defined but never used
import { OpenAI } from "openai"
import { configHelper } from "./ConfigHelper"
import Anthropic from '@anthropic-ai/sdk';
Expand All @@ -31,7 +31,7 @@
finishReason: string;
}>;
}
interface AnthropicMessage {

Check failure on line 34 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

'AnthropicMessage' is defined but never used

Check failure on line 34 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'AnthropicMessage' is defined but never used

Check failure on line 34 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

'AnthropicMessage' is defined but never used
role: 'user' | 'assistant';
content: Array<{
type: 'text' | 'image';
Expand All @@ -56,7 +56,7 @@

constructor(deps: IProcessingHelperDeps) {
this.deps = deps
this.screenshotHelper = deps.getScreenshotHelper()

Check failure on line 59 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Type 'ScreenshotHelper | null' is not assignable to type 'ScreenshotHelper'.

// Initialize AI client based on config
this.initializeAIClient();
Expand All @@ -74,12 +74,13 @@
try {
const config = configHelper.loadConfig();

if (config.apiProvider === "openai") {
if (config.apiProvider === "openai" || config.apiProvider === "openai-compatible") {
if (config.apiKey) {
this.openaiClient = new OpenAI({
apiKey: config.apiKey,
timeout: 60000, // 60 second timeout
maxRetries: 2 // Retry up to 2 times
maxRetries: 2, // Retry up to 2 times
baseURL: config.apiProvider === "openai-compatible" ? config.baseUrl : undefined,
});
this.geminiApiKey = null;
this.anthropicClient = null;
Expand Down Expand Up @@ -286,7 +287,7 @@
throw new Error("Failed to load screenshot data");
}

const result = await this.processScreenshotsHelper(validScreenshots, signal)

Check failure on line 290 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Argument of type '({ path: string; preview: string; data: string; } | null)[]' is not assignable to parameter of type '{ path: string; data: string; }[]'.

if (!result.success) {
console.log("Processing failed:", result.error)
Expand All @@ -313,7 +314,7 @@
result.data
)
this.deps.setView("solutions")
} catch (error: any) {

Check failure on line 317 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 317 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 317 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
mainWindow.webContents.send(
this.deps.PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR,
error
Expand Down Expand Up @@ -400,11 +401,11 @@

console.log(
"Combined screenshots for processing:",
validScreenshots.map((s) => s.path)

Check failure on line 404 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

's' is possibly 'null'.
)

const result = await this.processExtraScreenshotsHelper(
validScreenshots,

Check failure on line 408 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Argument of type '({ path: string; preview: string; data: string; } | null)[]' is not assignable to parameter of type '{ path: string; data: string; }[]'.
signal
)

Expand All @@ -420,7 +421,7 @@
result.error
)
}
} catch (error: any) {

Check failure on line 424 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 424 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 424 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
if (axios.isCancel(error)) {
mainWindow.webContents.send(
this.deps.PROCESSING_EVENTS.DEBUG_ERROR,
Expand Down Expand Up @@ -460,7 +461,7 @@

let problemInfo;

if (config.apiProvider === "openai") {
if (config.apiProvider === "openai" || config.apiProvider === "openai-compatible") {
// Verify OpenAI client
if (!this.openaiClient) {
this.initializeAIClient(); // Try to reinitialize
Expand Down Expand Up @@ -506,8 +507,11 @@
try {
const responseText = extractionResponse.choices[0].message.content;
// Handle when OpenAI might wrap the JSON in markdown code blocks
const jsonText = responseText.replace(/```json|```/g, '').trim();
problemInfo = JSON.parse(jsonText);
const jsonRegex = /```json\n([\s\S]*?)\n```/;
const match = responseText.match(jsonRegex);

Check failure on line 511 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'responseText' is possibly 'null'.
if (match && match[1]) {
problemInfo = JSON.parse(match[1]);
}
Comment on lines +510 to +514
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the need of the change here?
Also I think

if (match && match[1]) {
             problemInfo = JSON.parse(match[1]);
           }

there's a better way to do this matching

} catch (error) {
console.error("Error parsing OpenAI response:", error);
return {
Expand Down Expand Up @@ -613,7 +617,7 @@
const responseText = (response.content[0] as { type: 'text', text: string }).text;
const jsonText = responseText.replace(/```json|```/g, '').trim();
problemInfo = JSON.parse(jsonText);
} catch (error: any) {

Check failure on line 620 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on macos-latest

Unexpected any. Specify a different type

Check failure on line 620 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

Unexpected any. Specify a different type

Check failure on line 620 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on windows-latest

Unexpected any. Specify a different type
console.error("Error using Anthropic API:", error);

// Add specific handling for Claude's limitations
Expand Down Expand Up @@ -764,7 +768,7 @@

let responseContent;

if (config.apiProvider === "openai") {
if (config.apiProvider === "openai" || config.apiProvider === "openai-compatible") {
// OpenAI processing
if (!this.openaiClient) {
return {
Expand Down Expand Up @@ -889,12 +893,12 @@
}

// Extract parts from the response
const codeMatch = responseContent.match(/```(?:\w+)?\s*([\s\S]*?)```/);

Check failure on line 896 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'responseContent' is possibly 'null' or 'undefined'.
const code = codeMatch ? codeMatch[1].trim() : responseContent;

// Extract thoughts, looking for bullet points or numbered lists
const thoughtsRegex = /(?:Thoughts:|Key Insights:|Reasoning:|Approach:)([\s\S]*?)(?:Time complexity:|$)/i;
const thoughtsMatch = responseContent.match(thoughtsRegex);

Check failure on line 901 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'responseContent' is possibly 'null' or 'undefined'.
let thoughts: string[] = [];

if (thoughtsMatch && thoughtsMatch[1]) {
Expand All @@ -919,7 +923,7 @@
let timeComplexity = "O(n) - Linear time complexity because we only iterate through the array once. Each element is processed exactly one time, and the hashmap lookups are O(1) operations.";
let spaceComplexity = "O(n) - Linear space complexity because we store elements in the hashmap. In the worst case, we might need to store all elements before finding the solution pair.";

const timeMatch = responseContent.match(timeComplexityPattern);

Check failure on line 926 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'responseContent' is possibly 'null' or 'undefined'.
if (timeMatch && timeMatch[1]) {
timeComplexity = timeMatch[1].trim();
if (!timeComplexity.match(/O\([^)]+\)/i)) {
Expand All @@ -934,7 +938,7 @@
}
}

const spaceMatch = responseContent.match(spaceComplexityPattern);

Check failure on line 941 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'responseContent' is possibly 'null' or 'undefined'.
if (spaceMatch && spaceMatch[1]) {
spaceComplexity = spaceMatch[1].trim();
if (!spaceComplexity.match(/O\([^)]+\)/i)) {
Expand Down Expand Up @@ -1255,7 +1259,7 @@
}

let extractedCode = "// Debug mode - see analysis below";
const codeMatch = debugContent.match(/```(?:[a-zA-Z]+)?([\s\S]*?)```/);

Check failure on line 1262 in electron/ProcessingHelper.ts

View workflow job for this annotation

GitHub Actions / Build and Test on ubuntu-latest

'debugContent' is possibly 'null' or 'undefined'.
if (codeMatch && codeMatch[1]) {
extractedCode = codeMatch[1].trim();
}
Expand Down
136 changes: 109 additions & 27 deletions src/components/Settings/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Button } from "../ui/button";
import { Settings } from "lucide-react";
import { useToast } from "../../contexts/toast";

type APIProvider = "openai" | "gemini" | "anthropic";
type APIProvider = "openai" | "gemini" | "anthropic" | "openai-compatible";

type AIModel = {
id: string;
Expand Down Expand Up @@ -181,6 +181,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
const [open, setOpen] = useState(externalOpen || false);
const [apiKey, setApiKey] = useState("");
const [apiProvider, setApiProvider] = useState<APIProvider>("openai");
const [baseUrl, setBaseUrl] = useState("");
const [extractionModel, setExtractionModel] = useState("gpt-4o");
const [solutionModel, setSolutionModel] = useState("gpt-4o");
const [debuggingModel, setDebuggingModel] = useState("gpt-4o");
Expand Down Expand Up @@ -210,6 +211,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
interface Config {
apiKey?: string;
apiProvider?: APIProvider;
baseUrl?: string;
extractionModel?: string;
solutionModel?: string;
debuggingModel?: string;
Expand All @@ -220,6 +222,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
.then((config: Config) => {
setApiKey(config.apiKey || "");
setApiProvider(config.apiProvider || "openai");
setBaseUrl(config.baseUrl || "");
setExtractionModel(config.extractionModel || "gpt-4o");
setSolutionModel(config.solutionModel || "gpt-4o");
setDebuggingModel(config.debuggingModel || "gpt-4o");
Expand Down Expand Up @@ -251,6 +254,11 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
setExtractionModel("claude-3-7-sonnet-20250219");
setSolutionModel("claude-3-7-sonnet-20250219");
setDebuggingModel("claude-3-7-sonnet-20250219");
} else if (provider === "openai-compatible") {
// For OpenAI-compatible APIs, we'll keep the current models or set defaults
if (!extractionModel) setExtractionModel("gpt-3.5-turbo");
if (!solutionModel) setSolutionModel("gpt-3.5-turbo");
if (!debuggingModel) setDebuggingModel("gpt-3.5-turbo");
Comment on lines +257 to +261
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are creating a new model but in ProcessingHelper.ts here
you are using the same client? So why not bifurcate that too

}
};

Expand All @@ -260,6 +268,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
const result = await window.electronAPI.updateConfig({
apiKey,
apiProvider,
baseUrl: apiProvider === "openai-compatible" ? baseUrl : "",
extractionModel,
solutionModel,
debuggingModel,
Expand Down Expand Up @@ -325,9 +334,9 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
{/* API Provider Selection */}
<div className="space-y-2">
<label className="text-sm font-medium text-white">API Provider</label>
<div className="flex gap-2">
<div className="grid grid-cols-2 gap-2 mb-2">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

<div
className={`flex-1 p-2 rounded-lg cursor-pointer transition-colors ${
className={`p-2 rounded-lg cursor-pointer transition-colors ${
apiProvider === "openai"
? "bg-white/10 border border-white/20"
: "bg-black/30 border border-white/5 hover:bg-white/5"
Expand All @@ -347,7 +356,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</div>
</div>
<div
className={`flex-1 p-2 rounded-lg cursor-pointer transition-colors ${
className={`p-2 rounded-lg cursor-pointer transition-colors ${
apiProvider === "gemini"
? "bg-white/10 border border-white/20"
: "bg-black/30 border border-white/5 hover:bg-white/5"
Expand All @@ -367,7 +376,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</div>
</div>
<div
className={`flex-1 p-2 rounded-lg cursor-pointer transition-colors ${
className={`p-2 rounded-lg cursor-pointer transition-colors ${
apiProvider === "anthropic"
? "bg-white/10 border border-white/20"
: "bg-black/30 border border-white/5 hover:bg-white/5"
Expand All @@ -386,24 +395,46 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</div>
</div>
</div>
<div
className={`p-2 rounded-lg cursor-pointer transition-colors ${
apiProvider === "openai-compatible"
? "bg-white/10 border border-white/20"
: "bg-black/30 border border-white/5 hover:bg-white/5"
}`}
onClick={() => handleProviderChange("openai-compatible")}
>
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full ${
apiProvider === "openai-compatible" ? "bg-white" : "bg-white/20"
}`}
/>
<div className="flex flex-col">
<p className="font-medium text-white text-sm">OpenAI Compatible</p>
<p className="text-xs text-white/60">Open Router, etc.</p>
</div>
</div>
</div>
Comment on lines +398 to +417
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again here, I would rather have you support a new model with everything made specifically for that model rather than creating new at some places and making changes in others

</div>
</div>

<div className="space-y-2">
<label className="text-sm font-medium text-white" htmlFor="apiKey">
{apiProvider === "openai" ? "OpenAI API Key" :
apiProvider === "gemini" ? "Gemini API Key" :
"Anthropic API Key"}
{apiProvider === "openai" ? "OpenAI API Key" :
apiProvider === "gemini" ? "Gemini API Key" :
apiProvider === "anthropic" ? "Anthropic API Key" :
"API Key"}
</label>
<Input
id="apiKey"
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={
apiProvider === "openai" ? "sk-..." :
apiProvider === "openai" ? "sk-..." :
apiProvider === "gemini" ? "Enter your Gemini API key" :
"sk-ant-..."
apiProvider === "anthropic" ? "sk-ant-..." :
"Enter your API key"
}
className="bg-black/50 border-white/10 text-white"
/>
Expand All @@ -413,11 +444,36 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</p>
)}
<p className="text-xs text-white/50">
Your API key is stored locally and never sent to any server except {apiProvider === "openai" ? "OpenAI" : "Google"}
Your API key is stored locally and never sent to any server except {
apiProvider === "openai" ? "OpenAI" :
apiProvider === "gemini" ? "Google" :
apiProvider === "anthropic" ? "Anthropic" :
"the API provider you specified"
}
</p>

{apiProvider === "openai-compatible" && (
<div className="space-y-2">
<label className="text-sm font-medium text-white" htmlFor="customApiUrl">
API URL
</label>
<Input
id="baseUrl"
type="text"
value={baseUrl}
onChange={(e) => setBaseUrl(e.target.value)}
placeholder="https://api.openrouter.ai/api/v1"
className="bg-black/50 border-white/10 text-white"
/>
<p className="text-xs text-white/50">
Enter the base URL for the OpenAI-compatible API (e.g., Open Router)
</p>
</div>
)}

<div className="mt-2 p-2 rounded-md bg-white/5 border border-white/10">
<p className="text-xs text-white/80 mb-1">Don't have an API key?</p>
{apiProvider === "openai" ? (
{apiProvider === "openai" && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

<>
<p className="text-xs text-white/60 mb-1">1. Create an account at <button
onClick={() => openExternalLink('https://platform.openai.com/signup')}
Expand All @@ -429,7 +485,8 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</p>
<p className="text-xs text-white/60">3. Create a new secret key and paste it here</p>
</>
) : apiProvider === "gemini" ? (
)}
{apiProvider === "gemini" && (
<>
<p className="text-xs text-white/60 mb-1">1. Create an account at <button
onClick={() => openExternalLink('https://aistudio.google.com/')}
Expand All @@ -441,7 +498,8 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
</p>
<p className="text-xs text-white/60">3. Create a new API key and paste it here</p>
</>
) : (
)}
{apiProvider === "anthropic" && (
<>
<p className="text-xs text-white/60 mb-1">1. Create an account at <button
onClick={() => openExternalLink('https://console.anthropic.com/signup')}
Expand All @@ -454,6 +512,19 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
<p className="text-xs text-white/60">3. Create a new API key and paste it here</p>
</>
)}
{apiProvider === "openai-compatible" && (
<>
<p className="text-xs text-white/60 mb-1">1. Create an account at <button
onClick={() => openExternalLink('https://openrouter.ai/')}
className="text-blue-400 hover:underline cursor-pointer">Open Router</button>
</p>
<p className="text-xs text-white/60 mb-1">2. Go to the <button
onClick={() => openExternalLink('https://openrouter.ai/dashboard/api-keys')}
className="text-blue-400 hover:underline cursor-pointer">API Keys</button> section
</p>
<p className="text-xs text-white/60">3. Create a new API key and paste it here</p>
</>
)}
</div>
</div>

Expand Down Expand Up @@ -512,6 +583,18 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
apiProvider === "openai" ? category.openaiModels :
apiProvider === "gemini" ? category.geminiModels :
category.anthropicModels;

// Determine which state to use based on category key
const currentValue =
category.key === 'extractionModel' ? extractionModel :
category.key === 'solutionModel' ? solutionModel :
debuggingModel;

// Determine which setter function to use
const setValue =
category.key === 'extractionModel' ? setExtractionModel :
category.key === 'solutionModel' ? setSolutionModel :
setDebuggingModel;

return (
<div key={category.key} className="mb-4">
Expand All @@ -521,19 +604,18 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
<p className="text-xs text-white/60 mb-2">{category.description}</p>

<div className="space-y-2">
{models.map((m) => {
// Determine which state to use based on category key
const currentValue =
category.key === 'extractionModel' ? extractionModel :
category.key === 'solutionModel' ? solutionModel :
debuggingModel;

// Determine which setter function to use
const setValue =
category.key === 'extractionModel' ? setExtractionModel :
category.key === 'solutionModel' ? setSolutionModel :
setDebuggingModel;

{apiProvider === "openai-compatible" && (
<Input
id="extractionModelCustom"
type="text"
value={currentValue}
onChange={(e) => setValue(e.target.value)}
placeholder="gpt-3.5-turbo"
className="bg-black/50 border-white/10 text-white"
/>
)}

{apiProvider !== "openai-compatible" && models.map((m) => {
Comment on lines +607 to +618
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why separate handling?

return (
<div
key={m.id}
Expand Down
Loading