presenton/electron/servers/nextjs/utils/storeHelpers.ts
2026-04-16 19:00:24 +05:45

171 lines
5.6 KiB
TypeScript

import { setLLMConfig } from "@/store/slices/userConfig";
import { store } from "@/store/store";
import { LLMConfig } from "@/types/llm_config";
function isProvided(value: unknown): boolean {
return value !== "" && value !== null && value !== undefined;
}
/**
* Returns a user-facing validation message, or null when the config is valid.
*/
export const getLLMConfigValidationError = (
llmConfig: LLMConfig
): string | null => {
if (!llmConfig.LLM) {
return "Select a text provider.";
}
if (!llmConfig.DISABLE_IMAGE_GENERATION && !llmConfig.IMAGE_PROVIDER) {
return "Select an image provider, or turn off image generation.";
}
const llm = llmConfig.LLM;
if (llm === "openai") {
if (!isProvided(llmConfig.OPENAI_API_KEY)) {
return "OpenAI API key is required.";
}
if (!isProvided(llmConfig.OPENAI_MODEL)) {
return 'No OpenAI model selected. Use "Check models" after entering your API key, then choose a model.';
}
} else if (llm === "google") {
if (!isProvided(llmConfig.GOOGLE_API_KEY)) {
return "Google API key is required.";
}
if (!isProvided(llmConfig.GOOGLE_MODEL)) {
return 'No Google model selected. Use "Check models" after entering your API key, then choose a model.';
}
} else if (llm === "anthropic") {
if (!isProvided(llmConfig.ANTHROPIC_API_KEY)) {
return "Anthropic API key is required.";
}
if (!isProvided(llmConfig.ANTHROPIC_MODEL)) {
return 'No Anthropic model selected. Use "Check models" after entering your API key, then choose a model.';
}
} else if (llm === "ollama") {
if (!isProvided(llmConfig.OLLAMA_URL)) {
return "Ollama server URL is required.";
}
if (!isProvided(llmConfig.OLLAMA_MODEL)) {
return "Select an Ollama model. If none appear, confirm Ollama is running and reachable.";
}
} else if (llm === "custom") {
if (!isProvided(llmConfig.CUSTOM_LLM_URL)) {
return "Enter your custom LLM endpoint URL (OpenAI-compatible).";
}
if (!isProvided(llmConfig.CUSTOM_MODEL)) {
return 'No model selected for your custom endpoint. Use "Check models" after entering the URL, then choose a model.';
}
} else if (llm === "codex" || llm === "chatgpt") {
if (!isProvided(llmConfig.CODEX_MODEL)) {
return "Select a Codex model.";
}
} else {
return "Unsupported or unknown text provider.";
}
if (!llmConfig.DISABLE_IMAGE_GENERATION) {
switch (llmConfig.IMAGE_PROVIDER) {
case "pexels":
if (!isProvided(llmConfig.PEXELS_API_KEY)) {
return "Pexels API key is required.";
}
break;
case "pixabay":
if (!isProvided(llmConfig.PIXABAY_API_KEY)) {
return "Pixabay API key is required.";
}
break;
case "dall-e-3":
if (!isProvided(llmConfig.OPENAI_API_KEY)) {
return "OpenAI API key is required for DALL·E 3.";
}
break;
case "gpt-image-1.5":
if (!isProvided(llmConfig.OPENAI_API_KEY)) {
return "OpenAI API key is required for GPT Image 1.5.";
}
break;
case "gemini_flash":
if (!isProvided(llmConfig.GOOGLE_API_KEY)) {
return "Google API key is required for Gemini Flash image generation.";
}
break;
case "nanobanana_pro":
if (!isProvided(llmConfig.GOOGLE_API_KEY)) {
return "Google API key is required for NanoBanana Pro.";
}
break;
case "comfyui":
if (!isProvided(llmConfig.COMFYUI_URL)) {
return "ComfyUI server URL is required.";
}
break;
default:
return "Select a valid image provider.";
}
}
return null;
};
/** Codex is selected but no model chosen — block navigation away from Settings. */
export function isCodexMissingSelectedModel(llmConfig: LLMConfig): boolean {
return llmConfig.LLM === "codex" && !isProvided(llmConfig.CODEX_MODEL);
}
/**
* While on Settings with Codex selected and no model (e.g. after sign-out), block leaving
* for any destination other than Settings. Resolves once the user picks a model, signs in again, or switches provider.
*/
export function shouldBlockCodexOutboundNav(
llmConfig: LLMConfig,
destinationPath: string,
currentPathname: string | null
): boolean {
if (!isCodexMissingSelectedModel(llmConfig)) return false;
const onSettings =
currentPathname === "/settings" ||
(currentPathname?.startsWith("/settings/") ?? false);
if (!onSettings) return false;
const path = destinationPath.split("?")[0] || "";
if (path === "/settings" || path.startsWith("/settings/")) return false;
return true;
}
/** Keep Redux in sync when Codex signs out so nav guards see cleared CODEX_MODEL. */
export function syncStoreAfterCodexSignOut(): void {
const prev = store.getState().userConfig.llm_config;
store.dispatch(
setLLMConfig({
...prev,
LLM: "codex",
CODEX_MODEL: "",
})
);
}
export const handleSaveLLMConfig = async (llmConfig: LLMConfig) => {
const validationError = getLLMConfigValidationError(llmConfig);
if (validationError) {
throw new Error(validationError);
}
// Check if running in Electron environment
if (typeof window !== 'undefined' && window.electron?.setUserConfig) {
// Use Electron IPC handler
await window.electron.setUserConfig(llmConfig);
} else {
// Fallback to API route for web-based deployments
await fetch("/api/user-config", {
method: "POST",
body: JSON.stringify(llmConfig),
});
}
store.dispatch(setLLMConfig(llmConfig));
};
export const hasValidLLMConfig = (llmConfig: LLMConfig) =>
getLLMConfigValidationError(llmConfig) === null;