Advertisement
Ra7eN

webui main python script modified

Apr 17th, 2025 (edited)
317
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 64.75 KB | Source Code | 0 0
  1. ##############################################################################
  2. # PROJECT: OPEN WEBUI/OLLAMA/MODEL TO HAVE PERSISTENT MEMORY LONG TERM.
  3. # FILENAME: MAIN.PY
  4. # DESC: Part of open_webui custom persistent memory
  5. # UPDATED: 250418 11:30
  6. #   HAS CUSTOM PERSONALITY,
  7. #   LOGS CONVO'S, READ BACK LAST 5(changeable)
  8. #   UPDATES PEOPLE AND BASIC INFO (TODO EXPAND ON DETAIL ABOUT PEOPLE)
  9. #   LOGS LIKES/DISLIKES OF USER AND AI (MARY)
  10. # TODO:
  11. #   REMEMBER EVENTS AND OTHER IMPORTANT INFO
  12. #   REMOVED DATA DUMP FROM BELOW:
  13. #       ...\open_webui\retrieval\uitls.py line:88
  14. #   TODO:
  15. #   SQLLITE3 MODEL FOR LONGER TERM MEMORY.
  16. ##############################################################################
  17. import asyncio
  18. import inspect
  19. import json
  20. import logging
  21. import mimetypes
  22. import os
  23. import shutil
  24. import sys
  25. import time
  26. import random
  27. from datetime import datetime
  28. import pytz
  29.  
  30. from contextlib import asynccontextmanager
  31. from urllib.parse import urlencode, parse_qs, urlparse
  32. from pydantic import BaseModel
  33. from sqlalchemy import text
  34.  
  35. from typing import Optional
  36. from aiocache import cached
  37. import aiohttp
  38. import requests
  39.  
  40.  
  41. from fastapi import (
  42.     Depends,
  43.     FastAPI,
  44.     File,
  45.     Form,
  46.     HTTPException,
  47.     Request,
  48.     UploadFile,
  49.     status,
  50.     applications,
  51.     BackgroundTasks,
  52. )
  53.  
  54. from fastapi.openapi.docs import get_swagger_ui_html
  55.  
  56. from fastapi.middleware.cors import CORSMiddleware
  57. from fastapi.responses import JSONResponse, RedirectResponse
  58. from fastapi.staticfiles import StaticFiles
  59.  
  60. from starlette.exceptions import HTTPException as StarletteHTTPException
  61. from starlette.middleware.base import BaseHTTPMiddleware
  62. from starlette.middleware.sessions import SessionMiddleware
  63. from starlette.responses import Response, StreamingResponse
  64.  
  65.  
  66. from open_webui.utils import logger
  67. from open_webui.utils.audit import AuditLevel, AuditLoggingMiddleware
  68. from open_webui.utils.logger import start_logger
  69. from open_webui.socket.main import (
  70.     app as socket_app,
  71.     periodic_usage_pool_cleanup,
  72. )
  73. from open_webui.routers import (
  74.     audio,
  75.     images,
  76.     ollama,
  77.     openai,
  78.     retrieval,
  79.     pipelines,
  80.     tasks,
  81.     auths,
  82.     channels,
  83.     chats,
  84.     folders,
  85.     configs,
  86.     groups,
  87.     files,
  88.     functions,
  89.     memories,
  90.     models,
  91.     knowledge,
  92.     prompts,
  93.     evaluations,
  94.     tools,
  95.     users,
  96.     utils,
  97. )
  98.  
  99. from open_webui.routers.retrieval import (
  100.     get_embedding_function,
  101.     get_ef,
  102.     get_rf,
  103. )
  104.  
  105. from open_webui.internal.db import Session, engine
  106.  
  107. from open_webui.models.functions import Functions
  108. from open_webui.models.models import Models
  109. from open_webui.models.users import UserModel, Users
  110. from open_webui.models.chats import Chats
  111.  
  112. from open_webui.config import (
  113.     LICENSE_KEY,
  114.     # Ollama
  115.     ENABLE_OLLAMA_API,
  116.     OLLAMA_BASE_URLS,
  117.     OLLAMA_API_CONFIGS,
  118.     # OpenAI
  119.     ENABLE_OPENAI_API,
  120.     ONEDRIVE_CLIENT_ID,
  121.     OPENAI_API_BASE_URLS,
  122.     OPENAI_API_KEYS,
  123.     OPENAI_API_CONFIGS,
  124.     # Direct Connections
  125.     ENABLE_DIRECT_CONNECTIONS,
  126.     # Tool Server Configs
  127.     TOOL_SERVER_CONNECTIONS,
  128.     # Code Execution
  129.     ENABLE_CODE_EXECUTION,
  130.     CODE_EXECUTION_ENGINE,
  131.     CODE_EXECUTION_JUPYTER_URL,
  132.     CODE_EXECUTION_JUPYTER_AUTH,
  133.     CODE_EXECUTION_JUPYTER_AUTH_TOKEN,
  134.     CODE_EXECUTION_JUPYTER_AUTH_PASSWORD,
  135.     CODE_EXECUTION_JUPYTER_TIMEOUT,
  136.     ENABLE_CODE_INTERPRETER,
  137.     CODE_INTERPRETER_ENGINE,
  138.     CODE_INTERPRETER_PROMPT_TEMPLATE,
  139.     CODE_INTERPRETER_JUPYTER_URL,
  140.     CODE_INTERPRETER_JUPYTER_AUTH,
  141.     CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
  142.     CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
  143.     CODE_INTERPRETER_JUPYTER_TIMEOUT,
  144.     # Image
  145.     AUTOMATIC1111_API_AUTH,
  146.     AUTOMATIC1111_BASE_URL,
  147.     AUTOMATIC1111_CFG_SCALE,
  148.     AUTOMATIC1111_SAMPLER,
  149.     AUTOMATIC1111_SCHEDULER,
  150.     COMFYUI_BASE_URL,
  151.     COMFYUI_API_KEY,
  152.     COMFYUI_WORKFLOW,
  153.     COMFYUI_WORKFLOW_NODES,
  154.     ENABLE_IMAGE_GENERATION,
  155.     ENABLE_IMAGE_PROMPT_GENERATION,
  156.     IMAGE_GENERATION_ENGINE,
  157.     IMAGE_GENERATION_MODEL,
  158.     IMAGE_SIZE,
  159.     IMAGE_STEPS,
  160.     IMAGES_OPENAI_API_BASE_URL,
  161.     IMAGES_OPENAI_API_KEY,
  162.     IMAGES_GEMINI_API_BASE_URL,
  163.     IMAGES_GEMINI_API_KEY,
  164.     # Audio
  165.     AUDIO_STT_ENGINE,
  166.     AUDIO_STT_MODEL,
  167.     AUDIO_STT_OPENAI_API_BASE_URL,
  168.     AUDIO_STT_OPENAI_API_KEY,
  169.     AUDIO_STT_AZURE_API_KEY,
  170.     AUDIO_STT_AZURE_REGION,
  171.     AUDIO_STT_AZURE_LOCALES,
  172.     AUDIO_TTS_API_KEY,
  173.     AUDIO_TTS_ENGINE,
  174.     AUDIO_TTS_MODEL,
  175.     AUDIO_TTS_OPENAI_API_BASE_URL,
  176.     AUDIO_TTS_OPENAI_API_KEY,
  177.     AUDIO_TTS_SPLIT_ON,
  178.     AUDIO_TTS_VOICE,
  179.     AUDIO_TTS_AZURE_SPEECH_REGION,
  180.     AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
  181.     PLAYWRIGHT_WS_URL,
  182.     PLAYWRIGHT_TIMEOUT,
  183.     FIRECRAWL_API_BASE_URL,
  184.     FIRECRAWL_API_KEY,
  185.     WEB_LOADER_ENGINE,
  186.     WHISPER_MODEL,
  187.     WHISPER_VAD_FILTER,
  188.     DEEPGRAM_API_KEY,
  189.     WHISPER_MODEL_AUTO_UPDATE,
  190.     WHISPER_MODEL_DIR,
  191.     # Retrieval
  192.     RAG_TEMPLATE,
  193.     DEFAULT_RAG_TEMPLATE,
  194.     RAG_FULL_CONTEXT,
  195.     BYPASS_EMBEDDING_AND_RETRIEVAL,
  196.     RAG_EMBEDDING_MODEL,
  197.     RAG_EMBEDDING_MODEL_AUTO_UPDATE,
  198.     RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
  199.     RAG_RERANKING_MODEL,
  200.     RAG_RERANKING_MODEL_AUTO_UPDATE,
  201.     RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
  202.     RAG_EMBEDDING_ENGINE,
  203.     RAG_EMBEDDING_BATCH_SIZE,
  204.     RAG_RELEVANCE_THRESHOLD,
  205.     RAG_FILE_MAX_COUNT,
  206.     RAG_FILE_MAX_SIZE,
  207.     RAG_OPENAI_API_BASE_URL,
  208.     RAG_OPENAI_API_KEY,
  209.     RAG_OLLAMA_BASE_URL,
  210.     RAG_OLLAMA_API_KEY,
  211.     CHUNK_OVERLAP,
  212.     CHUNK_SIZE,
  213.     CONTENT_EXTRACTION_ENGINE,
  214.     TIKA_SERVER_URL,
  215.     DOCLING_SERVER_URL,
  216.     DOCUMENT_INTELLIGENCE_ENDPOINT,
  217.     DOCUMENT_INTELLIGENCE_KEY,
  218.     MISTRAL_OCR_API_KEY,
  219.     RAG_TOP_K,
  220.     RAG_TOP_K_RERANKER,
  221.     RAG_TEXT_SPLITTER,
  222.     TIKTOKEN_ENCODING_NAME,
  223.     PDF_EXTRACT_IMAGES,
  224.     YOUTUBE_LOADER_LANGUAGE,
  225.     YOUTUBE_LOADER_PROXY_URL,
  226.     # Retrieval (Web Search)
  227.     ENABLE_WEB_SEARCH,
  228.     WEB_SEARCH_ENGINE,
  229.     BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL,
  230.     WEB_SEARCH_RESULT_COUNT,
  231.     WEB_SEARCH_CONCURRENT_REQUESTS,
  232.     WEB_SEARCH_TRUST_ENV,
  233.     WEB_SEARCH_DOMAIN_FILTER_LIST,
  234.     JINA_API_KEY,
  235.     SEARCHAPI_API_KEY,
  236.     SEARCHAPI_ENGINE,
  237.     SERPAPI_API_KEY,
  238.     SERPAPI_ENGINE,
  239.     SEARXNG_QUERY_URL,
  240.     SERPER_API_KEY,
  241.     SERPLY_API_KEY,
  242.     SERPSTACK_API_KEY,
  243.     SERPSTACK_HTTPS,
  244.     TAVILY_API_KEY,
  245.     TAVILY_EXTRACT_DEPTH,
  246.     BING_SEARCH_V7_ENDPOINT,
  247.     BING_SEARCH_V7_SUBSCRIPTION_KEY,
  248.     BRAVE_SEARCH_API_KEY,
  249.     EXA_API_KEY,
  250.     PERPLEXITY_API_KEY,
  251.     SOUGOU_API_SID,
  252.     SOUGOU_API_SK,
  253.     KAGI_SEARCH_API_KEY,
  254.     MOJEEK_SEARCH_API_KEY,
  255.     BOCHA_SEARCH_API_KEY,
  256.     GOOGLE_PSE_API_KEY,
  257.     GOOGLE_PSE_ENGINE_ID,
  258.     GOOGLE_DRIVE_CLIENT_ID,
  259.     GOOGLE_DRIVE_API_KEY,
  260.     ONEDRIVE_CLIENT_ID,
  261.     ENABLE_RAG_HYBRID_SEARCH,
  262.     ENABLE_RAG_LOCAL_WEB_FETCH,
  263.     ENABLE_WEB_LOADER_SSL_VERIFICATION,
  264.     ENABLE_GOOGLE_DRIVE_INTEGRATION,
  265.     ENABLE_ONEDRIVE_INTEGRATION,
  266.     UPLOAD_DIR,
  267.     # WebUI
  268.     WEBUI_AUTH,
  269.     WEBUI_NAME,
  270.     WEBUI_BANNERS,
  271.     WEBHOOK_URL,
  272.     ADMIN_EMAIL,
  273.     SHOW_ADMIN_DETAILS,
  274.     JWT_EXPIRES_IN,
  275.     ENABLE_SIGNUP,
  276.     ENABLE_LOGIN_FORM,
  277.     ENABLE_API_KEY,
  278.     ENABLE_API_KEY_ENDPOINT_RESTRICTIONS,
  279.     API_KEY_ALLOWED_ENDPOINTS,
  280.     ENABLE_CHANNELS,
  281.     ENABLE_COMMUNITY_SHARING,
  282.     ENABLE_MESSAGE_RATING,
  283.     ENABLE_USER_WEBHOOKS,
  284.     ENABLE_EVALUATION_ARENA_MODELS,
  285.     USER_PERMISSIONS,
  286.     DEFAULT_USER_ROLE,
  287.     DEFAULT_PROMPT_SUGGESTIONS,
  288.     DEFAULT_MODELS,
  289.     DEFAULT_ARENA_MODEL,
  290.     MODEL_ORDER_LIST,
  291.     EVALUATION_ARENA_MODELS,
  292.     # WebUI (OAuth)
  293.     ENABLE_OAUTH_ROLE_MANAGEMENT,
  294.     OAUTH_ROLES_CLAIM,
  295.     OAUTH_EMAIL_CLAIM,
  296.     OAUTH_PICTURE_CLAIM,
  297.     OAUTH_USERNAME_CLAIM,
  298.     OAUTH_ALLOWED_ROLES,
  299.     OAUTH_ADMIN_ROLES,
  300.     # WebUI (LDAP)
  301.     ENABLE_LDAP,
  302.     LDAP_SERVER_LABEL,
  303.     LDAP_SERVER_HOST,
  304.     LDAP_SERVER_PORT,
  305.     LDAP_ATTRIBUTE_FOR_MAIL,
  306.     LDAP_ATTRIBUTE_FOR_USERNAME,
  307.     LDAP_SEARCH_FILTERS,
  308.     LDAP_SEARCH_BASE,
  309.     LDAP_APP_DN,
  310.     LDAP_APP_PASSWORD,
  311.     LDAP_USE_TLS,
  312.     LDAP_CA_CERT_FILE,
  313.     LDAP_CIPHERS,
  314.     # Misc
  315.     ENV,
  316.     CACHE_DIR,
  317.     STATIC_DIR,
  318.     FRONTEND_BUILD_DIR,
  319.     CORS_ALLOW_ORIGIN,
  320.     DEFAULT_LOCALE,
  321.     OAUTH_PROVIDERS,
  322.     WEBUI_URL,
  323.     # Admin
  324.     ENABLE_ADMIN_CHAT_ACCESS,
  325.     ENABLE_ADMIN_EXPORT,
  326.     # Tasks
  327.     TASK_MODEL,
  328.     TASK_MODEL_EXTERNAL,
  329.     ENABLE_TAGS_GENERATION,
  330.     ENABLE_TITLE_GENERATION,
  331.     ENABLE_SEARCH_QUERY_GENERATION,
  332.     ENABLE_RETRIEVAL_QUERY_GENERATION,
  333.     ENABLE_AUTOCOMPLETE_GENERATION,
  334.     TITLE_GENERATION_PROMPT_TEMPLATE,
  335.     TAGS_GENERATION_PROMPT_TEMPLATE,
  336.     IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE,
  337.     TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
  338.     QUERY_GENERATION_PROMPT_TEMPLATE,
  339.     AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE,
  340.     AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH,
  341.     AppConfig,
  342.     reset_config,
  343. )
  344. from open_webui.env import (
  345.     AUDIT_EXCLUDED_PATHS,
  346.     AUDIT_LOG_LEVEL,
  347.     CHANGELOG,
  348.     REDIS_URL,
  349.     REDIS_SENTINEL_HOSTS,
  350.     REDIS_SENTINEL_PORT,
  351.     GLOBAL_LOG_LEVEL,
  352.     MAX_BODY_LOG_SIZE,
  353.     SAFE_MODE,
  354.     SRC_LOG_LEVELS,
  355.     VERSION,
  356.     WEBUI_BUILD_HASH,
  357.     WEBUI_SECRET_KEY,
  358.     WEBUI_SESSION_COOKIE_SAME_SITE,
  359.     WEBUI_SESSION_COOKIE_SECURE,
  360.     WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
  361.     WEBUI_AUTH_TRUSTED_NAME_HEADER,
  362.     ENABLE_WEBSOCKET_SUPPORT,
  363.     BYPASS_MODEL_ACCESS_CONTROL,
  364.     RESET_CONFIG_ON_START,
  365.     OFFLINE_MODE,
  366.     ENABLE_OTEL,
  367.     EXTERNAL_PWA_MANIFEST_URL,
  368. )
  369.  
  370.  
  371. from open_webui.utils.models import (
  372.     get_all_models,
  373.     get_all_base_models,
  374.     check_model_access,
  375. )
  376. from open_webui.utils.chat import (
  377.     generate_chat_completion as chat_completion_handler,
  378.     chat_completed as chat_completed_handler,
  379.     chat_action as chat_action_handler,
  380. )
  381. from open_webui.utils.middleware import process_chat_payload, process_chat_response
  382. from open_webui.utils.access_control import has_access
  383.  
  384. from open_webui.utils.auth import (
  385.     get_license_data,
  386.     get_http_authorization_cred,
  387.     decode_token,
  388.     get_admin_user,
  389.     get_verified_user,
  390. )
  391. from open_webui.utils.oauth import OAuthManager
  392. from open_webui.utils.security_headers import SecurityHeadersMiddleware
  393.  
  394. from open_webui.tasks import (
  395.     list_task_ids_by_chat_id,
  396.     stop_task,
  397.     list_tasks,
  398. )  # Import from tasks.py
  399.  
  400. from open_webui.utils.redis import get_sentinels_from_env
  401.  
  402.  
  403. if SAFE_MODE:
  404.     print("SAFE MODE ENABLED")
  405.     Functions.deactivate_all_functions()
  406.  
  407. logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
  408. log = logging.getLogger(__name__)
  409. log.setLevel(SRC_LOG_LEVELS["MAIN"])
  410.  
  411.  
  412. class SPAStaticFiles(StaticFiles):
  413.     async def get_response(self, path: str, scope):
  414.         try:
  415.             return await super().get_response(path, scope)
  416.         except (HTTPException, StarletteHTTPException) as ex:
  417.             if ex.status_code == 404:
  418.                 if path.endswith(".js"):
  419.                     # Return 404 for javascript files
  420.                     raise ex
  421.                 else:
  422.                     return await super().get_response("index.html", scope)
  423.             else:
  424.                 raise ex
  425.  
  426.  
  427. print(
  428.     rf"""
  429. ██████╗ ██████╗ ███████╗███╗   ██╗    ██╗    ██╗███████╗██████╗ ██╗   ██╗██╗
  430. ██╔═══██╗██╔══██╗██╔════╝████╗  ██║    ██║    ██║██╔════╝██╔══██╗██║   ██║██║
  431. ██║   ██║██████╔╝█████╗  ██╔██╗ ██║    ██║ █╗ ██║█████╗  ██████╔╝██║   ██║██║
  432. ██║   ██║██╔═══╝ ██╔══╝  ██║╚██╗██║    ██║███╗██║██╔══╝  ██╔══██╗██║   ██║██║
  433. ╚██████╔╝██║     ███████╗██║ ╚████║    ╚███╔███╔╝███████╗██████╔╝╚██████╔╝██║
  434. ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═══╝     ╚══╝╚══╝ ╚══════╝╚═════╝  ╚═════╝ ╚═╝
  435.  
  436.  
  437. v{VERSION} - building the best open-source AI user interface.
  438. {f"Commit: {WEBUI_BUILD_HASH}" if WEBUI_BUILD_HASH != "dev-build" else ""}
  439. https://github.com/open-webui/open-webui
  440. """
  441. )
  442. # === Setup ===
  443. # Dynamically load BASE_DIR from base_dir.txt
  444. # This will have the path to data files like
  445. #    conversations, peronality, people etc..
  446. # I use 2 differnt computers when working on this, but my path
  447. # path is alway wrong.
  448. # get the directory where *this file* lives
  449. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  450. # now read base_dir.txt from that same folder
  451. try:
  452.     with open(os.path.join(SCRIPT_DIR, "base_dir.txt"), "r", encoding="utf-8") as f:
  453.         BASE_DIR = f.read().strip()
  454. except Exception as e:
  455.     raise RuntimeError(f"❌ Could not load base_dir.txt: {e}")
  456.  
  457. PERSONALITY_FILE = os.path.join(BASE_DIR, "personality.txt")
  458. CONVERSATION_FILE = os.path.join(BASE_DIR, "conversation.json")
  459. PEOPLE_FILE = os.path.join(BASE_DIR, "people.json")
  460. MARY_MEMORY_FILE = os.path.join(BASE_DIR, "mary_memory.json")
  461. USER_MEMORY_FILE = os.path.join(BASE_DIR, "user_memory.json")
  462.  
  463. try:
  464.     with open(USER_MEMORY_FILE, "r", encoding="utf-8") as f:
  465.         user_memory = json.load(f)
  466.         print("👥 user_memory.json loaded successfully!")        
  467. except:
  468.     user_memory = {"likes": {}, "dislikes": {}}
  469.  
  470.  
  471. try:
  472.     with open(MARY_MEMORY_FILE, "r", encoding="utf-8") as f:
  473.         mary_memory = json.load(f)
  474.         print("👥 mary_memory.json loaded successfully!")        
  475. except:
  476.     mary_memory = {"likes": {}, "dislikes": {}}
  477.  
  478. try:
  479.     with open(PEOPLE_FILE, "r", encoding="utf-8") as f:
  480.         people = json.load(f)
  481.         print("👥 people.json loaded successfully!")
  482. except Exception as e:
  483.     people = {}
  484.     print(f"⚠️ Could not load people.json: {e}")
  485.    
  486.    
  487. try:
  488.     with open(PERSONALITY_FILE, "r", encoding="utf-8") as f:        
  489.         personality = f.read()
  490.         print(f"️\nperonality.txt Read SUCCESSFULLY!\n")
  491. except Exception as e:
  492.     personality = "You are Richards girlfriend."
  493.     print(f"⚠️ Could not load personality.txt: {e}")
  494.  
  495. @asynccontextmanager
  496. async def lifespan(app: FastAPI):
  497.     start_logger()
  498.     if RESET_CONFIG_ON_START:
  499.         reset_config()
  500.  
  501.     if LICENSE_KEY:
  502.         get_license_data(app, LICENSE_KEY)
  503.  
  504.     asyncio.create_task(periodic_usage_pool_cleanup())
  505.     yield
  506.  
  507.  
  508. app = FastAPI(
  509.     title="Open WebUI",
  510.     docs_url="/docs" if ENV == "dev" else None,
  511.     openapi_url="/openapi.json" if ENV == "dev" else None,
  512.     redoc_url=None,
  513.     lifespan=lifespan,
  514. )
  515.  
  516. oauth_manager = OAuthManager(app)
  517.  
  518. app.state.config = AppConfig(
  519.     redis_url=REDIS_URL,
  520.     redis_sentinels=get_sentinels_from_env(REDIS_SENTINEL_HOSTS, REDIS_SENTINEL_PORT),
  521. )
  522.  
  523. app.state.WEBUI_NAME = WEBUI_NAME
  524. app.state.LICENSE_METADATA = None
  525.  
  526.  
  527. ########################################
  528. #
  529. # OPENTELEMETRY
  530. #
  531. ########################################
  532.  
  533. if ENABLE_OTEL:
  534.     from open_webui.utils.telemetry.setup import setup as setup_opentelemetry
  535.  
  536.     setup_opentelemetry(app=app, db_engine=engine)
  537.  
  538.  
  539. ########################################
  540. #
  541. # OLLAMA
  542. #
  543. ########################################
  544.  
  545.  
  546. app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
  547. app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
  548. app.state.config.OLLAMA_API_CONFIGS = OLLAMA_API_CONFIGS
  549.  
  550. app.state.OLLAMA_MODELS = {}
  551.  
  552. ########################################
  553. #
  554. # OPENAI
  555. #
  556. ########################################
  557.  
  558. app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
  559. app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
  560. app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
  561. app.state.config.OPENAI_API_CONFIGS = OPENAI_API_CONFIGS
  562.  
  563. app.state.OPENAI_MODELS = {}
  564.  
  565. ########################################
  566. #
  567. # TOOL SERVERS
  568. #
  569. ########################################
  570.  
  571. app.state.config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS
  572. app.state.TOOL_SERVERS = []
  573.  
  574. ########################################
  575. #
  576. # DIRECT CONNECTIONS
  577. #
  578. ########################################
  579.  
  580. app.state.config.ENABLE_DIRECT_CONNECTIONS = ENABLE_DIRECT_CONNECTIONS
  581.  
  582. ########################################
  583. #
  584. # WEBUI
  585. #
  586. ########################################
  587.  
  588. app.state.config.WEBUI_URL = WEBUI_URL
  589. app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
  590. app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM
  591.  
  592. app.state.config.ENABLE_API_KEY = ENABLE_API_KEY
  593. app.state.config.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS = (
  594.     ENABLE_API_KEY_ENDPOINT_RESTRICTIONS
  595. )
  596. app.state.config.API_KEY_ALLOWED_ENDPOINTS = API_KEY_ALLOWED_ENDPOINTS
  597.  
  598. app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
  599.  
  600. app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS
  601. app.state.config.ADMIN_EMAIL = ADMIN_EMAIL
  602.  
  603.  
  604. app.state.config.DEFAULT_MODELS = DEFAULT_MODELS
  605. app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
  606. app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
  607.  
  608. app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
  609. app.state.config.WEBHOOK_URL = WEBHOOK_URL
  610. app.state.config.BANNERS = WEBUI_BANNERS
  611. app.state.config.MODEL_ORDER_LIST = MODEL_ORDER_LIST
  612.  
  613.  
  614. app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS
  615. app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
  616. app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
  617. app.state.config.ENABLE_USER_WEBHOOKS = ENABLE_USER_WEBHOOKS
  618.  
  619. app.state.config.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS
  620. app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS
  621.  
  622. app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM
  623. app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM
  624. app.state.config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM
  625.  
  626. app.state.config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT
  627. app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
  628. app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
  629. app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
  630.  
  631. app.state.config.ENABLE_LDAP = ENABLE_LDAP
  632. app.state.config.LDAP_SERVER_LABEL = LDAP_SERVER_LABEL
  633. app.state.config.LDAP_SERVER_HOST = LDAP_SERVER_HOST
  634. app.state.config.LDAP_SERVER_PORT = LDAP_SERVER_PORT
  635. app.state.config.LDAP_ATTRIBUTE_FOR_MAIL = LDAP_ATTRIBUTE_FOR_MAIL
  636. app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME = LDAP_ATTRIBUTE_FOR_USERNAME
  637. app.state.config.LDAP_APP_DN = LDAP_APP_DN
  638. app.state.config.LDAP_APP_PASSWORD = LDAP_APP_PASSWORD
  639. app.state.config.LDAP_SEARCH_BASE = LDAP_SEARCH_BASE
  640. app.state.config.LDAP_SEARCH_FILTERS = LDAP_SEARCH_FILTERS
  641. app.state.config.LDAP_USE_TLS = LDAP_USE_TLS
  642. app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE
  643. app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
  644.  
  645.  
  646. app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
  647. app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
  648. app.state.EXTERNAL_PWA_MANIFEST_URL = EXTERNAL_PWA_MANIFEST_URL
  649.  
  650. app.state.USER_COUNT = None
  651. app.state.TOOLS = {}
  652. app.state.FUNCTIONS = {}
  653.  
  654. ########################################
  655. #
  656. # RETRIEVAL
  657. #
  658. ########################################
  659.  
  660.  
  661. app.state.config.TOP_K = RAG_TOP_K
  662. app.state.config.TOP_K_RERANKER = RAG_TOP_K_RERANKER
  663. app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
  664. app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
  665. app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
  666.  
  667.  
  668. app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
  669. app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL = BYPASS_EMBEDDING_AND_RETRIEVAL
  670. app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
  671. app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION = ENABLE_WEB_LOADER_SSL_VERIFICATION
  672.  
  673. app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE
  674. app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
  675. app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
  676. app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT = DOCUMENT_INTELLIGENCE_ENDPOINT
  677. app.state.config.DOCUMENT_INTELLIGENCE_KEY = DOCUMENT_INTELLIGENCE_KEY
  678. app.state.config.MISTRAL_OCR_API_KEY = MISTRAL_OCR_API_KEY
  679.  
  680. app.state.config.TEXT_SPLITTER = RAG_TEXT_SPLITTER
  681. app.state.config.TIKTOKEN_ENCODING_NAME = TIKTOKEN_ENCODING_NAME
  682.  
  683. app.state.config.CHUNK_SIZE = CHUNK_SIZE
  684. app.state.config.CHUNK_OVERLAP = CHUNK_OVERLAP
  685.  
  686. app.state.config.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
  687. app.state.config.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
  688. app.state.config.RAG_EMBEDDING_BATCH_SIZE = RAG_EMBEDDING_BATCH_SIZE
  689. app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL
  690. app.state.config.RAG_TEMPLATE = RAG_TEMPLATE
  691.  
  692. app.state.config.RAG_OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
  693. app.state.config.RAG_OPENAI_API_KEY = RAG_OPENAI_API_KEY
  694.  
  695. app.state.config.RAG_OLLAMA_BASE_URL = RAG_OLLAMA_BASE_URL
  696. app.state.config.RAG_OLLAMA_API_KEY = RAG_OLLAMA_API_KEY
  697.  
  698. app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
  699.  
  700. app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
  701. app.state.config.YOUTUBE_LOADER_PROXY_URL = YOUTUBE_LOADER_PROXY_URL
  702.  
  703.  
  704. app.state.config.ENABLE_WEB_SEARCH = ENABLE_WEB_SEARCH
  705. app.state.config.WEB_SEARCH_ENGINE = WEB_SEARCH_ENGINE
  706. app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST = WEB_SEARCH_DOMAIN_FILTER_LIST
  707. app.state.config.WEB_SEARCH_RESULT_COUNT = WEB_SEARCH_RESULT_COUNT
  708. app.state.config.WEB_SEARCH_CONCURRENT_REQUESTS = WEB_SEARCH_CONCURRENT_REQUESTS
  709. app.state.config.WEB_LOADER_ENGINE = WEB_LOADER_ENGINE
  710. app.state.config.WEB_SEARCH_TRUST_ENV = WEB_SEARCH_TRUST_ENV
  711. app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = (
  712.     BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
  713. )
  714.  
  715. app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION
  716. app.state.config.ENABLE_ONEDRIVE_INTEGRATION = ENABLE_ONEDRIVE_INTEGRATION
  717. app.state.config.SEARXNG_QUERY_URL = SEARXNG_QUERY_URL
  718. app.state.config.GOOGLE_PSE_API_KEY = GOOGLE_PSE_API_KEY
  719. app.state.config.GOOGLE_PSE_ENGINE_ID = GOOGLE_PSE_ENGINE_ID
  720. app.state.config.BRAVE_SEARCH_API_KEY = BRAVE_SEARCH_API_KEY
  721. app.state.config.KAGI_SEARCH_API_KEY = KAGI_SEARCH_API_KEY
  722. app.state.config.MOJEEK_SEARCH_API_KEY = MOJEEK_SEARCH_API_KEY
  723. app.state.config.BOCHA_SEARCH_API_KEY = BOCHA_SEARCH_API_KEY
  724. app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY
  725. app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS
  726. app.state.config.SERPER_API_KEY = SERPER_API_KEY
  727. app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
  728. app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
  729. app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY
  730. app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
  731. app.state.config.SERPAPI_API_KEY = SERPAPI_API_KEY
  732. app.state.config.SERPAPI_ENGINE = SERPAPI_ENGINE
  733. app.state.config.JINA_API_KEY = JINA_API_KEY
  734. app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
  735. app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
  736. app.state.config.EXA_API_KEY = EXA_API_KEY
  737. app.state.config.PERPLEXITY_API_KEY = PERPLEXITY_API_KEY
  738. app.state.config.SOUGOU_API_SID = SOUGOU_API_SID
  739. app.state.config.SOUGOU_API_SK = SOUGOU_API_SK
  740.  
  741.  
  742. app.state.config.PLAYWRIGHT_WS_URL = PLAYWRIGHT_WS_URL
  743. app.state.config.PLAYWRIGHT_TIMEOUT = PLAYWRIGHT_TIMEOUT
  744. app.state.config.FIRECRAWL_API_BASE_URL = FIRECRAWL_API_BASE_URL
  745. app.state.config.FIRECRAWL_API_KEY = FIRECRAWL_API_KEY
  746. app.state.config.TAVILY_EXTRACT_DEPTH = TAVILY_EXTRACT_DEPTH
  747.  
  748. app.state.EMBEDDING_FUNCTION = None
  749. app.state.ef = None
  750. app.state.rf = None
  751.  
  752. app.state.YOUTUBE_LOADER_TRANSLATION = None
  753.  
  754.  
  755. try:
  756.     app.state.ef = get_ef(
  757.         app.state.config.RAG_EMBEDDING_ENGINE,
  758.         app.state.config.RAG_EMBEDDING_MODEL,
  759.         RAG_EMBEDDING_MODEL_AUTO_UPDATE,
  760.     )
  761.  
  762.     app.state.rf = get_rf(
  763.         app.state.config.RAG_RERANKING_MODEL,
  764.         RAG_RERANKING_MODEL_AUTO_UPDATE,
  765.     )
  766. except Exception as e:
  767.     log.error(f"Error updating models: {e}")
  768.     pass
  769.  
  770.  
  771. app.state.EMBEDDING_FUNCTION = get_embedding_function(
  772.     app.state.config.RAG_EMBEDDING_ENGINE,
  773.     app.state.config.RAG_EMBEDDING_MODEL,
  774.     app.state.ef,
  775.     (
  776.         app.state.config.RAG_OPENAI_API_BASE_URL
  777.         if app.state.config.RAG_EMBEDDING_ENGINE == "openai"
  778.         else app.state.config.RAG_OLLAMA_BASE_URL
  779.     ),
  780.     (
  781.         app.state.config.RAG_OPENAI_API_KEY
  782.         if app.state.config.RAG_EMBEDDING_ENGINE == "openai"
  783.         else app.state.config.RAG_OLLAMA_API_KEY
  784.     ),
  785.     app.state.config.RAG_EMBEDDING_BATCH_SIZE,
  786. )
  787.  
  788. ########################################
  789. #
  790. # CODE EXECUTION
  791. #
  792. ########################################
  793.  
  794. app.state.config.ENABLE_CODE_EXECUTION = ENABLE_CODE_EXECUTION
  795. app.state.config.CODE_EXECUTION_ENGINE = CODE_EXECUTION_ENGINE
  796. app.state.config.CODE_EXECUTION_JUPYTER_URL = CODE_EXECUTION_JUPYTER_URL
  797. app.state.config.CODE_EXECUTION_JUPYTER_AUTH = CODE_EXECUTION_JUPYTER_AUTH
  798. app.state.config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN = CODE_EXECUTION_JUPYTER_AUTH_TOKEN
  799. app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = (
  800.     CODE_EXECUTION_JUPYTER_AUTH_PASSWORD
  801. )
  802. app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT = CODE_EXECUTION_JUPYTER_TIMEOUT
  803.  
  804. app.state.config.ENABLE_CODE_INTERPRETER = ENABLE_CODE_INTERPRETER
  805. app.state.config.CODE_INTERPRETER_ENGINE = CODE_INTERPRETER_ENGINE
  806. app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE = CODE_INTERPRETER_PROMPT_TEMPLATE
  807.  
  808. app.state.config.CODE_INTERPRETER_JUPYTER_URL = CODE_INTERPRETER_JUPYTER_URL
  809. app.state.config.CODE_INTERPRETER_JUPYTER_AUTH = CODE_INTERPRETER_JUPYTER_AUTH
  810. app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = (
  811.     CODE_INTERPRETER_JUPYTER_AUTH_TOKEN
  812. )
  813. app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = (
  814.     CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD
  815. )
  816. app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = CODE_INTERPRETER_JUPYTER_TIMEOUT
  817.  
  818. ########################################
  819. #
  820. # IMAGES
  821. #
  822. ########################################
  823.  
  824. app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE
  825. app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION
  826. app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
  827.  
  828. app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
  829. app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
  830.  
  831. app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL
  832. app.state.config.IMAGES_GEMINI_API_KEY = IMAGES_GEMINI_API_KEY
  833.  
  834. app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
  835.  
  836. app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
  837. app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
  838. app.state.config.AUTOMATIC1111_CFG_SCALE = AUTOMATIC1111_CFG_SCALE
  839. app.state.config.AUTOMATIC1111_SAMPLER = AUTOMATIC1111_SAMPLER
  840. app.state.config.AUTOMATIC1111_SCHEDULER = AUTOMATIC1111_SCHEDULER
  841. app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
  842. app.state.config.COMFYUI_API_KEY = COMFYUI_API_KEY
  843. app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW
  844. app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES
  845.  
  846. app.state.config.IMAGE_SIZE = IMAGE_SIZE
  847. app.state.config.IMAGE_STEPS = IMAGE_STEPS
  848.  
  849.  
  850. ########################################
  851. #
  852. # AUDIO
  853. #
  854. ########################################
  855.  
  856. app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
  857. app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
  858. app.state.config.STT_ENGINE = AUDIO_STT_ENGINE
  859. app.state.config.STT_MODEL = AUDIO_STT_MODEL
  860.  
  861. app.state.config.WHISPER_MODEL = WHISPER_MODEL
  862. app.state.config.WHISPER_VAD_FILTER = WHISPER_VAD_FILTER
  863. app.state.config.DEEPGRAM_API_KEY = DEEPGRAM_API_KEY
  864.  
  865. app.state.config.AUDIO_STT_AZURE_API_KEY = AUDIO_STT_AZURE_API_KEY
  866. app.state.config.AUDIO_STT_AZURE_REGION = AUDIO_STT_AZURE_REGION
  867. app.state.config.AUDIO_STT_AZURE_LOCALES = AUDIO_STT_AZURE_LOCALES
  868.  
  869. app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
  870. app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
  871. app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
  872. app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
  873. app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
  874. app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
  875. app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
  876.  
  877.  
  878. app.state.config.TTS_AZURE_SPEECH_REGION = AUDIO_TTS_AZURE_SPEECH_REGION
  879. app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT
  880.  
  881.  
  882. app.state.faster_whisper_model = None
  883. app.state.speech_synthesiser = None
  884. app.state.speech_speaker_embeddings_dataset = None
  885.  
  886.  
  887. ########################################
  888. #
  889. # TASKS
  890. #
  891. ########################################
  892.  
  893.  
  894. app.state.config.TASK_MODEL = TASK_MODEL
  895. app.state.config.TASK_MODEL_EXTERNAL = TASK_MODEL_EXTERNAL
  896.  
  897.  
  898. app.state.config.ENABLE_SEARCH_QUERY_GENERATION = ENABLE_SEARCH_QUERY_GENERATION
  899. app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION = ENABLE_RETRIEVAL_QUERY_GENERATION
  900. app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = ENABLE_AUTOCOMPLETE_GENERATION
  901. app.state.config.ENABLE_TAGS_GENERATION = ENABLE_TAGS_GENERATION
  902. app.state.config.ENABLE_TITLE_GENERATION = ENABLE_TITLE_GENERATION
  903.  
  904.  
  905. app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE = TITLE_GENERATION_PROMPT_TEMPLATE
  906. app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE = TAGS_GENERATION_PROMPT_TEMPLATE
  907. app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = (
  908.     IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE
  909. )
  910.  
  911. app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
  912.     TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
  913. )
  914. app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE = QUERY_GENERATION_PROMPT_TEMPLATE
  915. app.state.config.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = (
  916.     AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE
  917. )
  918. app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = (
  919.     AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH
  920. )
  921.  
  922.  
  923. ########################################
  924. #
  925. # WEBUI
  926. #
  927. ########################################
  928.  
  929. app.state.MODELS = {}
  930.  
  931.  
  932. class RedirectMiddleware(BaseHTTPMiddleware):
  933.     async def dispatch(self, request: Request, call_next):
  934.         # Check if the request is a GET request
  935.         if request.method == "GET":
  936.             path = request.url.path
  937.             query_params = dict(parse_qs(urlparse(str(request.url)).query))
  938.  
  939.             # Check for the specific watch path and the presence of 'v' parameter
  940.             if path.endswith("/watch") and "v" in query_params:
  941.                 video_id = query_params["v"][0]  # Extract the first 'v' parameter
  942.                 encoded_video_id = urlencode({"youtube": video_id})
  943.                 redirect_url = f"/?{encoded_video_id}"
  944.                 return RedirectResponse(url=redirect_url)
  945.  
  946.         # Proceed with the normal flow of other requests
  947.         response = await call_next(request)
  948.         return response
  949.  
  950.  
  951. # Add the middleware to the app
  952. app.add_middleware(RedirectMiddleware)
  953. app.add_middleware(SecurityHeadersMiddleware)
  954.  
  955. def log_conversation(user_msg: str, ai_msg: str):
  956.     print(f"📝 Logging: user='{user_msg[:30]}', ai='{ai_msg[:30]}'")
  957.  
  958.     entry = {
  959.         "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  960.         "user": trim(user_msg.strip(), 500),
  961.         "ai": trim(ai_msg.strip(), 500),
  962.     }
  963.  
  964.     data = []
  965.     if os.path.exists(CONVERSATION_FILE):
  966.         try:
  967.             with open(CONVERSATION_FILE, "r", encoding="utf-8") as f:
  968.                 data = json.load(f)
  969.         except Exception as e:
  970.             print(f"⚠️ Could not read conversation.json: {e}")
  971.  
  972.     data.append(entry)
  973.  
  974.     try:
  975.         with open(CONVERSATION_FILE, "w", encoding="utf-8") as f:
  976.             json.dump(data, f, indent=2, ensure_ascii=False)
  977.             print("✅ conversation.json updated!")
  978.     except Exception as e:
  979.         print(f"⚠️ Could not write to conversation.json: {e}")
  980.  
  981. def trim(text: str, limit: int = 250) -> str:
  982.     return text if len(text) <= limit else text[:limit].rstrip() + "..."
  983.    
  984. def get_recent_convo_for_prompt(count=5):
  985.     entries = pull_conversation(count)
  986.     lines = []
  987.     for e in entries:
  988.         user = trim(e.get("user", "").replace('"', "'").replace("\r", "").replace("\t", "    "), 250)
  989.         ai = trim(e.get("ai", "").replace('"', "'").replace("\r", "").replace("\t", "    "), 250)
  990.  
  991.         # optional: cut "Mary Redshire:" from memory
  992.         if ai.lower().startswith("mary redshire:"):
  993.             ai = ai[len("mary redshire:"):].lstrip(" :\n*-")
  994.  
  995.         lines.append(f"User: {user}\nAI: {ai}")
  996.     return "\n\n".join(lines)
  997.  
  998. def pull_conversation(count: int = 5):
  999.     if not os.path.exists(CONVERSATION_FILE):
  1000.         return []
  1001.  
  1002.     try:
  1003.         with open(CONVERSATION_FILE, "r", encoding="utf-8") as f:
  1004.             data = json.load(f)
  1005.             return data[-count:]  # return last X entries
  1006.     except Exception as e:
  1007.         print(f"⚠️ Failed to load conversation history: {e}")
  1008.         return []
  1009.        
  1010. def remember_person(name: str, info: dict):
  1011.     people[name] = info
  1012.     try:
  1013.         with open(PEOPLE_FILE, "w", encoding="utf-8") as f:
  1014.             json.dump(people, f, indent=2)
  1015.             print(f"✅ Remembered {name}")
  1016.     except Exception as e:
  1017.         print(f"⚠️ Could not update people.json: {e}")
  1018.        
  1019. def remember_like(category: str, item: str):
  1020.     global mary_memory
  1021.     if category not in mary_memory["likes"]:
  1022.         mary_memory["likes"][category] = []
  1023.     if item not in mary_memory["likes"][category]:
  1024.         mary_memory["likes"][category].append(item)
  1025.  
  1026.     try:
  1027.         with open(MARY_MEMORY_FILE, "w", encoding="utf-8") as f:
  1028.             json.dump(mary_memory, f, indent=2)
  1029.             print(f"✅ Logged Mary's like → {category}: {item}")
  1030.     except Exception as e:
  1031.         print(f"⚠️ Error saving Mary's like: {e}")
  1032.  
  1033.  
  1034. def remember_dislike(category: str, item: str):
  1035.     global mary_memory
  1036.     if category not in mary_memory["dislikes"]:
  1037.         mary_memory["dislikes"][category] = []
  1038.     if item not in mary_memory["dislikes"][category]:
  1039.         mary_memory["dislikes"][category].append(item)
  1040.  
  1041.     try:
  1042.         with open(MARY_MEMORY_FILE, "w", encoding="utf-8") as f:
  1043.             json.dump(mary_memory, f, indent=2)
  1044.             print(f"✅ Logged Mary's dislike → {category}: {item}")
  1045.     except Exception as e:
  1046.         print(f"⚠️ Error saving Mary's dislike: {e}")
  1047.        
  1048. def remember_user_like(category: str, item: str):
  1049.     global user_memory
  1050.     if category not in user_memory["likes"]:
  1051.         user_memory["likes"][category] = []
  1052.     if item not in user_memory["likes"][category]:
  1053.         user_memory["likes"][category].append(item)
  1054.  
  1055.     try:
  1056.         with open(USER_MEMORY_FILE, "w", encoding="utf-8") as f:
  1057.             json.dump(user_memory, f, indent=2)
  1058.             print(f"✅ Logged your like → {category}: {item}")
  1059.     except Exception as e:
  1060.         print(f"⚠️ Error saving your like: {e}")
  1061.  
  1062.  
  1063. def remember_user_dislike(category: str, item: str):
  1064.     global user_memory
  1065.     if category not in user_memory["dislikes"]:
  1066.         user_memory["dislikes"][category] = []
  1067.     if item not in user_memory["dislikes"][category]:
  1068.         user_memory["dislikes"][category].append(item)
  1069.  
  1070.     try:
  1071.         with open(USER_MEMORY_FILE, "w", encoding="utf-8") as f:
  1072.             json.dump(user_memory, f, indent=2)
  1073.             print(f"✅ Logged your dislike → {category}: {item}")
  1074.     except Exception as e:
  1075.         print(f"⚠️ Error saving your dislike: {e}")
  1076.        
  1077.  
  1078.  
  1079. @app.middleware("http")
  1080. async def commit_session_after_request(request: Request, call_next):
  1081.     response = await call_next(request)
  1082.     # log.debug("Commit session after request")
  1083.     Session.commit()
  1084.     return response
  1085.  
  1086.  
  1087. @app.middleware("http")
  1088. async def check_url(request: Request, call_next):
  1089.     start_time = int(time.time())
  1090.     request.state.token = get_http_authorization_cred(
  1091.         request.headers.get("Authorization")
  1092.     )
  1093.  
  1094.     request.state.enable_api_key = app.state.config.ENABLE_API_KEY
  1095.     response = await call_next(request)
  1096.     process_time = int(time.time()) - start_time
  1097.     response.headers["X-Process-Time"] = str(process_time)
  1098.     return response
  1099.  
  1100.  
  1101. @app.middleware("http")
  1102. async def inspect_websocket(request: Request, call_next):
  1103.     if (
  1104.         "/ws/socket.io" in request.url.path
  1105.         and request.query_params.get("transport") == "websocket"
  1106.     ):
  1107.         upgrade = (request.headers.get("Upgrade") or "").lower()
  1108.         connection = (request.headers.get("Connection") or "").lower().split(",")
  1109.         # Check that there's the correct headers for an upgrade, else reject the connection
  1110.         # This is to work around this upstream issue: https://github.com/miguelgrinberg/python-engineio/issues/367
  1111.         if upgrade != "websocket" or "upgrade" not in connection:
  1112.             return JSONResponse(
  1113.                 status_code=status.HTTP_400_BAD_REQUEST,
  1114.                 content={"detail": "Invalid WebSocket upgrade request"},
  1115.             )
  1116.     return await call_next(request)
  1117.  
  1118.  
  1119. app.add_middleware(
  1120.     CORSMiddleware,
  1121.     allow_origins=CORS_ALLOW_ORIGIN,
  1122.     allow_credentials=True,
  1123.     allow_methods=["*"],
  1124.     allow_headers=["*"],
  1125. )
  1126.  
  1127.  
  1128. app.mount("/ws", socket_app)
  1129.  
  1130.  
  1131. app.include_router(ollama.router, prefix="/ollama", tags=["ollama"])
  1132. app.include_router(openai.router, prefix="/openai", tags=["openai"])
  1133.  
  1134.  
  1135. app.include_router(pipelines.router, prefix="/api/v1/pipelines", tags=["pipelines"])
  1136. app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
  1137. app.include_router(images.router, prefix="/api/v1/images", tags=["images"])
  1138.  
  1139. app.include_router(audio.router, prefix="/api/v1/audio", tags=["audio"])
  1140. app.include_router(retrieval.router, prefix="/api/v1/retrieval", tags=["retrieval"])
  1141.  
  1142. app.include_router(configs.router, prefix="/api/v1/configs", tags=["configs"])
  1143.  
  1144. app.include_router(auths.router, prefix="/api/v1/auths", tags=["auths"])
  1145. app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
  1146.  
  1147.  
  1148. app.include_router(channels.router, prefix="/api/v1/channels", tags=["channels"])
  1149. app.include_router(chats.router, prefix="/api/v1/chats", tags=["chats"])
  1150.  
  1151. app.include_router(models.router, prefix="/api/v1/models", tags=["models"])
  1152. app.include_router(knowledge.router, prefix="/api/v1/knowledge", tags=["knowledge"])
  1153. app.include_router(prompts.router, prefix="/api/v1/prompts", tags=["prompts"])
  1154. app.include_router(tools.router, prefix="/api/v1/tools", tags=["tools"])
  1155.  
  1156. app.include_router(memories.router, prefix="/api/v1/memories", tags=["memories"])
  1157. app.include_router(folders.router, prefix="/api/v1/folders", tags=["folders"])
  1158. app.include_router(groups.router, prefix="/api/v1/groups", tags=["groups"])
  1159. app.include_router(files.router, prefix="/api/v1/files", tags=["files"])
  1160. app.include_router(functions.router, prefix="/api/v1/functions", tags=["functions"])
  1161. app.include_router(
  1162.     evaluations.router, prefix="/api/v1/evaluations", tags=["evaluations"]
  1163. )
  1164. app.include_router(utils.router, prefix="/api/v1/utils", tags=["utils"])
  1165.  
  1166.  
  1167. try:
  1168.     audit_level = AuditLevel(AUDIT_LOG_LEVEL)
  1169. except ValueError as e:
  1170.     logger.error(f"Invalid audit level: {AUDIT_LOG_LEVEL}. Error: {e}")
  1171.     audit_level = AuditLevel.NONE
  1172.  
  1173. if audit_level != AuditLevel.NONE:
  1174.     app.add_middleware(
  1175.         AuditLoggingMiddleware,
  1176.         audit_level=audit_level,
  1177.         excluded_paths=AUDIT_EXCLUDED_PATHS,
  1178.         max_body_size=MAX_BODY_LOG_SIZE,
  1179.     )
  1180. ##################################
  1181. #
  1182. # Chat Endpoints
  1183. #
  1184. ##################################
  1185.  
  1186.  
  1187. @app.get("/api/models")
  1188. async def get_models(request: Request, user=Depends(get_verified_user)):
  1189.     def get_filtered_models(models, user):
  1190.         filtered_models = []
  1191.         for model in models:
  1192.             if model.get("arena"):
  1193.                 if has_access(
  1194.                     user.id,
  1195.                     type="read",
  1196.                     access_control=model.get("info", {})
  1197.                     .get("meta", {})
  1198.                     .get("access_control", {}),
  1199.                 ):
  1200.                     filtered_models.append(model)
  1201.                 continue
  1202.  
  1203.             model_info = Models.get_model_by_id(model["id"])
  1204.             if model_info:
  1205.                 if user.id == model_info.user_id or has_access(
  1206.                     user.id, type="read", access_control=model_info.access_control
  1207.                 ):
  1208.                     filtered_models.append(model)
  1209.  
  1210.         return filtered_models
  1211.  
  1212.     all_models = await get_all_models(request, user=user)
  1213.  
  1214.     models = []
  1215.     for model in all_models:
  1216.         # Filter out filter pipelines
  1217.         if "pipeline" in model and model["pipeline"].get("type", None) == "filter":
  1218.             continue
  1219.  
  1220.         try:
  1221.             model_tags = [
  1222.                 tag.get("name")
  1223.                 for tag in model.get("info", {}).get("meta", {}).get("tags", [])
  1224.             ]
  1225.             tags = [tag.get("name") for tag in model.get("tags", [])]
  1226.  
  1227.             tags = list(set(model_tags + tags))
  1228.             model["tags"] = [{"name": tag} for tag in tags]
  1229.         except Exception as e:
  1230.             log.debug(f"Error processing model tags: {e}")
  1231.             model["tags"] = []
  1232.             pass
  1233.  
  1234.         models.append(model)
  1235.  
  1236.     model_order_list = request.app.state.config.MODEL_ORDER_LIST
  1237.     if model_order_list:
  1238.         model_order_dict = {model_id: i for i, model_id in enumerate(model_order_list)}
  1239.         # Sort models by order list priority, with fallback for those not in the list
  1240.         models.sort(
  1241.             key=lambda x: (model_order_dict.get(x["id"], float("inf")), x["name"])
  1242.         )
  1243.  
  1244.     # Filter out models that the user does not have access to
  1245.     if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
  1246.         models = get_filtered_models(models, user)
  1247.  
  1248.     log.debug(
  1249.         f"/api/models returned filtered models accessible to the user: {json.dumps([model['id'] for model in models])}"
  1250.     )
  1251.     return {"data": models}
  1252.  
  1253.  
  1254. @app.get("/api/models/base")
  1255. async def get_base_models(request: Request, user=Depends(get_admin_user)):
  1256.     models = await get_all_base_models(request, user=user)
  1257.     return {"data": models}
  1258.  
  1259.  
  1260. @app.post("/api/chat/completions")
  1261. async def chat_completion(
  1262.     request: Request,
  1263.     form_data: dict,
  1264.     user=Depends(get_verified_user),
  1265. ):
  1266.     if not request.app.state.MODELS:
  1267.         await get_all_models(request, user=user)
  1268.  
  1269.     model_item = form_data.pop("model_item", {})
  1270.     tasks = form_data.pop("background_tasks", None)
  1271.  
  1272.     metadata = {}
  1273.     try:
  1274.         if not model_item.get("direct", False):
  1275.             model_id = form_data.get("model", None)
  1276.             if model_id not in request.app.state.MODELS:
  1277.                 raise Exception("Model not found")
  1278.  
  1279.             model = request.app.state.MODELS[model_id]
  1280.             model_info = Models.get_model_by_id(model_id)
  1281.  
  1282.             if not BYPASS_MODEL_ACCESS_CONTROL and user.role == "user":
  1283.                 check_model_access(user, model)
  1284.         else:
  1285.             model = model_item
  1286.             model_info = None
  1287.             request.state.direct = True
  1288.             request.state.model = model
  1289.  
  1290.         metadata = {
  1291.             "user_id": user.id,
  1292.             "chat_id": form_data.pop("chat_id", None),
  1293.             "message_id": form_data.pop("id", None),
  1294.             "session_id": form_data.pop("session_id", None),
  1295.             "tool_ids": form_data.get("tool_ids", None),
  1296.             "tool_servers": form_data.pop("tool_servers", None),
  1297.             "files": form_data.get("files", None),
  1298.             "features": form_data.get("features", None),
  1299.             "variables": form_data.get("variables", None),
  1300.             "model": model,
  1301.             "direct": model_item.get("direct", False),
  1302.             **(
  1303.                 {"function_calling": "native"}
  1304.                 if form_data.get("params", {}).get("function_calling") == "native"
  1305.                 or (
  1306.                     model_info
  1307.                     and model_info.params.model_dump().get("function_calling") == "native"
  1308.                 )
  1309.                 else {}
  1310.             ),
  1311.         }
  1312.  
  1313.         request.state.metadata = metadata
  1314.         form_data["metadata"] = metadata
  1315.  
  1316.         form_data, metadata, events = await process_chat_payload(
  1317.             request, form_data, user, metadata, model
  1318.         )
  1319.  
  1320.     except Exception as e:
  1321.         log.debug(f"Error processing chat payload: {e}")
  1322.         if metadata.get("chat_id") and metadata.get("message_id"):
  1323.             Chats.upsert_message_to_chat_by_id_and_message_id(
  1324.                 metadata["chat_id"],
  1325.                 metadata["message_id"],
  1326.                 {"error": {"content": str(e)}},
  1327.             )
  1328.  
  1329.         raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
  1330.  
  1331.     try:
  1332.         # === SYSTEM PROMPT CONSTRUCTION ===
  1333.         recent_context = get_recent_convo_for_prompt(5)
  1334.         system_prompt = personality.strip()
  1335.  
  1336.         # → Time awareness
  1337.         local_tz = pytz.timezone("America/Chicago")
  1338.         now_dt = datetime.now(local_tz)
  1339.         current_time_str = now_dt.strftime("%A, %B %d, %Y at %I:%M %p")
  1340.         hour = now_dt.hour
  1341.         tod = "morning" if 5 <= hour < 12 else "afternoon" if hour < 17 else "evening" if hour < 21 else "night"
  1342.         day_of_week = now_dt.strftime("%A")
  1343.  
  1344.         system_prompt += f"\n\n[The current time is {current_time_str}.]"
  1345.         system_prompt += f"\n[It is {tod} on {day_of_week}. You may reference that naturally.]"
  1346.  
  1347.         # → Mary’s memory
  1348.         if mary_memory:
  1349.             memory_section = "\n\n[Mary's personal preferences:]\n"
  1350.             for pref_type in ["likes", "dislikes"]:
  1351.                 for category, items in mary_memory.get(pref_type, {}).items():
  1352.                     if items:
  1353.                         memory_section += f"Mary {pref_type} {category}: {', '.join(items)}.\n"
  1354.             system_prompt += memory_section
  1355.  
  1356.         # → User memory
  1357.         if user_memory:
  1358.             user_section = "\n\n[User's preferences:]\n"
  1359.             for pref_type in ["likes", "dislikes"]:
  1360.                 for category, items in user_memory.get(pref_type, {}).items():
  1361.                     if items:
  1362.                         user_section += f"The user {pref_type} {category}: {', '.join(items)}.\n"
  1363.             system_prompt += user_section
  1364.  
  1365.         # → Known people
  1366.         if people:
  1367.             memory_snippet = "\n\n[People you know:]\n"
  1368.             for name, info in people.items():
  1369.                 line = f"- {name}: " + ", ".join(
  1370.                     f"{k}={v}" if not isinstance(v, list) else f"{k}={'/'.join(v)}" for k, v in info.items()
  1371.                 )
  1372.                 memory_snippet += line + "\n"
  1373.             system_prompt += memory_snippet
  1374.  
  1375.         # → Chat recap
  1376.         if recent_context:
  1377.             recent_context = recent_context.strip().replace("\r", "").replace("\t", "    ")
  1378.             system_prompt += "\n\n[Here’s a recap of recent conversation:]\n" + recent_context
  1379.  
  1380.         form_data["messages"].insert(0, {
  1381.             "role": "system",
  1382.             "content": system_prompt
  1383.         })
  1384.  
  1385.         response = await chat_completion_handler(request, form_data, user)
  1386.  
  1387.         # === AUTO-LEARN MEMORY ===
  1388.         try:
  1389.             messages = form_data.get("messages", [])
  1390.             user_input = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "(no input)")
  1391.  
  1392.             import re
  1393.  
  1394.             # standard match
  1395.             match = re.search(
  1396.                 r"\b([A-Z][a-z]+)\b.*?(?:is|’s|is my|my)?\s*(\d{2})[^\.]*?\b(girlfriend|wife|friend|sister|mom|mother|ex)\b",
  1397.                 user_input, re.IGNORECASE
  1398.             )
  1399.            
  1400.             # USER likes/dislikes
  1401.             like_match = re.search(r"\bi\s+(?:really\s+)?(?:love|like)\s+(.*?)$", user_input, re.IGNORECASE)
  1402.             if like_match:
  1403.                 remember_user_like("books", like_match.group(1).strip())
  1404.  
  1405.             dislike_match = re.search(r"\bi\s+(?:really\s+)?(?:hate|dislike|can[’']?t stand|do(?:n['’]t| not) like)\s+(.*?)$", user_input, re.IGNORECASE)
  1406.  
  1407.             if dislike_match:
  1408.                 remember_user_dislike("books", dislike_match.group(1).strip())
  1409.                
  1410.            
  1411.             # MARY likes/dislikes
  1412.             mary_like = re.search(r"\bmary\s+(?:really\s+)?(?:loves?|likes?)\s+(.*?)$", user_input, re.IGNORECASE)
  1413.             if mary_like:
  1414.                 remember_like("books", mary_like.group(1).strip())
  1415.  
  1416.             mary_dislike = re.search(r"\bi\s+(?:really\s+)?(?:hate|dislike|can[’']?t stand|do(?:n['’]t| not) like)\s+(.*?)$", user_input, re.IGNORECASE)
  1417.  
  1418.             if mary_dislike:
  1419.                 remember_dislike("books", mary_dislike.group(1).strip())
  1420.                
  1421.                
  1422.             # fallback match
  1423.             if not match:
  1424.                 fallback = re.search(
  1425.                     r"\b([A-Z][a-z]+)\b.*?(?:is|was|’s|my)\s+(late\s+)?(wife|ex|girlfriend|mother|sister)\b.*",
  1426.                     user_input, re.IGNORECASE
  1427.                 )
  1428.                 if fallback:
  1429.                     name = fallback.group(1)
  1430.                     relation = fallback.group(3).lower()
  1431.                     note = user_input.strip().replace("\n", " ")
  1432.                     remember_person(name, {
  1433.                         "relationship": relation,
  1434.                         "notes": [note]
  1435.                     })
  1436.  
  1437.             if match:
  1438.                 name = match.group(1)
  1439.                 if name.lower() not in ["just", "i", "it"]:
  1440.                     age = int(match.group(2))
  1441.                     relation = match.group(3).lower()
  1442.                     sentence = user_input.strip().replace("\n", " ")
  1443.                     name_start = sentence.lower().find(name.lower())
  1444.                     notes = []
  1445.                     if name_start != -1:
  1446.                         note_fragment = sentence[name_start + len(name):].strip()
  1447.                         note_fragment = re.sub(
  1448.                             r".*?\b(?:is|’s|is my|my)\s+\d{2}[^\.]*?\b(?:girlfriend|wife|friend|sister|mom|mother|ex)\b",
  1449.                             "",
  1450.                             note_fragment,
  1451.                             flags=re.IGNORECASE
  1452.                         ).strip(" .,-")
  1453.                         if note_fragment:
  1454.                             notes.append(note_fragment)
  1455.  
  1456.                     remember_person(name, {
  1457.                         "age": age,
  1458.                         "relationship": relation,
  1459.                         "notes": notes
  1460.                     })
  1461.  
  1462.             # === HANDLE AI RESPONSE ===
  1463.             ai_output = ""
  1464.             if isinstance(response, StreamingResponse):
  1465.                 chunks, ai_parts = [], []
  1466.                 async for chunk in response.body_iterator:
  1467.                     chunk = chunk.decode("utf-8") if isinstance(chunk, bytes) else str(chunk)
  1468.                     chunks.append(chunk)
  1469.                     if not chunk.strip():
  1470.                         continue
  1471.                     if chunk.strip().startswith("data:"):
  1472.                         chunk = chunk.strip()[5:].strip()
  1473.                     try:
  1474.                         if chunk.strip() == "[DONE]":
  1475.                             continue
  1476.                         data = json.loads(chunk)
  1477.                         content_piece = data.get("choices", [{}])[0].get("delta", {}).get("content", "")
  1478.                         ai_parts.append(content_piece)
  1479.                     except Exception as e:
  1480.                         print(f"⚠️ Could not parse chunk: {repr(chunk[:50])} → {e}")
  1481.  
  1482.                 ai_output = "".join(ai_parts)
  1483.                 async def replay():
  1484.                     for chunk in chunks:
  1485.                         yield chunk.encode("utf-8")
  1486.                 response.body_iterator = replay()
  1487.             else:
  1488.                 body = await response.body()
  1489.                 ai_output = body.decode("utf-8") if isinstance(body, bytes) else str(body)
  1490.  
  1491.             log_conversation(user_input, ai_output.strip())
  1492.  
  1493.         except Exception as e:
  1494.             log_conversation(user_input, "[error extracting AI reply]")
  1495.             print(f"⚠️ Could not log conversation: {e}")
  1496.  
  1497.         return await process_chat_response(
  1498.             request, response, form_data, user, metadata, model, events, tasks
  1499.         )
  1500.  
  1501.     except Exception as e:
  1502.         raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
  1503.  
  1504.  
  1505. # Alias for chat_completion (Legacy)
  1506. generate_chat_completions = chat_completion
  1507. generate_chat_completion = chat_completion
  1508.  
  1509.  
  1510. @app.post("/api/chat/completed")
  1511. async def chat_completed(
  1512.     request: Request, form_data: dict, user=Depends(get_verified_user)
  1513. ):
  1514.     try:
  1515.         model_item = form_data.pop("model_item", {})
  1516.  
  1517.         if model_item.get("direct", False):
  1518.             request.state.direct = True
  1519.             request.state.model = model_item
  1520.  
  1521.         return await chat_completed_handler(request, form_data, user)
  1522.     except Exception as e:
  1523.         raise HTTPException(
  1524.             status_code=status.HTTP_400_BAD_REQUEST,
  1525.             detail=str(e),
  1526.         )
  1527.  
  1528.  
  1529. @app.post("/api/chat/actions/{action_id}")
  1530. async def chat_action(
  1531.     request: Request, action_id: str, form_data: dict, user=Depends(get_verified_user)
  1532. ):
  1533.     try:
  1534.         model_item = form_data.pop("model_item", {})
  1535.  
  1536.         if model_item.get("direct", False):
  1537.             request.state.direct = True
  1538.             request.state.model = model_item
  1539.  
  1540.         return await chat_action_handler(request, action_id, form_data, user)
  1541.     except Exception as e:
  1542.         raise HTTPException(
  1543.             status_code=status.HTTP_400_BAD_REQUEST,
  1544.             detail=str(e),
  1545.         )
  1546.  
  1547.  
  1548. @app.post("/api/tasks/stop/{task_id}")
  1549. async def stop_task_endpoint(task_id: str, user=Depends(get_verified_user)):
  1550.     try:
  1551.         result = await stop_task(task_id)
  1552.         return result
  1553.     except ValueError as e:
  1554.         raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
  1555.  
  1556.  
  1557. # @app.get("/api/conversation")
  1558. # def get_recent_convo_for_prompt(count=5):
  1559.     # entries = pull_conversation(count)
  1560.     # lines = []
  1561.     # for e in entries:
  1562.         # user = e.get("user", "").replace('"', "'").replace("\r", "").replace("\t", "    ")
  1563.         # ai = e.get("ai", "").replace('"', "'").replace("\r", "").replace("\t", "    ")
  1564.         # lines.append(f"User: {user}\nAI: {ai}")
  1565.     # return "\n\n".join(lines)
  1566.  
  1567. @app.get("/api/tasks")
  1568. async def list_tasks_endpoint(user=Depends(get_verified_user)):
  1569.     return {"tasks": list_tasks()}
  1570.  
  1571.  
  1572. @app.get("/api/tasks/chat/{chat_id}")
  1573. async def list_tasks_by_chat_id_endpoint(chat_id: str, user=Depends(get_verified_user)):
  1574.     chat = Chats.get_chat_by_id(chat_id)
  1575.     if chat is None or chat.user_id != user.id:
  1576.         return {"task_ids": []}
  1577.  
  1578.     task_ids = list_task_ids_by_chat_id(chat_id)
  1579.  
  1580.     print(f"Task IDs for chat {chat_id}: {task_ids}")
  1581.     return {"task_ids": task_ids}
  1582.  
  1583.  
  1584. ##################################
  1585. #
  1586. # Config Endpoints
  1587. #
  1588. ##################################
  1589.  
  1590.  
  1591. @app.get("/api/config")
  1592. async def get_app_config(request: Request):
  1593.     user = None
  1594.     if "token" in request.cookies:
  1595.         token = request.cookies.get("token")
  1596.         try:
  1597.             data = decode_token(token)
  1598.         except Exception as e:
  1599.             log.debug(e)
  1600.             raise HTTPException(
  1601.                 status_code=status.HTTP_401_UNAUTHORIZED,
  1602.                 detail="Invalid token",
  1603.             )
  1604.         if data is not None and "id" in data:
  1605.             user = Users.get_user_by_id(data["id"])
  1606.  
  1607.     user_count = Users.get_num_users()
  1608.     onboarding = False
  1609.  
  1610.     if user is None:
  1611.         onboarding = user_count == 0
  1612.  
  1613.     return {
  1614.         **({"onboarding": True} if onboarding else {}),
  1615.         "status": True,
  1616.         "name": app.state.WEBUI_NAME,
  1617.         "version": VERSION,
  1618.         "default_locale": str(DEFAULT_LOCALE),
  1619.         "oauth": {
  1620.             "providers": {
  1621.                 name: config.get("name", name)
  1622.                 for name, config in OAUTH_PROVIDERS.items()
  1623.             }
  1624.         },
  1625.         "features": {
  1626.             "auth": WEBUI_AUTH,
  1627.             "auth_trusted_header": bool(app.state.AUTH_TRUSTED_EMAIL_HEADER),
  1628.             "enable_ldap": app.state.config.ENABLE_LDAP,
  1629.             "enable_api_key": app.state.config.ENABLE_API_KEY,
  1630.             "enable_signup": app.state.config.ENABLE_SIGNUP,
  1631.             "enable_login_form": app.state.config.ENABLE_LOGIN_FORM,
  1632.             "enable_websocket": ENABLE_WEBSOCKET_SUPPORT,
  1633.             **(
  1634.                 {
  1635.                     "enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS,
  1636.                     "enable_channels": app.state.config.ENABLE_CHANNELS,
  1637.                     "enable_web_search": app.state.config.ENABLE_WEB_SEARCH,
  1638.                     "enable_code_execution": app.state.config.ENABLE_CODE_EXECUTION,
  1639.                     "enable_code_interpreter": app.state.config.ENABLE_CODE_INTERPRETER,
  1640.                     "enable_image_generation": app.state.config.ENABLE_IMAGE_GENERATION,
  1641.                     "enable_autocomplete_generation": app.state.config.ENABLE_AUTOCOMPLETE_GENERATION,
  1642.                     "enable_community_sharing": app.state.config.ENABLE_COMMUNITY_SHARING,
  1643.                     "enable_message_rating": app.state.config.ENABLE_MESSAGE_RATING,
  1644.                     "enable_user_webhooks": app.state.config.ENABLE_USER_WEBHOOKS,
  1645.                     "enable_admin_export": ENABLE_ADMIN_EXPORT,
  1646.                     "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
  1647.                     "enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
  1648.                     "enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
  1649.                 }
  1650.                 if user is not None
  1651.                 else {}
  1652.             ),
  1653.         },
  1654.         **(
  1655.             {
  1656.                 "default_models": app.state.config.DEFAULT_MODELS,
  1657.                 "default_prompt_suggestions": app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
  1658.                 "user_count": user_count,
  1659.                 "code": {
  1660.                     "engine": app.state.config.CODE_EXECUTION_ENGINE,
  1661.                 },
  1662.                 "audio": {
  1663.                     "tts": {
  1664.                         "engine": app.state.config.TTS_ENGINE,
  1665.                         "voice": app.state.config.TTS_VOICE,
  1666.                         "split_on": app.state.config.TTS_SPLIT_ON,
  1667.                     },
  1668.                     "stt": {
  1669.                         "engine": app.state.config.STT_ENGINE,
  1670.                     },
  1671.                 },
  1672.                 "file": {
  1673.                     "max_size": app.state.config.FILE_MAX_SIZE,
  1674.                     "max_count": app.state.config.FILE_MAX_COUNT,
  1675.                 },
  1676.                 "permissions": {**app.state.config.USER_PERMISSIONS},
  1677.                 "google_drive": {
  1678.                     "client_id": GOOGLE_DRIVE_CLIENT_ID.value,
  1679.                     "api_key": GOOGLE_DRIVE_API_KEY.value,
  1680.                 },
  1681.                 "onedrive": {"client_id": ONEDRIVE_CLIENT_ID.value},
  1682.                 "license_metadata": app.state.LICENSE_METADATA,
  1683.                 **(
  1684.                     {
  1685.                         "active_entries": app.state.USER_COUNT,
  1686.                     }
  1687.                     if user.role == "admin"
  1688.                     else {}
  1689.                 ),
  1690.             }
  1691.             if user is not None
  1692.             else {}
  1693.         ),
  1694.     }
  1695.  
  1696.  
  1697. class UrlForm(BaseModel):
  1698.     url: str
  1699.  
  1700.  
  1701. @app.get("/api/webhook")
  1702. async def get_webhook_url(user=Depends(get_admin_user)):
  1703.     return {
  1704.         "url": app.state.config.WEBHOOK_URL,
  1705.     }
  1706.  
  1707.  
  1708. @app.post("/api/webhook")
  1709. async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
  1710.     app.state.config.WEBHOOK_URL = form_data.url
  1711.     app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL
  1712.     return {"url": app.state.config.WEBHOOK_URL}
  1713.  
  1714.  
  1715. @app.get("/api/version")
  1716. async def get_app_version():
  1717.     return {
  1718.         "version": VERSION,
  1719.     }
  1720.  
  1721.  
  1722. @app.get("/api/version/updates")
  1723. async def get_app_latest_release_version(user=Depends(get_verified_user)):
  1724.     if OFFLINE_MODE:
  1725.         log.debug(
  1726.             f"Offline mode is enabled, returning current version as latest version"
  1727.         )
  1728.         return {"current": VERSION, "latest": VERSION}
  1729.     try:
  1730.         timeout = aiohttp.ClientTimeout(total=1)
  1731.         async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
  1732.             async with session.get(
  1733.                 "https://api.github.com/repos/open-webui/open-webui/releases/latest"
  1734.             ) as response:
  1735.                 response.raise_for_status()
  1736.                 data = await response.json()
  1737.                 latest_version = data["tag_name"]
  1738.  
  1739.                 return {"current": VERSION, "latest": latest_version[1:]}
  1740.     except Exception as e:
  1741.         log.debug(e)
  1742.         return {"current": VERSION, "latest": VERSION}
  1743.  
  1744.  
  1745. @app.get("/api/changelog")
  1746. async def get_app_changelog():
  1747.     return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
  1748.  
  1749.  
  1750. ############################
  1751. # OAuth Login & Callback
  1752. ############################
  1753.  
  1754. # SessionMiddleware is used by authlib for oauth
  1755. if len(OAUTH_PROVIDERS) > 0:
  1756.     app.add_middleware(
  1757.         SessionMiddleware,
  1758.         secret_key=WEBUI_SECRET_KEY,
  1759.         session_cookie="oui-session",
  1760.         same_site=WEBUI_SESSION_COOKIE_SAME_SITE,
  1761.         https_only=WEBUI_SESSION_COOKIE_SECURE,
  1762.     )
  1763.  
  1764.  
  1765. @app.get("/oauth/{provider}/login")
  1766. async def oauth_login(provider: str, request: Request):
  1767.     return await oauth_manager.handle_login(request, provider)
  1768.  
  1769.  
  1770. # OAuth login logic is as follows:
  1771. # 1. Attempt to find a user with matching subject ID, tied to the provider
  1772. # 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth
  1773. #    - This is considered insecure in general, as OAuth providers do not always verify email addresses
  1774. # 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user
  1775. #    - Email addresses are considered unique, so we fail registration if the email address is already taken
  1776. @app.get("/oauth/{provider}/callback")
  1777. async def oauth_callback(provider: str, request: Request, response: Response):
  1778.     return await oauth_manager.handle_callback(request, provider, response)
  1779.  
  1780.  
  1781. @app.get("/manifest.json")
  1782. async def get_manifest_json():
  1783.     if app.state.EXTERNAL_PWA_MANIFEST_URL:
  1784.         return requests.get(app.state.EXTERNAL_PWA_MANIFEST_URL).json()
  1785.     else:
  1786.         return {
  1787.             "name": app.state.WEBUI_NAME,
  1788.             "short_name": app.state.WEBUI_NAME,
  1789.             "description": "Open WebUI is an open, extensible, user-friendly interface for AI that adapts to your workflow.",
  1790.             "start_url": "/",
  1791.             "display": "standalone",
  1792.             "background_color": "#343541",
  1793.             "orientation": "natural",
  1794.             "icons": [
  1795.                 {
  1796.                     "src": "/static/logo.png",
  1797.                     "type": "image/png",
  1798.                     "sizes": "500x500",
  1799.                     "purpose": "any",
  1800.                 },
  1801.                 {
  1802.                     "src": "/static/logo.png",
  1803.                     "type": "image/png",
  1804.                     "sizes": "500x500",
  1805.                     "purpose": "maskable",
  1806.                 },
  1807.             ],
  1808.         }
  1809.  
  1810.  
  1811. @app.get("/opensearch.xml")
  1812. async def get_opensearch_xml():
  1813.     xml_content = rf"""
  1814.    <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  1815.    <ShortName>{app.state.WEBUI_NAME}</ShortName>
  1816.    <Description>Search {app.state.WEBUI_NAME}</Description>
  1817.    <InputEncoding>UTF-8</InputEncoding>
  1818.    <Image width="16" height="16" type="image/x-icon">{app.state.config.WEBUI_URL}/static/favicon.png</Image>
  1819.    <Url type="text/html" method="get" template="{app.state.config.WEBUI_URL}/?q={"{searchTerms}"}"/>
  1820.    <moz:SearchForm>{app.state.config.WEBUI_URL}</moz:SearchForm>
  1821.    </OpenSearchDescription>
  1822.    """
  1823.     return Response(content=xml_content, media_type="application/xml")
  1824.  
  1825.  
  1826. @app.get("/health")
  1827. async def healthcheck():
  1828.     return {"status": True}
  1829.  
  1830.  
  1831. @app.get("/health/db")
  1832. async def healthcheck_with_db():
  1833.     Session.execute(text("SELECT 1;")).all()
  1834.     return {"status": True}
  1835.  
  1836.  
  1837. app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
  1838. app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
  1839.  
  1840.  
  1841. def swagger_ui_html(*args, **kwargs):
  1842.     return get_swagger_ui_html(
  1843.         *args,
  1844.         **kwargs,
  1845.         swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js",
  1846.         swagger_css_url="/static/swagger-ui/swagger-ui.css",
  1847.         swagger_favicon_url="/static/swagger-ui/favicon.png",
  1848.     )
  1849.  
  1850.  
  1851. applications.get_swagger_ui_html = swagger_ui_html
  1852.  
  1853. if os.path.exists(FRONTEND_BUILD_DIR):
  1854.     mimetypes.add_type("text/javascript", ".js")
  1855.     app.mount(
  1856.         "/",
  1857.         SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
  1858.         name="spa-static-files",
  1859.     )
  1860. else:
  1861.     log.warning(
  1862.         f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only."
  1863.     )
  1864.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement