2025-03-14 18:53:14 -07:00
|
|
|
import os
|
2025-05-05 01:39:31 -07:00
|
|
|
import shutil
|
2025-07-24 14:43:48 -07:00
|
|
|
from pathlib import Path
|
2025-03-14 18:53:14 -07:00
|
|
|
|
2025-11-14 21:53:46 -08:00
|
|
|
import yaml
|
2025-04-29 23:02:07 -07:00
|
|
|
from chonkie import AutoEmbeddings, CodeChunker, RecursiveChunker
|
2025-03-14 18:53:14 -07:00
|
|
|
from dotenv import load_dotenv
|
2025-04-29 23:02:07 -07:00
|
|
|
from rerankers import Reranker
|
2025-05-13 21:13:53 -07:00
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# Get the base directory of the project
|
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
|
|
|
|
|
|
|
|
env_file = BASE_DIR / ".env"
|
|
|
|
|
load_dotenv(env_file)
|
|
|
|
|
|
|
|
|
|
|
2025-05-05 01:39:31 -07:00
|
|
|
def is_ffmpeg_installed():
|
|
|
|
|
"""
|
|
|
|
|
Check if ffmpeg is installed on the current system.
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-05-05 01:39:31 -07:00
|
|
|
Returns:
|
|
|
|
|
bool: True if ffmpeg is installed, False otherwise.
|
|
|
|
|
"""
|
|
|
|
|
return shutil.which("ffmpeg") is not None
|
|
|
|
|
|
|
|
|
|
|
2025-11-14 21:53:46 -08:00
|
|
|
def load_global_llm_configs():
|
|
|
|
|
"""
|
|
|
|
|
Load global LLM configurations from YAML file.
|
|
|
|
|
Falls back to example file if main file doesn't exist.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
list: List of global LLM config dictionaries, or empty list if file doesn't exist
|
|
|
|
|
"""
|
|
|
|
|
# Try main config file first
|
|
|
|
|
global_config_file = BASE_DIR / "app" / "config" / "global_llm_config.yaml"
|
|
|
|
|
|
|
|
|
|
if not global_config_file.exists():
|
|
|
|
|
# No global configs available
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(global_config_file, encoding="utf-8") as f:
|
|
|
|
|
data = yaml.safe_load(f)
|
|
|
|
|
return data.get("global_llm_configs", [])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Warning: Failed to load global LLM configs: {e}")
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
class Config:
|
2025-05-05 01:39:31 -07:00
|
|
|
# Check if ffmpeg is installed
|
|
|
|
|
if not is_ffmpeg_installed():
|
|
|
|
|
import static_ffmpeg
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-05-05 01:39:31 -07:00
|
|
|
# ffmpeg installed on first call to add_paths(), threadsafe.
|
|
|
|
|
static_ffmpeg.add_paths()
|
|
|
|
|
# check if ffmpeg is installed again
|
|
|
|
|
if not is_ffmpeg_installed():
|
2025-07-24 14:43:48 -07:00
|
|
|
raise ValueError(
|
|
|
|
|
"FFmpeg is not installed on the system. Please install it to use the Surfsense Podcaster."
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# Database
|
|
|
|
|
DATABASE_URL = os.getenv("DATABASE_URL")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
NEXT_FRONTEND_URL = os.getenv("NEXT_FRONTEND_URL")
|
2025-10-30 23:52:14 -07:00
|
|
|
# Backend URL to override the http to https in the OAuth redirect URI
|
|
|
|
|
BACKEND_URL = os.getenv("BACKEND_URL")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-08-02 05:36:43 +02:00
|
|
|
# Auth
|
2025-05-21 20:56:23 -07:00
|
|
|
AUTH_TYPE = os.getenv("AUTH_TYPE")
|
2025-10-20 15:54:52 +05:30
|
|
|
REGISTRATION_ENABLED = os.getenv("REGISTRATION_ENABLED", "TRUE").upper() == "TRUE"
|
2025-08-02 04:39:48 +02:00
|
|
|
|
2025-08-02 05:36:43 +02:00
|
|
|
# Google OAuth
|
2025-08-02 04:39:48 +02:00
|
|
|
GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
|
|
|
|
|
GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
|
2025-08-02 05:36:43 +02:00
|
|
|
|
|
|
|
|
# Google Calendar redirect URI
|
2025-08-02 04:39:48 +02:00
|
|
|
GOOGLE_CALENDAR_REDIRECT_URI = os.getenv("GOOGLE_CALENDAR_REDIRECT_URI")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-08-04 01:02:35 +02:00
|
|
|
# Google Gmail redirect URI
|
|
|
|
|
GOOGLE_GMAIL_REDIRECT_URI = os.getenv("GOOGLE_GMAIL_REDIRECT_URI")
|
|
|
|
|
|
2025-12-28 15:53:51 +02:00
|
|
|
# Google Drive redirect URI
|
|
|
|
|
GOOGLE_DRIVE_REDIRECT_URI = os.getenv("GOOGLE_DRIVE_REDIRECT_URI")
|
|
|
|
|
|
2025-08-26 13:56:31 +02:00
|
|
|
# Airtable OAuth
|
|
|
|
|
AIRTABLE_CLIENT_ID = os.getenv("AIRTABLE_CLIENT_ID")
|
|
|
|
|
AIRTABLE_CLIENT_SECRET = os.getenv("AIRTABLE_CLIENT_SECRET")
|
|
|
|
|
AIRTABLE_REDIRECT_URI = os.getenv("AIRTABLE_REDIRECT_URI")
|
|
|
|
|
|
2026-01-02 20:07:14 +05:30
|
|
|
# Notion OAuth
|
|
|
|
|
NOTION_CLIENT_ID = os.getenv("NOTION_CLIENT_ID")
|
|
|
|
|
NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET")
|
|
|
|
|
NOTION_REDIRECT_URI = os.getenv("NOTION_REDIRECT_URI")
|
|
|
|
|
|
2026-01-06 13:20:22 +05:30
|
|
|
# Atlassian OAuth (shared for Jira and Confluence)
|
|
|
|
|
ATLASSIAN_CLIENT_ID = os.getenv("ATLASSIAN_CLIENT_ID")
|
|
|
|
|
ATLASSIAN_CLIENT_SECRET = os.getenv("ATLASSIAN_CLIENT_SECRET")
|
2026-01-06 01:27:29 +05:30
|
|
|
JIRA_REDIRECT_URI = os.getenv("JIRA_REDIRECT_URI")
|
2026-01-06 13:20:22 +05:30
|
|
|
CONFLUENCE_REDIRECT_URI = os.getenv("CONFLUENCE_REDIRECT_URI")
|
2026-01-06 01:27:29 +05:30
|
|
|
|
2026-01-02 21:24:28 +05:30
|
|
|
# Linear OAuth
|
|
|
|
|
LINEAR_CLIENT_ID = os.getenv("LINEAR_CLIENT_ID")
|
|
|
|
|
LINEAR_CLIENT_SECRET = os.getenv("LINEAR_CLIENT_SECRET")
|
|
|
|
|
LINEAR_REDIRECT_URI = os.getenv("LINEAR_REDIRECT_URI")
|
|
|
|
|
|
2026-01-04 02:30:00 +05:30
|
|
|
# Slack OAuth
|
|
|
|
|
SLACK_CLIENT_ID = os.getenv("SLACK_CLIENT_ID")
|
|
|
|
|
SLACK_CLIENT_SECRET = os.getenv("SLACK_CLIENT_SECRET")
|
|
|
|
|
SLACK_REDIRECT_URI = os.getenv("SLACK_REDIRECT_URI")
|
|
|
|
|
|
2026-01-05 14:21:39 +05:30
|
|
|
# Discord OAuth
|
|
|
|
|
DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID")
|
|
|
|
|
DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET")
|
|
|
|
|
DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI")
|
|
|
|
|
DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
|
|
|
|
|
|
2026-01-07 15:15:49 -08:00
|
|
|
# Microsoft Teams OAuth
|
|
|
|
|
TEAMS_CLIENT_ID = os.getenv("TEAMS_CLIENT_ID")
|
|
|
|
|
TEAMS_CLIENT_SECRET = os.getenv("TEAMS_CLIENT_SECRET")
|
|
|
|
|
TEAMS_REDIRECT_URI = os.getenv("TEAMS_REDIRECT_URI")
|
|
|
|
|
|
2026-01-07 15:15:25 +05:30
|
|
|
# ClickUp OAuth
|
|
|
|
|
CLICKUP_CLIENT_ID = os.getenv("CLICKUP_CLIENT_ID")
|
|
|
|
|
CLICKUP_CLIENT_SECRET = os.getenv("CLICKUP_CLIENT_SECRET")
|
|
|
|
|
CLICKUP_REDIRECT_URI = os.getenv("CLICKUP_REDIRECT_URI")
|
|
|
|
|
|
2025-06-09 15:50:15 -07:00
|
|
|
# LLM instances are now managed per-user through the LLMConfig system
|
|
|
|
|
# Legacy environment variables removed in favor of user-specific configurations
|
2025-03-14 18:53:14 -07:00
|
|
|
|
2025-11-14 21:53:46 -08:00
|
|
|
# Global LLM Configurations (optional)
|
|
|
|
|
# Load from global_llm_config.yaml if available
|
|
|
|
|
# These can be used as default options for users
|
|
|
|
|
GLOBAL_LLM_CONFIGS = load_global_llm_configs()
|
|
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# Chonkie Configuration | Edit this to your needs
|
|
|
|
|
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL")
|
2025-10-30 22:33:47 -07:00
|
|
|
# Azure OpenAI credentials from environment variables
|
|
|
|
|
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
|
|
|
|
|
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
|
|
|
|
|
|
|
|
|
|
# Pass Azure credentials to embeddings when using Azure OpenAI
|
|
|
|
|
embedding_kwargs = {}
|
|
|
|
|
if AZURE_OPENAI_ENDPOINT:
|
|
|
|
|
embedding_kwargs["azure_endpoint"] = AZURE_OPENAI_ENDPOINT
|
|
|
|
|
if AZURE_OPENAI_API_KEY:
|
|
|
|
|
embedding_kwargs["azure_api_key"] = AZURE_OPENAI_API_KEY
|
|
|
|
|
|
|
|
|
|
embedding_model_instance = AutoEmbeddings.get_embeddings(
|
|
|
|
|
EMBEDDING_MODEL,
|
|
|
|
|
**embedding_kwargs,
|
|
|
|
|
)
|
2025-04-29 23:02:07 -07:00
|
|
|
chunker_instance = RecursiveChunker(
|
2025-07-24 14:43:48 -07:00
|
|
|
chunk_size=getattr(embedding_model_instance, "max_seq_length", 512)
|
2025-03-14 18:53:14 -07:00
|
|
|
)
|
2025-04-29 23:02:07 -07:00
|
|
|
code_chunker_instance = CodeChunker(
|
2025-07-24 14:43:48 -07:00
|
|
|
chunk_size=getattr(embedding_model_instance, "max_seq_length", 512)
|
2025-04-29 23:02:07 -07:00
|
|
|
)
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# Reranker's Configuration | Pinecode, Cohere etc. Read more at https://github.com/AnswerDotAI/rerankers?tab=readme-ov-file#usage
|
2025-10-29 23:23:08 -07:00
|
|
|
RERANKERS_ENABLED = os.getenv("RERANKERS_ENABLED", "FALSE").upper() == "TRUE"
|
|
|
|
|
if RERANKERS_ENABLED:
|
|
|
|
|
RERANKERS_MODEL_NAME = os.getenv("RERANKERS_MODEL_NAME")
|
|
|
|
|
RERANKERS_MODEL_TYPE = os.getenv("RERANKERS_MODEL_TYPE")
|
|
|
|
|
reranker_instance = Reranker(
|
|
|
|
|
model_name=RERANKERS_MODEL_NAME,
|
|
|
|
|
model_type=RERANKERS_MODEL_TYPE,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
reranker_instance = None
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# OAuth JWT
|
|
|
|
|
SECRET_KEY = os.getenv("SECRET_KEY")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-05-30 19:17:19 -07:00
|
|
|
# ETL Service
|
|
|
|
|
ETL_SERVICE = os.getenv("ETL_SERVICE")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-12-11 00:29:56 -08:00
|
|
|
# Pages limit for ETL services (default to very high number for OSS unlimited usage)
|
|
|
|
|
PAGES_LIMIT = int(os.getenv("PAGES_LIMIT", "999999999"))
|
|
|
|
|
|
2025-05-30 19:17:19 -07:00
|
|
|
if ETL_SERVICE == "UNSTRUCTURED":
|
|
|
|
|
# Unstructured API Key
|
|
|
|
|
UNSTRUCTURED_API_KEY = os.getenv("UNSTRUCTURED_API_KEY")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-05-30 19:17:19 -07:00
|
|
|
elif ETL_SERVICE == "LLAMACLOUD":
|
|
|
|
|
# LlamaCloud API Key
|
|
|
|
|
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-05-05 01:39:31 -07:00
|
|
|
# Litellm TTS Configuration
|
|
|
|
|
TTS_SERVICE = os.getenv("TTS_SERVICE")
|
2025-05-13 21:13:53 -07:00
|
|
|
TTS_SERVICE_API_BASE = os.getenv("TTS_SERVICE_API_BASE")
|
2025-06-09 15:50:15 -07:00
|
|
|
TTS_SERVICE_API_KEY = os.getenv("TTS_SERVICE_API_KEY")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-10-11 23:56:12 +05:00
|
|
|
# STT Configuration
|
2025-05-13 21:13:53 -07:00
|
|
|
STT_SERVICE = os.getenv("STT_SERVICE")
|
|
|
|
|
STT_SERVICE_API_BASE = os.getenv("STT_SERVICE_API_BASE")
|
2025-06-09 15:50:15 -07:00
|
|
|
STT_SERVICE_API_KEY = os.getenv("STT_SERVICE_API_KEY")
|
2025-07-24 14:43:48 -07:00
|
|
|
|
2025-03-14 18:53:14 -07:00
|
|
|
# Validation Checks
|
|
|
|
|
# Check embedding dimension
|
2025-07-24 14:43:48 -07:00
|
|
|
if (
|
|
|
|
|
hasattr(embedding_model_instance, "dimension")
|
|
|
|
|
and embedding_model_instance.dimension > 2000
|
|
|
|
|
):
|
2025-03-14 18:53:14 -07:00
|
|
|
raise ValueError(
|
|
|
|
|
f"Embedding dimension for Model: {EMBEDDING_MODEL} "
|
|
|
|
|
f"has {embedding_model_instance.dimension} dimensions, which "
|
|
|
|
|
f"exceeds the maximum of 2000 allowed by PGVector."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def get_settings(cls):
|
|
|
|
|
"""Get all settings as a dictionary."""
|
|
|
|
|
return {
|
|
|
|
|
key: value
|
|
|
|
|
for key, value in cls.__dict__.items()
|
|
|
|
|
if not key.startswith("_") and not callable(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create a config instance
|
|
|
|
|
config = Config()
|