diff --git a/changedetectionio/blueprint/settings/__init__.py b/changedetectionio/blueprint/settings/__init__.py index a98151bc3..b4d103d21 100644 --- a/changedetectionio/blueprint/settings/__init__.py +++ b/changedetectionio/blueprint/settings/__init__.py @@ -14,8 +14,10 @@ from changedetectionio.auth_decorator import login_optionally_required def construct_blueprint(datastore: ChangeDetectionStore): from changedetectionio.blueprint.settings.llm import construct_llm_blueprint + from changedetectionio.llm.evaluator import is_llm_features_disabled settings_blueprint = Blueprint('settings', __name__, template_folder="templates") - settings_blueprint.register_blueprint(construct_llm_blueprint(datastore), url_prefix='/llm') + if not is_llm_features_disabled(): + settings_blueprint.register_blueprint(construct_llm_blueprint(datastore), url_prefix='/llm') @settings_blueprint.route("", methods=['GET', "POST"]) @login_optionally_required diff --git a/changedetectionio/blueprint/settings/templates/settings.html b/changedetectionio/blueprint/settings/templates/settings.html index ed05839bf..b2e578978 100644 --- a/changedetectionio/blueprint/settings/templates/settings.html +++ b/changedetectionio/blueprint/settings/templates/settings.html @@ -34,7 +34,9 @@
  • {{ tab.tab_label }}
  • {% endfor %} {% endif %} + {% if not llm_features_disabled %}
  • {{ _('AI / LLM') }}
  • + {% endif %}
  • {{ _('Info') }}
  • @@ -394,7 +396,9 @@ nav {% endfor %} {% endif %} + {% if not llm_features_disabled %} {% include 'settings_llm_tab.html' %} + {% endif %}

    {{ _('Uptime:') }} {{ uptime_seconds|format_duration }}

    {{ _('Python version:') }} {{ python_version }}

    diff --git a/changedetectionio/blueprint/ui/templates/edit.html b/changedetectionio/blueprint/ui/templates/edit.html index 8ea17b439..420ec72c2 100644 --- a/changedetectionio/blueprint/ui/templates/edit.html +++ b/changedetectionio/blueprint/ui/templates/edit.html @@ -57,7 +57,9 @@ {% if capabilities.supports_visual_selector %}
  • {{ _('Visual Filter Selector') }}
  • {% endif %} + {% if not llm_features_disabled %}
  • {{ _('AI / LLM') }}
  • + {% endif %} {% if capabilities.supports_text_filters_and_triggers %}
  • {{ _('Filters & Triggers') }}
  • {{ _('Conditions') }}
  • @@ -321,9 +323,11 @@ Math: {{ 1 + 1 }}") }}
    + {% if not llm_features_disabled %}
    {% include "edit/include_llm_intent.html" %}
    + {% endif %}
    {{ _('Activate preview') }} @@ -503,7 +507,7 @@ Math: {{ 1 + 1 }}") }} {{ _('Server type reply') }} {{ watch.get('remote_server_reply') }} - {% if settings_application.get('llm', {}).get('model') %} + {% if not llm_features_disabled and settings_application.get('llm', {}).get('model') %} {{ _('AI tokens (last check)') }} {{ "{:,}".format(watch.get('llm_last_tokens_used') or 0) }} diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 987309b8c..3449726bf 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -522,6 +522,11 @@ def changedetection_app(config=None, datastore_o=None): available_languages=available_languages ) + @app.context_processor + def inject_llm_features_disabled(): + from changedetectionio.llm.evaluator import is_llm_features_disabled + return dict(llm_features_disabled=is_llm_features_disabled()) + # Set up a request hook to check authentication for all routes @app.before_request def check_authentication(): diff --git a/changedetectionio/llm/evaluator.py b/changedetectionio/llm/evaluator.py index 3360f3ec0..085e9a2d4 100644 --- a/changedetectionio/llm/evaluator.py +++ b/changedetectionio/llm/evaluator.py @@ -20,6 +20,8 @@ from dataclasses import dataclass from datetime import datetime, timezone from loguru import logger +from changedetectionio.strtobool import strtobool + from . import client as llm_client from .prompt_builder import ( build_change_summary_prompt, build_change_summary_system_prompt, @@ -31,6 +33,11 @@ from .response_parser import parse_eval_response, parse_preview_response, parse_ _DEFAULT_MAX_INPUT_CHARS = 100_000 + +def is_llm_features_disabled() -> bool: + """True when the LLM_FEATURES_DISABLED env var is set to a truthy value.""" + return bool(strtobool(os.getenv('LLM_FEATURES_DISABLED', ''))) + def _get_max_input_chars(datastore) -> int: """Max input characters to send to the LLM. Resolution: env var → datastore → 100,000. Always returns at least 1 — unlimited is not permitted. @@ -207,6 +214,8 @@ def get_llm_config(datastore) -> dict | None: 1. Environment variables: LLM_MODEL, LLM_API_KEY, LLM_API_BASE 2. Datastore settings (set via UI) """ + if is_llm_features_disabled(): + return None # 1. Environment variable override env_model = os.getenv('LLM_MODEL', '').strip() if env_model: @@ -225,6 +234,8 @@ def get_llm_config(datastore) -> dict | None: def llm_configured_via_env() -> bool: """True when LLM config comes from environment variables, not the UI.""" + if is_llm_features_disabled(): + return False return bool(os.getenv('LLM_MODEL', '').strip()) diff --git a/changedetectionio/templates/_common_fields.html b/changedetectionio/templates/_common_fields.html index 696185c23..829ca1c4b 100644 --- a/changedetectionio/templates/_common_fields.html +++ b/changedetectionio/templates/_common_fields.html @@ -112,7 +112,7 @@ {{ '{{triggered_text}}' }} {{ _('Text that tripped the trigger from filters') }} - {% if settings_application and settings_application.get('llm', {}).get('model') %} + {% if not llm_features_disabled and settings_application and settings_application.get('llm', {}).get('model') %} {{ '{{diff}}' }} {{ _('(upgraded)') }} {{ _('When AI Change Summary is configured, contains the AI-generated description instead of the raw diff. Falls back to raw diff when not configured.') }} diff --git a/changedetectionio/templates/base.html b/changedetectionio/templates/base.html index 0204d84b2..94d318b10 100644 --- a/changedetectionio/templates/base.html +++ b/changedetectionio/templates/base.html @@ -281,6 +281,7 @@
    + {% if not llm_features_disabled %} + {% endif %} {% if current_user.is_authenticated or not has_password %} diff --git a/changedetectionio/templates/menu.html b/changedetectionio/templates/menu.html index 4afaa8b5d..a67f901e6 100644 --- a/changedetectionio/templates/menu.html +++ b/changedetectionio/templates/menu.html @@ -37,10 +37,12 @@ {% endif %}