Skip to content
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
7 changes: 7 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ MAX_MESSAGES_PER_SESSION=5
# Google Authentication
GOOGLE_CLIENT_ID=your-client-id

# Ollama base URL — server-side default when no ollama_url is sent in the request.
# For local dev this is fine. For deployed instances (e.g. Vercel), users must pass
# their own ollama_url in each API request (e.g. an ngrok tunnel to their local machine),
# because the server cannot reach a user's localhost.
# Example for a self-hosted or Docker instance: OLLAMA_BASE_URL="http://192.168.1.100:11434"
OLLAMA_BASE_URL="http://localhost:11434"

# Plugin Architecture Config
# PLUGINS_DIRS="path/to/plugins1,path/to/plugins2" # Optional: Override the default plugins locations
# ENABLED_PLUGINS="example_plugin,another_plugin" # Optional: Comma-separated list of plugins to enable
1 change: 1 addition & 0 deletions server/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ class Config:
ENABLED_PLUGINS = os.getenv("ENABLED_PLUGINS", None) # Comma-separated list of plugin folder names
MAX_MESSAGES_PER_SESSION = int(os.getenv("MAX_MESSAGES_PER_SESSION", 10))
CONTACT_EMAIL = os.getenv("CONTACT_EMAIL", "support@privgpt-studio.com")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
16 changes: 11 additions & 5 deletions server/api/routes/chat_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,14 @@ def chat():
model_name = request.form.get("model_name", "")
session_id = request.form.get("session_id", "1")
session_name = request.form.get("session_name", "")

# User-supplied Ollama URL (e.g. an ngrok tunnel) lets deployed instances
# proxy to the user's local Ollama without changing the server default.
ollama_url = request.form.get("ollama_url") or Config.OLLAMA_BASE_URL

# Plugin: before_prompt
if plugin_manager:
user_msg = plugin_manager.before_prompt(user_msg)

user_timestamp = datetime.now() - timedelta(seconds=10)
session_id = request.form.get("session_id", "1")

Expand Down Expand Up @@ -286,7 +289,7 @@ def chat():
payload["system"] = system_prompt
try:
latency_ms = datetime.now()
response = requests.post("http://localhost:11434/api/generate", json=payload, timeout=60)
response = requests.post(f"{ollama_url}/api/generate", json=payload, timeout=60)
latency_ms = int((datetime.now() - latency_ms).total_seconds() * 1000)
bot_reply = response.json().get("response", "No reply.")

Expand Down Expand Up @@ -429,11 +432,14 @@ def chat_stream():
model_name = request.form.get("model_name", "")
session_id = request.form.get("session_id", "1")
session_name = request.form.get("session_name", "")
# User-supplied Ollama URL (e.g. an ngrok tunnel) lets deployed instances
# proxy to the user's local Ollama without changing the server default.
ollama_url = request.form.get("ollama_url") or Config.OLLAMA_BASE_URL

# Plugin: before_prompt
if plugin_manager:
user_msg = plugin_manager.before_prompt(user_msg)

if is_session_locked(session_id):
def error_generator_locked():
err_msg = "This chat is locked and cannot receive new messages."
Expand Down Expand Up @@ -549,7 +555,7 @@ def generate_stream():
payload["options"]["seed"] = seed
if system_prompt:
payload["system"] = system_prompt
response = requests.post("http://localhost:11434/api/generate", json=payload, stream=True, timeout=60)
response = requests.post(f"{ollama_url}/api/generate", json=payload, stream=True, timeout=60)
response.raise_for_status()

for line in response.iter_lines():
Expand Down
14 changes: 9 additions & 5 deletions server/api/routes/model_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ def models():
"""
Returns available local and cloud models.

Accepts optional query param ?ollama_url= so deployed frontends can
proxy through a user-supplied Ollama address (e.g. an ngrok tunnel).

Returns:
JSON: Dictionary with local_models and cloud_models keys.
"""

local_models = get_available_models()
ollama_url = request.args.get("ollama_url") or None
local_models = get_available_models(ollama_url=ollama_url)
cloud_models = ["gemini"]
return jsonify({
"local_models": local_models,
Expand All @@ -23,14 +26,15 @@ def models():
def model_info():
"""
Returns detailed information about a specific model.
Expects JSON: { "model_name": "name", "model_type": "local"|"cloud" }
Expects JSON: { "model_name": "name", "model_type": "local"|"cloud", "ollama_url": "..." (optional) }
"""
data = request.json
if not data:
return jsonify({"error": "No data provided"}), 400

model_name = data.get("model_name")
model_type = data.get("model_type", "local")
ollama_url = data.get("ollama_url") or None

if not model_name:
return jsonify({"error": "Model name is required"}), 400
Expand All @@ -51,10 +55,10 @@ def model_info():
}
})

details = get_model_details(model_name)
details = get_model_details(model_name, ollama_url=ollama_url)
if details:
return jsonify(details)

return jsonify({"error": "Failed to fetch model info"}), 500

select_model_bp = Blueprint('select_model_bp', __name__)
Expand Down
15 changes: 11 additions & 4 deletions server/api/services/ollama_services.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import requests
from api.config import Config

def get_available_models():
def get_available_models(ollama_url=None):
"""
Fetches list of available local models from Ollama.

Args:
ollama_url (str, optional): Custom Ollama base URL. Defaults to Config.OLLAMA_BASE_URL.

Returns:
list: Names of available local models (with full tags).
"""
base_url = ollama_url or Config.OLLAMA_BASE_URL
try:
res = requests.get("http://localhost:11434/api/tags", timeout=5)
res = requests.get(f"{base_url}/api/tags", timeout=5)
# Return full model names including tags (e.g., "gemma3:1b" instead of just "gemma3")
return sorted(m['name'] for m in res.json().get("models", []))
except:
return []

def get_model_details(model_name):
def get_model_details(model_name, ollama_url=None):
"""
Fetches detailed information for a specific local model from Ollama.

Args:
model_name (str): The name of the model to inspect.
ollama_url (str, optional): Custom Ollama base URL. Defaults to Config.OLLAMA_BASE_URL.

Returns:
dict: The JSON response from Ollama's /api/show endpoint, or None if failed.
"""
base_url = ollama_url or Config.OLLAMA_BASE_URL
try:
res = requests.post("http://localhost:11434/api/show", json={"name": model_name}, timeout=5)
res = requests.post(f"{base_url}/api/show", json={"name": model_name}, timeout=5)
if res.status_code == 200:
return res.json()
return None
Expand Down