Merge pull request #549 from presenton/feature/vertex-openaiazure
Some checks failed
Test All Applications / Test Main FastAPI (push) Has been cancelled
Test All Applications / Test Electron FastAPI (push) Has been cancelled
Test All Applications / Test Main Next.js (push) Has been cancelled
Test All Applications / Test Electron Next.js (push) Has been cancelled

Feature/vertex openaiazure
This commit is contained in:
Sudip Parajuli 2026-04-30 11:54:19 +05:45 committed by GitHub
commit 342679ec60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 706 additions and 19 deletions

View file

@ -25,7 +25,7 @@ What makes Presenton different?
- Use Fully **self-hosted** in Web through [Docker Package](https://docs.presenton.ai/v3/get-started/quickstart)
- Or Download [Desktop App](https://presenton.ai/download) (Mac, Windows & Linux)
- Works with OpenAI, Gemini, Anthropic, Ollama, or custom models
- Works with OpenAI, Gemini, Vertex AI, Azure OpenAI, Anthropic, Ollama, or custom models
- Comes with AI Presentation Generation API
- Fully open-source (Apache 2.0)
- Works with your own design/templates
@ -97,7 +97,7 @@ Presenton gives you complete control over your AI presentation workflow. Choose
- Flexible Generation — Build presentations from prompts or uploaded documents
- Export Ready — Save as PowerPoint (PPTX) and PDF with professional formatting
- Built-In MCP Server — Generate presentations over Model Context Protocol
- Bring Your Own Key — Use your own API keys for OpenAI, Google Gemini, Anthropic Claude, or any compatible provider. Only pay for what you use, no hidden fees or subscriptions.
- Bring Your Own Key — Use your own API keys for OpenAI, Google Gemini, Vertex AI, Azure OpenAI, Anthropic Claude, or any compatible provider. Only pay for what you use, no hidden fees or subscriptions.
- Ollama Integration — Run open-source models locally with full privacy
- OpenAI API Compatible — Connect to any OpenAI-compatible endpoint with your own models
- Multi-Provider Support — Mix and match text and image generation providers
@ -204,11 +204,20 @@ Other optional variables exist in code (for example advanced Mem0 paths, LitePar
#### LLM and API keys
- **CAN_CHANGE_KEYS**=[true/false]: Set to **false** if you want to keep API keys hidden and make them unmodifiable.
- **LLM**=[openai/google/anthropic/ollama/custom/codex]: Select the text **LLM**.
- **LLM**=[openai/google/vertex/azure/anthropic/ollama/custom/codex]: Select the text **LLM**.
- **OPENAI_API_KEY**: Required if **LLM** is **openai**.
- **OPENAI_MODEL**: Required if **LLM** is **openai** (default: `gpt-4.1`).
- **GOOGLE_API_KEY**: Required if **LLM** is **google**.
- **GOOGLE_MODEL**: Required if **LLM** is **google** (default: `models/gemini-2.0-flash`).
- **VERTEX_MODEL**: Required if **LLM** is **vertex** (default: `gemini-2.5-flash`).
- **VERTEX_API_KEY**: Optional auth path for **LLM=vertex** (Vertex Express).
- **VERTEX_PROJECT** / **VERTEX_LOCATION**: Optional auth path for **LLM=vertex** when using GCP project credentials (do not combine with `VERTEX_API_KEY`).
- **VERTEX_BASE_URL**: Optional Vertex gateway/base URL override.
- **AZURE_OPENAI_MODEL**: Required if **LLM** is **azure** (deployment/model name).
- **AZURE_OPENAI_API_KEY**: Required if **LLM** is **azure**.
- **AZURE_OPENAI_API_VERSION**: Required if **LLM** is **azure** (for example `2024-10-21`).
- **AZURE_OPENAI_ENDPOINT** / **AZURE_OPENAI_BASE_URL**: At least one is required if **LLM** is **azure**.
- **AZURE_OPENAI_DEPLOYMENT**: Optional deployment override for **LLM** is **azure**.
- **ANTHROPIC_API_KEY**: Required if **LLM** is **anthropic**.
- **ANTHROPIC_MODEL**: Required if **LLM** is **anthropic** (default: `claude-3-5-sonnet-20241022`).
- **CODEX_MODEL**: Required if **LLM** is **codex** (Codex OAuth flow; compose maps host port **1455** for the callback).
@ -321,6 +330,12 @@ Same variables as compose; use `-e` instead of `.env` when running `docker run`
- Using Google
<pre><code class="language-bash">docker run -it --name presenton -p 5000:80 -e LLM="google" -e GOOGLE_API_KEY="******" -e IMAGE_PROVIDER="gemini_flash" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest</code></pre>
- Using Vertex AI (API key mode)
<pre><code class="language-bash">docker run -it --name presenton -p 5000:80 -e LLM="vertex" -e VERTEX_API_KEY="******" -e VERTEX_MODEL="gemini-2.5-flash" -e IMAGE_PROVIDER="gemini_flash" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest</code></pre>
- Using Azure OpenAI
<pre><code class="language-bash">docker run -it --name presenton -p 5000:80 -e LLM="azure" -e AZURE_OPENAI_API_KEY="******" -e AZURE_OPENAI_MODEL="gpt-4.1" -e AZURE_OPENAI_API_VERSION="2024-10-21" -e AZURE_OPENAI_ENDPOINT="https://YOUR-RESOURCE.openai.azure.com" -e IMAGE_PROVIDER="pexels" -e PEXELS_API_KEY="******" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest</code></pre>
- Using Ollama
<pre><code class="language-bash">docker run -it --name presenton -p 5000:80 -e LLM="ollama" -e OLLAMA_MODEL="llama3.2:3b" -e IMAGE_PROVIDER="pexels" -e PEXELS_API_KEY="*******" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest</code></pre>

View file

@ -19,6 +19,17 @@ services:
- OPENAI_MODEL=${OPENAI_MODEL}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- GOOGLE_MODEL=${GOOGLE_MODEL}
- VERTEX_API_KEY=${VERTEX_API_KEY}
- VERTEX_MODEL=${VERTEX_MODEL}
- VERTEX_PROJECT=${VERTEX_PROJECT}
- VERTEX_LOCATION=${VERTEX_LOCATION}
- VERTEX_BASE_URL=${VERTEX_BASE_URL}
- AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_MODEL=${AZURE_OPENAI_MODEL}
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_BASE_URL=${AZURE_OPENAI_BASE_URL}
- AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
- AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
- OLLAMA_URL=${OLLAMA_URL}
@ -82,6 +93,17 @@ services:
- OPENAI_MODEL=${OPENAI_MODEL}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- GOOGLE_MODEL=${GOOGLE_MODEL}
- VERTEX_API_KEY=${VERTEX_API_KEY}
- VERTEX_MODEL=${VERTEX_MODEL}
- VERTEX_PROJECT=${VERTEX_PROJECT}
- VERTEX_LOCATION=${VERTEX_LOCATION}
- VERTEX_BASE_URL=${VERTEX_BASE_URL}
- AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_MODEL=${AZURE_OPENAI_MODEL}
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_BASE_URL=${AZURE_OPENAI_BASE_URL}
- AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
- AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
- OLLAMA_URL=${OLLAMA_URL}
@ -141,6 +163,17 @@ services:
- OPENAI_MODEL=${OPENAI_MODEL}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- GOOGLE_MODEL=${GOOGLE_MODEL}
- VERTEX_API_KEY=${VERTEX_API_KEY}
- VERTEX_MODEL=${VERTEX_MODEL}
- VERTEX_PROJECT=${VERTEX_PROJECT}
- VERTEX_LOCATION=${VERTEX_LOCATION}
- VERTEX_BASE_URL=${VERTEX_BASE_URL}
- AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_MODEL=${AZURE_OPENAI_MODEL}
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_BASE_URL=${AZURE_OPENAI_BASE_URL}
- AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
- AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
- OLLAMA_URL=${OLLAMA_URL}
@ -205,6 +238,17 @@ services:
- OPENAI_MODEL=${OPENAI_MODEL}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- GOOGLE_MODEL=${GOOGLE_MODEL}
- VERTEX_API_KEY=${VERTEX_API_KEY}
- VERTEX_MODEL=${VERTEX_MODEL}
- VERTEX_PROJECT=${VERTEX_PROJECT}
- VERTEX_LOCATION=${VERTEX_LOCATION}
- VERTEX_BASE_URL=${VERTEX_BASE_URL}
- AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_MODEL=${AZURE_OPENAI_MODEL}
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_BASE_URL=${AZURE_OPENAI_BASE_URL}
- AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
- AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
- OLLAMA_URL=${OLLAMA_URL}

View file

@ -3,5 +3,7 @@ OPENAI_URL = "https://api.openai.com/v1"
# Default models
DEFAULT_OPENAI_MODEL = "gpt-4.1"
DEFAULT_GOOGLE_MODEL = "models/gemini-2.5-flash"
DEFAULT_VERTEX_MODEL = "gemini-2.5-flash"
DEFAULT_AZURE_MODEL = "gpt-4.1"
DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
DEFAULT_CODEX_MODEL = "gpt-5.2"

View file

@ -5,6 +5,8 @@ class LLMProvider(Enum):
OLLAMA = "ollama"
OPENAI = "openai"
GOOGLE = "google"
VERTEX = "vertex"
AZURE = "azure"
ANTHROPIC = "anthropic"
CUSTOM = "custom"
CODEX = "codex"

View file

@ -13,6 +13,21 @@ class UserConfig(BaseModel):
GOOGLE_API_KEY: Optional[str] = None
GOOGLE_MODEL: Optional[str] = None
# Vertex AI
VERTEX_API_KEY: Optional[str] = None
VERTEX_MODEL: Optional[str] = None
VERTEX_PROJECT: Optional[str] = None
VERTEX_LOCATION: Optional[str] = None
VERTEX_BASE_URL: Optional[str] = None
# Azure OpenAI
AZURE_OPENAI_API_KEY: Optional[str] = None
AZURE_OPENAI_MODEL: Optional[str] = None
AZURE_OPENAI_ENDPOINT: Optional[str] = None
AZURE_OPENAI_BASE_URL: Optional[str] = None
AZURE_OPENAI_API_VERSION: Optional[str] = None
AZURE_OPENAI_DEPLOYMENT: Optional[str] = None
# Anthropic
ANTHROPIC_API_KEY: Optional[str] = None
ANTHROPIC_MODEL: Optional[str] = None

View file

@ -57,6 +57,50 @@ def get_google_model_env():
return os.getenv("GOOGLE_MODEL")
def get_vertex_api_key_env():
return os.getenv("VERTEX_API_KEY")
def get_vertex_model_env():
return os.getenv("VERTEX_MODEL")
def get_vertex_project_env():
return os.getenv("VERTEX_PROJECT")
def get_vertex_location_env():
return os.getenv("VERTEX_LOCATION")
def get_vertex_base_url_env():
return os.getenv("VERTEX_BASE_URL")
def get_azure_openai_api_key_env():
return os.getenv("AZURE_OPENAI_API_KEY")
def get_azure_openai_model_env():
return os.getenv("AZURE_OPENAI_MODEL")
def get_azure_openai_endpoint_env():
return os.getenv("AZURE_OPENAI_ENDPOINT")
def get_azure_openai_base_url_env():
return os.getenv("AZURE_OPENAI_BASE_URL")
def get_azure_openai_api_version_env():
return os.getenv("AZURE_OPENAI_API_VERSION")
def get_azure_openai_deployment_env():
return os.getenv("AZURE_OPENAI_DEPLOYMENT")
def get_custom_llm_api_key_env():
return os.getenv("CUSTOM_LLM_API_KEY")

View file

@ -4,15 +4,22 @@ from typing import Optional
from fastapi import HTTPException
from llmai.shared import (
AnthropicClientConfig,
AzureOpenAIClientConfig,
ChatGPTClientConfig,
ClientConfig,
GoogleClientConfig,
OpenAIApiType,
OpenAIClientConfig,
VertexAIClientConfig,
)
from enums.llm_provider import LLMProvider
from utils.get_env import (
get_azure_openai_api_key_env,
get_azure_openai_api_version_env,
get_azure_openai_base_url_env,
get_azure_openai_deployment_env,
get_azure_openai_endpoint_env,
get_anthropic_api_key_env,
get_codex_access_token_env,
get_codex_account_id_env,
@ -24,6 +31,10 @@ from utils.get_env import (
get_google_api_key_env,
get_ollama_url_env,
get_openai_api_key_env,
get_vertex_api_key_env,
get_vertex_base_url_env,
get_vertex_location_env,
get_vertex_project_env,
get_web_grounding_env,
)
from utils.llm_provider import get_llm_provider
@ -101,6 +112,74 @@ def get_llm_config() -> ClientConfig:
if not api_key:
raise HTTPException(status_code=400, detail="Google API Key is not set")
return GoogleClientConfig(api_key=api_key)
case LLMProvider.VERTEX:
api_key = get_vertex_api_key_env()
project = get_vertex_project_env()
location = get_vertex_location_env()
base_url = get_vertex_base_url_env()
if api_key and (project or location):
raise HTTPException(
status_code=400,
detail=(
"Vertex configuration is ambiguous. Configure either "
"VERTEX_API_KEY or VERTEX_PROJECT/VERTEX_LOCATION, not both."
),
)
if api_key:
return VertexAIClientConfig(
api_key=api_key,
base_url=base_url or None,
)
if not project:
raise HTTPException(
status_code=400,
detail=(
"Vertex configuration is incomplete. Set VERTEX_API_KEY "
"or VERTEX_PROJECT (optionally with VERTEX_LOCATION)."
),
)
return VertexAIClientConfig(
project=project,
location=location or None,
base_url=base_url or None,
)
case LLMProvider.AZURE:
api_key = get_azure_openai_api_key_env()
api_version = get_azure_openai_api_version_env()
endpoint = get_azure_openai_endpoint_env()
base_url = get_azure_openai_base_url_env()
deployment = get_azure_openai_deployment_env()
if not api_key:
raise HTTPException(
status_code=400,
detail="Azure OpenAI API Key is not set",
)
if not api_version:
raise HTTPException(
status_code=400,
detail="Azure OpenAI API Version is not set",
)
if not endpoint and not base_url:
raise HTTPException(
status_code=400,
detail=(
"Azure OpenAI endpoint is not set. "
"Configure AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_BASE_URL."
),
)
return AzureOpenAIClientConfig(
api_key=api_key,
api_version=api_version,
endpoint=endpoint or None,
base_url=base_url or None,
deployment=deployment or None,
)
case LLMProvider.ANTHROPIC:
api_key = get_anthropic_api_key_env()
if not api_key:
@ -134,8 +213,8 @@ def get_llm_config() -> ClientConfig:
raise HTTPException(
status_code=400,
detail=(
"LLM Provider must be either openai, google, anthropic, "
"ollama, custom, or codex"
"LLM Provider must be either openai, google, vertex, azure, "
"anthropic, ollama, custom, or codex"
),
)

View file

@ -4,12 +4,15 @@ from openai import OpenAI
from constants.llm import (
DEFAULT_ANTHROPIC_MODEL,
DEFAULT_AZURE_MODEL,
DEFAULT_CODEX_MODEL,
DEFAULT_GOOGLE_MODEL,
DEFAULT_OPENAI_MODEL,
DEFAULT_VERTEX_MODEL,
)
from enums.llm_provider import LLMProvider
from utils.get_env import (
get_azure_openai_model_env,
get_anthropic_model_env,
get_codex_model_env,
get_custom_model_env,
@ -19,6 +22,7 @@ from utils.get_env import (
get_ollama_model_env,
get_openai_api_key_env,
get_openai_model_env,
get_vertex_model_env,
)
@ -28,7 +32,10 @@ def get_llm_provider():
except:
raise HTTPException(
status_code=500,
detail=f"Invalid LLM provider. Please select one of: openai, google, anthropic, ollama, custom, codex",
detail=(
"Invalid LLM provider. Please select one of: "
"openai, google, vertex, azure, anthropic, ollama, custom, codex"
),
)
@ -44,6 +51,14 @@ def is_anthropic_selected():
return get_llm_provider() == LLMProvider.ANTHROPIC
def is_vertex_selected():
return get_llm_provider() == LLMProvider.VERTEX
def is_azure_selected():
return get_llm_provider() == LLMProvider.AZURE
def is_ollama_selected():
return get_llm_provider() == LLMProvider.OLLAMA
@ -62,6 +77,10 @@ def get_model():
return get_openai_model_env() or DEFAULT_OPENAI_MODEL
elif selected_llm == LLMProvider.GOOGLE:
return get_google_model_env() or DEFAULT_GOOGLE_MODEL
elif selected_llm == LLMProvider.VERTEX:
return get_vertex_model_env() or DEFAULT_VERTEX_MODEL
elif selected_llm == LLMProvider.AZURE:
return get_azure_openai_model_env() or DEFAULT_AZURE_MODEL
elif selected_llm == LLMProvider.ANTHROPIC:
return get_anthropic_model_env() or DEFAULT_ANTHROPIC_MODEL
elif selected_llm == LLMProvider.OLLAMA:
@ -73,7 +92,10 @@ def get_model():
else:
raise HTTPException(
status_code=500,
detail=f"Invalid LLM provider. Please select one of: openai, google, anthropic, ollama, custom, codex",
detail=(
"Invalid LLM provider. Please select one of: "
"openai, google, vertex, azure, anthropic, ollama, custom, codex"
),
)

View file

@ -64,7 +64,7 @@ def get_generate_kwargs(
if max_tokens is not None:
kwargs["max_tokens"] = max_tokens
if tools:
if get_llm_provider() == LLMProvider.GOOGLE:
if get_llm_provider() in (LLMProvider.GOOGLE, LLMProvider.VERTEX):
kwargs["tools"] = _tools_for_google_gemini(tools)
else:
kwargs["tools"] = tools

View file

@ -8,6 +8,10 @@ from utils.available_models import (
list_available_openai_compatible_models,
)
from utils.get_env import (
get_azure_openai_api_key_env,
get_azure_openai_api_version_env,
get_azure_openai_base_url_env,
get_azure_openai_endpoint_env,
get_anthropic_api_key_env,
get_anthropic_model_env,
get_can_change_keys_env,
@ -16,6 +20,9 @@ from utils.get_env import (
get_openai_model_env,
get_pixabay_api_key_env,
get_pexels_api_key_env,
get_vertex_api_key_env,
get_vertex_location_env,
get_vertex_project_env,
get_comfyui_url_env,
get_comfyui_workflow_env,
)
@ -65,6 +72,34 @@ async def check_llm_and_image_provider_api_or_model_availability():
print("Available models: ", available_models)
raise Exception(f"Model {google_model} is not available")
elif get_llm_provider() == LLMProvider.VERTEX:
vertex_api_key = get_vertex_api_key_env()
vertex_project = get_vertex_project_env()
vertex_location = get_vertex_location_env()
if not vertex_api_key and not vertex_project:
raise Exception(
"Configure VERTEX_API_KEY or VERTEX_PROJECT for Vertex AI"
)
if vertex_api_key and (vertex_project or vertex_location):
raise Exception(
"Vertex config is ambiguous. Use either VERTEX_API_KEY or "
"VERTEX_PROJECT/VERTEX_LOCATION, not both."
)
elif get_llm_provider() == LLMProvider.AZURE:
azure_api_key = get_azure_openai_api_key_env()
azure_endpoint = get_azure_openai_endpoint_env()
azure_base_url = get_azure_openai_base_url_env()
azure_api_version = get_azure_openai_api_version_env()
if not azure_api_key:
raise Exception("AZURE_OPENAI_API_KEY must be provided")
if not azure_api_version:
raise Exception("AZURE_OPENAI_API_VERSION must be provided")
if not azure_endpoint and not azure_base_url:
raise Exception(
"AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_BASE_URL must be provided"
)
elif get_llm_provider() == LLMProvider.ANTHROPIC:
anthropic_api_key = get_anthropic_api_key_env()
if not anthropic_api_key:

View file

@ -37,6 +37,50 @@ def set_google_model_env(value):
os.environ["GOOGLE_MODEL"] = value
def set_vertex_api_key_env(value):
os.environ["VERTEX_API_KEY"] = value
def set_vertex_model_env(value):
os.environ["VERTEX_MODEL"] = value
def set_vertex_project_env(value):
os.environ["VERTEX_PROJECT"] = value
def set_vertex_location_env(value):
os.environ["VERTEX_LOCATION"] = value
def set_vertex_base_url_env(value):
os.environ["VERTEX_BASE_URL"] = value
def set_azure_openai_api_key_env(value):
os.environ["AZURE_OPENAI_API_KEY"] = value
def set_azure_openai_model_env(value):
os.environ["AZURE_OPENAI_MODEL"] = value
def set_azure_openai_endpoint_env(value):
os.environ["AZURE_OPENAI_ENDPOINT"] = value
def set_azure_openai_base_url_env(value):
os.environ["AZURE_OPENAI_BASE_URL"] = value
def set_azure_openai_api_version_env(value):
os.environ["AZURE_OPENAI_API_VERSION"] = value
def set_azure_openai_deployment_env(value):
os.environ["AZURE_OPENAI_DEPLOYMENT"] = value
def set_anthropic_api_key_env(value):
os.environ["ANTHROPIC_API_KEY"] = value

View file

@ -15,6 +15,17 @@ from utils.get_env import (
get_disable_thinking_env,
get_google_api_key_env,
get_google_model_env,
get_vertex_api_key_env,
get_vertex_model_env,
get_vertex_project_env,
get_vertex_location_env,
get_vertex_base_url_env,
get_azure_openai_api_key_env,
get_azure_openai_model_env,
get_azure_openai_endpoint_env,
get_azure_openai_base_url_env,
get_azure_openai_api_version_env,
get_azure_openai_deployment_env,
get_gpt_image_1_5_quality_env,
get_llm_provider_env,
get_ollama_model_env,
@ -53,6 +64,17 @@ from utils.set_env import (
set_extended_reasoning_env,
set_google_api_key_env,
set_google_model_env,
set_vertex_api_key_env,
set_vertex_model_env,
set_vertex_project_env,
set_vertex_location_env,
set_vertex_base_url_env,
set_azure_openai_api_key_env,
set_azure_openai_model_env,
set_azure_openai_endpoint_env,
set_azure_openai_base_url_env,
set_azure_openai_api_version_env,
set_azure_openai_deployment_env,
set_gpt_image_1_5_quality_env,
set_llm_provider_env,
set_ollama_model_env,
@ -94,6 +116,23 @@ def get_user_config():
OPENAI_MODEL=existing_config.OPENAI_MODEL or get_openai_model_env(),
GOOGLE_API_KEY=existing_config.GOOGLE_API_KEY or get_google_api_key_env(),
GOOGLE_MODEL=existing_config.GOOGLE_MODEL or get_google_model_env(),
VERTEX_API_KEY=existing_config.VERTEX_API_KEY or get_vertex_api_key_env(),
VERTEX_MODEL=existing_config.VERTEX_MODEL or get_vertex_model_env(),
VERTEX_PROJECT=existing_config.VERTEX_PROJECT or get_vertex_project_env(),
VERTEX_LOCATION=existing_config.VERTEX_LOCATION or get_vertex_location_env(),
VERTEX_BASE_URL=existing_config.VERTEX_BASE_URL or get_vertex_base_url_env(),
AZURE_OPENAI_API_KEY=existing_config.AZURE_OPENAI_API_KEY
or get_azure_openai_api_key_env(),
AZURE_OPENAI_MODEL=existing_config.AZURE_OPENAI_MODEL
or get_azure_openai_model_env(),
AZURE_OPENAI_ENDPOINT=existing_config.AZURE_OPENAI_ENDPOINT
or get_azure_openai_endpoint_env(),
AZURE_OPENAI_BASE_URL=existing_config.AZURE_OPENAI_BASE_URL
or get_azure_openai_base_url_env(),
AZURE_OPENAI_API_VERSION=existing_config.AZURE_OPENAI_API_VERSION
or get_azure_openai_api_version_env(),
AZURE_OPENAI_DEPLOYMENT=existing_config.AZURE_OPENAI_DEPLOYMENT
or get_azure_openai_deployment_env(),
ANTHROPIC_API_KEY=existing_config.ANTHROPIC_API_KEY
or get_anthropic_api_key_env(),
ANTHROPIC_MODEL=existing_config.ANTHROPIC_MODEL or get_anthropic_model_env(),
@ -160,6 +199,28 @@ def update_env_with_user_config():
set_google_api_key_env(user_config.GOOGLE_API_KEY)
if user_config.GOOGLE_MODEL:
set_google_model_env(user_config.GOOGLE_MODEL)
if user_config.VERTEX_API_KEY:
set_vertex_api_key_env(user_config.VERTEX_API_KEY)
if user_config.VERTEX_MODEL:
set_vertex_model_env(user_config.VERTEX_MODEL)
if user_config.VERTEX_PROJECT:
set_vertex_project_env(user_config.VERTEX_PROJECT)
if user_config.VERTEX_LOCATION:
set_vertex_location_env(user_config.VERTEX_LOCATION)
if user_config.VERTEX_BASE_URL:
set_vertex_base_url_env(user_config.VERTEX_BASE_URL)
if user_config.AZURE_OPENAI_API_KEY:
set_azure_openai_api_key_env(user_config.AZURE_OPENAI_API_KEY)
if user_config.AZURE_OPENAI_MODEL:
set_azure_openai_model_env(user_config.AZURE_OPENAI_MODEL)
if user_config.AZURE_OPENAI_ENDPOINT:
set_azure_openai_endpoint_env(user_config.AZURE_OPENAI_ENDPOINT)
if user_config.AZURE_OPENAI_BASE_URL:
set_azure_openai_base_url_env(user_config.AZURE_OPENAI_BASE_URL)
if user_config.AZURE_OPENAI_API_VERSION:
set_azure_openai_api_version_env(user_config.AZURE_OPENAI_API_VERSION)
if user_config.AZURE_OPENAI_DEPLOYMENT:
set_azure_openai_deployment_env(user_config.AZURE_OPENAI_DEPLOYMENT)
if user_config.ANTHROPIC_API_KEY:
set_anthropic_api_key_env(user_config.ANTHROPIC_API_KEY)
if user_config.ANTHROPIC_MODEL:

View file

@ -289,6 +289,10 @@ const SettingsPage = () => {
? llmConfig.OPENAI_MODEL
: textProviderKey === "google"
? llmConfig.GOOGLE_MODEL
: textProviderKey === "vertex"
? llmConfig.VERTEX_MODEL
: textProviderKey === "azure"
? llmConfig.AZURE_OPENAI_MODEL
: textProviderKey === "anthropic"
? llmConfig.ANTHROPIC_MODEL
: textProviderKey === "ollama"
@ -312,7 +316,7 @@ const SettingsPage = () => {
useEffect(() => {
if (llmConfig.LLM === "codex" && !llmConfig.CODEX_MODEL || llmConfig.LLM === "openai" && !llmConfig.OPENAI_MODEL || llmConfig.LLM === "google" && !llmConfig.GOOGLE_MODEL || llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_MODEL || llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL || llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) {
if (llmConfig.LLM === "codex" && !llmConfig.CODEX_MODEL || llmConfig.LLM === "openai" && !llmConfig.OPENAI_MODEL || llmConfig.LLM === "google" && !llmConfig.GOOGLE_MODEL || llmConfig.LLM === "vertex" && !llmConfig.VERTEX_MODEL || llmConfig.LLM === "azure" && !llmConfig.AZURE_OPENAI_MODEL || llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_MODEL || llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL || llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) {
notify.error("Cannot save settings", "Please select a model for the selected provider");
const currentUrl = window.location.href;

View file

@ -23,6 +23,8 @@ interface ModelOption {
size?: string;
}
const MANUAL_MODEL_PROVIDERS = new Set(['vertex', 'azure']);
const TextProvider = ({
onInputChange,
@ -40,12 +42,17 @@ const TextProvider = ({
const selectedProvider = (llmConfig.LLM || 'openai') as keyof typeof LLM_PROVIDERS;
const selectedProviderMeta = LLM_PROVIDERS[selectedProvider];
const isManualModelProvider = MANUAL_MODEL_PROVIDERS.has(selectedProvider);
const currentModelField = useMemo(() => {
switch (selectedProvider) {
case 'openai':
return 'OPENAI_MODEL';
case 'google':
return 'GOOGLE_MODEL';
case 'vertex':
return 'VERTEX_MODEL';
case 'azure':
return 'AZURE_OPENAI_MODEL';
case 'anthropic':
return 'ANTHROPIC_MODEL';
case 'ollama':
@ -65,6 +72,10 @@ const TextProvider = ({
return 'OPENAI_API_KEY';
case 'google':
return 'GOOGLE_API_KEY';
case 'vertex':
return 'VERTEX_API_KEY';
case 'azure':
return 'AZURE_OPENAI_API_KEY';
case 'anthropic':
return 'ANTHROPIC_API_KEY';
case 'custom':
@ -80,6 +91,14 @@ const TextProvider = ({
const currentOllamaUrl = llmConfig.OLLAMA_URL || '';
const useCustomOllamaUrl = !!llmConfig.USE_CUSTOM_URL;
const modelLabel = selectedProviderMeta?.label || selectedProvider;
const providerApiKeyLabel =
selectedProvider === 'custom'
? 'Custom LLM API Key'
: selectedProvider === 'vertex'
? 'Vertex API Key'
: selectedProvider === 'azure'
? 'Azure OpenAI API Key'
: `${selectedProvider} API Key`;
useEffect(() => {
if (isFirstRender.current) {
@ -107,6 +126,10 @@ const TextProvider = ({
? 'OPENAI_API_KEY'
: llm === 'google'
? 'GOOGLE_API_KEY'
: llm === 'vertex'
? 'VERTEX_API_KEY'
: llm === 'azure'
? 'AZURE_OPENAI_API_KEY'
: llm === 'anthropic'
? 'ANTHROPIC_API_KEY'
: llm === 'custom'
@ -118,6 +141,7 @@ const TextProvider = ({
};
const fetchAvailableModels = async () => {
if (isManualModelProvider) return;
if (selectedProvider === 'openai' && !currentApiKey) return;
if (selectedProvider === 'google' && !currentApiKey) return;
if (selectedProvider === 'anthropic' && !currentApiKey) return;
@ -405,7 +429,7 @@ const TextProvider = ({
: (
<>
<label className="block text-sm font-medium capitalize text-gray-700 mb-2">
{selectedProvider === 'custom' ? 'Custom LLM API Key' : `${llmConfig.LLM} API Key`}
{providerApiKeyLabel}
</label>
<div className="relative">
<input
@ -413,7 +437,7 @@ const TextProvider = ({
value={currentApiKey}
onChange={(e) => onApiKeyChange(selectedProvider, e.target.value)}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder={`Enter your ${llmConfig.LLM} API key`}
placeholder={`Enter your ${providerApiKeyLabel}`}
/>
<button
type="button"
@ -434,10 +458,67 @@ const TextProvider = ({
placeholder="OpenAI-compatible URL"
/>
)}
{selectedProvider === 'vertex' && (
<div className="mt-2 space-y-2">
<input
type="text"
value={llmConfig.VERTEX_PROJECT || ''}
onChange={(e) => onInputChange(e.target.value, 'VERTEX_PROJECT')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="GCP project (optional if API key used)"
/>
<input
type="text"
value={llmConfig.VERTEX_LOCATION || ''}
onChange={(e) => onInputChange(e.target.value, 'VERTEX_LOCATION')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="GCP location (optional)"
/>
<input
type="text"
value={llmConfig.VERTEX_BASE_URL || ''}
onChange={(e) => onInputChange(e.target.value, 'VERTEX_BASE_URL')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Vertex base URL (optional)"
/>
</div>
)}
{selectedProvider === 'azure' && (
<div className="mt-2 space-y-2">
<input
type="text"
value={llmConfig.AZURE_OPENAI_ENDPOINT || ''}
onChange={(e) => onInputChange(e.target.value, 'AZURE_OPENAI_ENDPOINT')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Azure endpoint (https://...openai.azure.com)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_BASE_URL || ''}
onChange={(e) => onInputChange(e.target.value, 'AZURE_OPENAI_BASE_URL')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Azure base URL (optional alternative)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_API_VERSION || ''}
onChange={(e) => onInputChange(e.target.value, 'AZURE_OPENAI_API_VERSION')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="API version (e.g. 2024-10-21)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_DEPLOYMENT || ''}
onChange={(e) => onInputChange(e.target.value, 'AZURE_OPENAI_DEPLOYMENT')}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Deployment name (optional)"
/>
</div>
)}
</div>
{selectedProvider !== 'ollama' && selectedProvider !== 'codex' && (!modelsChecked || (modelsChecked && availableModels.length === 0)) && (
{!isManualModelProvider && selectedProvider !== 'ollama' && selectedProvider !== 'codex' && (!modelsChecked || (modelsChecked && availableModels.length === 0)) && (
<button
onClick={fetchAvailableModels}
@ -466,7 +547,7 @@ const TextProvider = ({
</div>
</div>
{/* Model Selection - only show if models are available */}
{selectedProvider !== 'codex' && modelsChecked && availableModels.length > 0 ? (
{!isManualModelProvider && selectedProvider !== 'codex' && modelsChecked && availableModels.length > 0 ? (
<div className="w-[222px]">
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
@ -553,6 +634,28 @@ const TextProvider = ({
</div>
</div>
) : null}
{isManualModelProvider ? (
<div className="w-[222px]">
<label className="block text-sm font-medium text-gray-700 mb-3">
{`Enter ${modelLabel} Model`}
</label>
<input
type="text"
value={currentModel}
onChange={(e) => {
if (currentModelField) {
onInputChange(e.target.value, currentModelField);
}
}}
className="w-full h-12 px-4 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder={
selectedProvider === 'vertex'
? 'e.g. gemini-2.5-flash'
: 'e.g. gpt-4.1'
}
/>
</div>
) : null}
</div>
</div>
{/* Show message if no models found */}

View file

@ -14,6 +14,10 @@ const CurrentConfig = () => {
? llmConfig.OPENAI_MODEL
: textProviderKey === "google"
? llmConfig.GOOGLE_MODEL
: textProviderKey === "vertex"
? llmConfig.VERTEX_MODEL
: textProviderKey === "azure"
? llmConfig.AZURE_OPENAI_MODEL
: textProviderKey === "anthropic"
? llmConfig.ANTHROPIC_MODEL
: textProviderKey === "ollama"

View file

@ -72,6 +72,10 @@ const getSelectedTextModel = (config?: LLMConfig): string => {
return config.OPENAI_MODEL || "";
case "google":
return config.GOOGLE_MODEL || "";
case "vertex":
return config.VERTEX_MODEL || "";
case "azure":
return config.AZURE_OPENAI_MODEL || "";
case "anthropic":
return config.ANTHROPIC_MODEL || "";
case "ollama":

View file

@ -19,6 +19,8 @@ import { checkIfSelectedOllamaModelIsPulled, pullOllamaModel } from '@/utils/pro
import { getApiUrl } from '@/utils/api';
import CodexConfig, { CHATGPT_MODELS } from '../CodexConfig';
const MANUAL_MODEL_PROVIDERS = new Set(["vertex", "azure"]);
const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: (step: number) => void }) => {
const pathname = usePathname();
const [openProviderSelect, setOpenProviderSelect] = useState(false);
@ -42,6 +44,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
status: string;
done: boolean;
} | null>(null);
const isManualModelProvider = MANUAL_MODEL_PROVIDERS.has(llmConfig.LLM || "");
const handleProviderChange = (provider: string) => {
setLlmConfig(prev => ({
@ -65,6 +68,10 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
return 'OPENAI_MODEL';
case 'google':
return 'GOOGLE_MODEL';
case 'vertex':
return 'VERTEX_MODEL';
case 'azure':
return 'AZURE_OPENAI_MODEL';
case 'anthropic':
return 'ANTHROPIC_MODEL';
case 'ollama':
@ -81,6 +88,10 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
return 'OPENAI_API_KEY';
case 'google':
return 'GOOGLE_API_KEY';
case 'vertex':
return 'VERTEX_API_KEY';
case 'azure':
return 'AZURE_OPENAI_API_KEY';
case 'anthropic':
return 'ANTHROPIC_API_KEY';
case 'custom':
@ -101,6 +112,14 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
const currentModel = currentModelField ? ((llmConfig as Record<string, unknown>)[currentModelField] as string || '') : '';
const currentOllamaUrl = llmConfig.OLLAMA_URL || '';
const useCustomOllamaUrl = !!llmConfig.USE_CUSTOM_URL;
const providerApiKeyLabel =
llmConfig.LLM === 'custom'
? 'Custom LLM API Key'
: llmConfig.LLM === 'vertex'
? 'Vertex API Key'
: llmConfig.LLM === 'azure'
? 'Azure OpenAI API Key'
: `${llmConfig.LLM} API Key`;
const getSelectedTextModel = (config: LLMConfig): string => {
switch (config.LLM) {
@ -108,6 +127,10 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
return config.OPENAI_MODEL || '';
case 'google':
return config.GOOGLE_MODEL || '';
case 'vertex':
return config.VERTEX_MODEL || '';
case 'azure':
return config.AZURE_OPENAI_MODEL || '';
case 'anthropic':
return config.ANTHROPIC_MODEL || '';
case 'ollama':
@ -128,6 +151,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
};
const fetchAvailableModels = async () => {
if (isManualModelProvider) return;
if (llmConfig.LLM === 'openai' && !currentApiKey) return;
if (llmConfig.LLM === 'google' && !currentApiKey) return;
if (llmConfig.LLM === 'anthropic' && !currentApiKey) return;
@ -607,7 +631,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
<div className='flex items-center justify-between mb-2'>
<label className="block text-sm font-medium capitalize text-gray-700 ">
{llmConfig.LLM === 'custom' ? 'Custom LLM API Key' : `${llmConfig.LLM} API Key`}
{providerApiKeyLabel}
</label>
{llmConfig.LLM && LLM_PROVIDERS[llmConfig.LLM!]?.getApiKeyUrl && <a href={LLM_PROVIDERS[llmConfig.LLM!]?.getApiKeyUrl || ""} target='_blank' className='text-[#666666] text-xs font-normal flex items-center gap-1'>Get API Key <ArrowUpRight className='w-3.5 h-3.5' /></a>}
</div>
@ -621,7 +645,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
[currentApiKeyField]: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder={`Enter your ${llmConfig.LLM} API key`}
placeholder={`Enter your ${providerApiKeyLabel}`}
/>
<button
type="button"
@ -645,12 +669,90 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
placeholder="OpenAI-compatible URL"
/>
)}
{llmConfig.LLM === 'vertex' && (
<div className="mt-2 space-y-2">
<input
type="text"
value={llmConfig.VERTEX_PROJECT || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
VERTEX_PROJECT: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="GCP project (optional if API key used)"
/>
<input
type="text"
value={llmConfig.VERTEX_LOCATION || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
VERTEX_LOCATION: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="GCP location (optional)"
/>
<input
type="text"
value={llmConfig.VERTEX_BASE_URL || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
VERTEX_BASE_URL: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Vertex base URL (optional)"
/>
</div>
)}
{llmConfig.LLM === 'azure' && (
<div className="mt-2 space-y-2">
<input
type="text"
value={llmConfig.AZURE_OPENAI_ENDPOINT || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
AZURE_OPENAI_ENDPOINT: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Azure endpoint (https://...openai.azure.com)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_BASE_URL || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
AZURE_OPENAI_BASE_URL: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Azure base URL (optional alternative)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_API_VERSION || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
AZURE_OPENAI_API_VERSION: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="API version (e.g. 2024-10-21)"
/>
<input
type="text"
value={llmConfig.AZURE_OPENAI_DEPLOYMENT || ''}
onChange={(e) => setLlmConfig(prev => ({
...prev,
AZURE_OPENAI_DEPLOYMENT: e.target.value
}))}
className="w-full px-2 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder="Deployment name (optional)"
/>
</div>
)}
</div>
{llmConfig.LLM !== 'ollama' && llmConfig.LLM !== 'chatgpt' && llmConfig.LLM !== 'codex' && (!modelsChecked || (modelsChecked && availableModels.length === 0)) && (
{!isManualModelProvider && llmConfig.LLM !== 'ollama' && llmConfig.LLM !== 'chatgpt' && llmConfig.LLM !== 'codex' && (!modelsChecked || (modelsChecked && availableModels.length === 0)) && (
<button
onClick={fetchAvailableModels}
@ -683,7 +785,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
{/* Model Selection - only show if models are available */}
{llmConfig.LLM !== 'chatgpt' && llmConfig.LLM !== 'codex' && modelsChecked && availableModels.length > 0 && (
{!isManualModelProvider && llmConfig.LLM !== 'chatgpt' && llmConfig.LLM !== 'codex' && modelsChecked && availableModels.length > 0 && (
<div className="w-full">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
@ -765,6 +867,27 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
</div>
</div>
)}
{isManualModelProvider && (
<div className="w-full">
<label className="block text-sm font-medium text-gray-700 mb-2">
Enter {LLM_PROVIDERS[llmConfig.LLM!]?.label} Model
</label>
<input
type="text"
value={currentModel}
onChange={(e) => {
if (currentModelField) {
setLlmConfig(prev => ({
...prev,
[currentModelField]: e.target.value
}));
}
}}
className="w-full h-12 px-4 py-3 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
placeholder={llmConfig.LLM === 'vertex' ? 'e.g. gemini-2.5-flash' : 'e.g. gpt-4.1'}
/>
</div>
)}
</div>
</div>
{/* Image Provider */}

View file

@ -9,6 +9,21 @@ export interface LLMConfig {
GOOGLE_API_KEY?: string;
GOOGLE_MODEL?: string;
// Vertex AI
VERTEX_API_KEY?: string;
VERTEX_MODEL?: string;
VERTEX_PROJECT?: string;
VERTEX_LOCATION?: string;
VERTEX_BASE_URL?: string;
// Azure OpenAI
AZURE_OPENAI_API_KEY?: string;
AZURE_OPENAI_MODEL?: string;
AZURE_OPENAI_ENDPOINT?: string;
AZURE_OPENAI_BASE_URL?: string;
AZURE_OPENAI_API_VERSION?: string;
AZURE_OPENAI_DEPLOYMENT?: string;
// Anthropic
ANTHROPIC_API_KEY?: string;
ANTHROPIC_MODEL?: string;

View file

@ -132,6 +132,20 @@ export const LLM_PROVIDERS: Record<string, LLMProviderOption> = {
icon: "/providers/gemini-color.svg",
getApiKeyUrl: "https://www.google.com/search?q=how+to+get+google+AI+studio+api+key&sxsrf=ANbL-n5_hUGaEiG9v6k9VxZWyv0mqO0Jew%3A1776339625724",
},
vertex: {
value: "vertex",
label: "Vertex AI",
description: "Google Vertex AI models",
icon: "/providers/gemini-color.svg",
getApiKeyUrl: "https://www.google.com/search?q=how+to+get+vertex+ai+api+key",
},
azure: {
value: "azure",
label: "Azure OpenAI",
description: "Azure-hosted OpenAI deployments",
icon: "/providers/openai.png",
getApiKeyUrl: "https://www.google.com/search?q=azure+openai+api+key",
},
anthropic: {
value: "anthropic",
label: "Anthropic",

View file

@ -34,6 +34,17 @@ export const updateLLMConfig = (
openai_model: "OPENAI_MODEL",
google_api_key: "GOOGLE_API_KEY",
google_model: "GOOGLE_MODEL",
vertex_api_key: "VERTEX_API_KEY",
vertex_model: "VERTEX_MODEL",
vertex_project: "VERTEX_PROJECT",
vertex_location: "VERTEX_LOCATION",
vertex_base_url: "VERTEX_BASE_URL",
azure_openai_api_key: "AZURE_OPENAI_API_KEY",
azure_openai_model: "AZURE_OPENAI_MODEL",
azure_openai_endpoint: "AZURE_OPENAI_ENDPOINT",
azure_openai_base_url: "AZURE_OPENAI_BASE_URL",
azure_openai_api_version: "AZURE_OPENAI_API_VERSION",
azure_openai_deployment: "AZURE_OPENAI_DEPLOYMENT",
anthropic_api_key: "ANTHROPIC_API_KEY",
anthropic_model: "ANTHROPIC_MODEL",
ollama_url: "OLLAMA_URL",
@ -81,7 +92,7 @@ export const changeProvider = (
} else if (provider === "google") {
newConfig.IMAGE_PROVIDER = "gemini_flash";
} else {
newConfig.IMAGE_PROVIDER = "pexels"; // default for ollama, custom, codex
newConfig.IMAGE_PROVIDER = "pexels"; // default for vertex, azure, ollama, custom, codex
}
return newConfig;

View file

@ -36,6 +36,35 @@ export const getLLMConfigValidationError = (
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 === "vertex") {
const hasApiKey = isProvided(llmConfig.VERTEX_API_KEY);
const hasProject = isProvided(llmConfig.VERTEX_PROJECT);
const hasLocation = isProvided(llmConfig.VERTEX_LOCATION);
if (!hasApiKey && !hasProject) {
return "Vertex AI requires either a Vertex API key or a GCP project.";
}
if (hasApiKey && (hasProject || hasLocation)) {
return "Use either Vertex API key mode or project/location mode, not both.";
}
if (!isProvided(llmConfig.VERTEX_MODEL)) {
return "Vertex model is required.";
}
} else if (llm === "azure") {
if (!isProvided(llmConfig.AZURE_OPENAI_API_KEY)) {
return "Azure OpenAI API key is required.";
}
if (!isProvided(llmConfig.AZURE_OPENAI_API_VERSION)) {
return "Azure OpenAI API version is required.";
}
if (
!isProvided(llmConfig.AZURE_OPENAI_ENDPOINT) &&
!isProvided(llmConfig.AZURE_OPENAI_BASE_URL)
) {
return "Azure OpenAI endpoint or base URL is required.";
}
if (!isProvided(llmConfig.AZURE_OPENAI_MODEL)) {
return "Azure OpenAI model/deployment name is required.";
}
} else if (llm === "anthropic") {
if (!isProvided(llmConfig.ANTHROPIC_API_KEY)) {
return "Anthropic API key is required.";

View file

@ -151,7 +151,7 @@ const setupUserConfigFromEnv = () => {
existingConfig = JSON.parse(readFileSync(userConfigPath, "utf8"));
}
if (!["ollama", "openai", "google", "anthropic", "custom", "codex"].includes(existingConfig.LLM)) {
if (!["ollama", "openai", "google", "vertex", "azure", "anthropic", "custom", "codex"].includes(existingConfig.LLM)) {
existingConfig.LLM = undefined;
}
@ -161,6 +161,23 @@ const setupUserConfigFromEnv = () => {
OPENAI_MODEL: process.env.OPENAI_MODEL || existingConfig.OPENAI_MODEL,
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || existingConfig.GOOGLE_API_KEY,
GOOGLE_MODEL: process.env.GOOGLE_MODEL || existingConfig.GOOGLE_MODEL,
VERTEX_API_KEY: process.env.VERTEX_API_KEY || existingConfig.VERTEX_API_KEY,
VERTEX_MODEL: process.env.VERTEX_MODEL || existingConfig.VERTEX_MODEL,
VERTEX_PROJECT: process.env.VERTEX_PROJECT || existingConfig.VERTEX_PROJECT,
VERTEX_LOCATION: process.env.VERTEX_LOCATION || existingConfig.VERTEX_LOCATION,
VERTEX_BASE_URL: process.env.VERTEX_BASE_URL || existingConfig.VERTEX_BASE_URL,
AZURE_OPENAI_API_KEY:
process.env.AZURE_OPENAI_API_KEY || existingConfig.AZURE_OPENAI_API_KEY,
AZURE_OPENAI_MODEL:
process.env.AZURE_OPENAI_MODEL || existingConfig.AZURE_OPENAI_MODEL,
AZURE_OPENAI_ENDPOINT:
process.env.AZURE_OPENAI_ENDPOINT || existingConfig.AZURE_OPENAI_ENDPOINT,
AZURE_OPENAI_BASE_URL:
process.env.AZURE_OPENAI_BASE_URL || existingConfig.AZURE_OPENAI_BASE_URL,
AZURE_OPENAI_API_VERSION:
process.env.AZURE_OPENAI_API_VERSION || existingConfig.AZURE_OPENAI_API_VERSION,
AZURE_OPENAI_DEPLOYMENT:
process.env.AZURE_OPENAI_DEPLOYMENT || existingConfig.AZURE_OPENAI_DEPLOYMENT,
OLLAMA_URL: process.env.OLLAMA_URL || existingConfig.OLLAMA_URL,
OLLAMA_MODEL: process.env.OLLAMA_MODEL || existingConfig.OLLAMA_MODEL,
ANTHROPIC_API_KEY: