mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-07-08 08:21:14 +00:00
Conforming 'diff' page meta type
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Base contract + shared helpers for a processor's history/diff ("difference") page.
|
||||
|
||||
Each processor can provide a custom history/diff view by shipping a
|
||||
``processors/<name>/difference.py`` module that exposes a module-level ``render(...)``
|
||||
function. The route ``blueprint/ui/diff.py::diff_history_page`` resolves it via
|
||||
``get_processor_submodule(processor_name, 'difference')`` and calls ``render(...)`` with the
|
||||
keyword contract below; processors without one fall back to ``text_json_diff``.
|
||||
|
||||
This module formalises that previously-implicit contract:
|
||||
* ``DifferenceRenderer`` documents the ``render(...)`` signature (for typing/static checks).
|
||||
* ``resolve_diff_versions()`` centralises the from/to version selection that was duplicated
|
||||
between ``text_json_diff/difference.py`` and the ``/diff/.../llm-summary`` route, so every
|
||||
processor resolves "which two snapshots am I comparing" identically.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, Protocol, runtime_checkable
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class DifferenceRenderer(Protocol):
|
||||
"""The contract a processor's ``difference.py`` module must satisfy.
|
||||
|
||||
Implemented as a module-level function (not a class method), so a conforming module looks
|
||||
like::
|
||||
|
||||
def render(watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
extract_form=None):
|
||||
...
|
||||
return render_template(...) # a Flask response
|
||||
|
||||
Returns a Flask response (rendered template / redirect / make_response).
|
||||
"""
|
||||
|
||||
def render(self, watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
extract_form: Any = None) -> Any:
|
||||
...
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiffVersions:
|
||||
"""The resolved comparison window for a difference page."""
|
||||
dates: List[str] # all history timestamps (string keys), oldest -> newest
|
||||
from_version: str # older snapshot timestamp being compared
|
||||
to_version: str # newer snapshot timestamp being compared
|
||||
from_contents: str # raw snapshot text for from_version
|
||||
to_contents: str # raw snapshot text for to_version
|
||||
viewing_latest: bool # True when to_version is the newest snapshot
|
||||
note: str # human note when not viewing the latest changes ('' otherwise)
|
||||
|
||||
|
||||
def resolve_diff_versions(watch, request) -> DiffVersions:
|
||||
"""Resolve which two snapshots a difference page should compare.
|
||||
|
||||
Default ``from_version`` is the snapshot closest to the user's last view (so the page opens
|
||||
on "what changed since I last looked"), falling back to the second-newest snapshot. Default
|
||||
``to_version`` is the newest snapshot. Both are overridable via ``?from_version=`` /
|
||||
``?to_version=`` query args. The caller is responsible for guaranteeing at least two
|
||||
snapshots exist (the route already redirects otherwise).
|
||||
|
||||
Note: read the versions BEFORE calling ``datastore.set_last_viewed()`` — the default
|
||||
``from_version`` depends on the pre-update last-viewed timestamp.
|
||||
"""
|
||||
dates = list(watch.history.keys())
|
||||
|
||||
best_last_viewed = watch.get_from_version_based_on_last_viewed
|
||||
from_default = best_last_viewed if best_last_viewed else dates[-2]
|
||||
from_version = request.args.get('from_version', from_default)
|
||||
to_version = request.args.get('to_version', dates[-1])
|
||||
|
||||
try:
|
||||
to_contents = watch.get_history_snapshot(timestamp=to_version)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read watch history to-version for version {to_version}: {str(e)}")
|
||||
to_contents = f"Unable to read to-version at {to_version}.\n"
|
||||
|
||||
try:
|
||||
from_contents = watch.get_history_snapshot(timestamp=from_version)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read watch history from-version for version {from_version}: {str(e)}")
|
||||
from_contents = f"Unable to read from-version {from_version}.\n"
|
||||
|
||||
note = ''
|
||||
if str(from_version) != str(dates[-2]) or str(to_version) != str(dates[-1]):
|
||||
note = 'Note: You are not viewing the latest changes.'
|
||||
|
||||
return DiffVersions(
|
||||
dates=dates,
|
||||
from_version=str(from_version),
|
||||
to_version=str(to_version),
|
||||
from_contents=from_contents,
|
||||
to_contents=to_contents,
|
||||
viewing_latest=str(to_version) == str(dates[-1]),
|
||||
note=note,
|
||||
)
|
||||
@@ -129,30 +129,14 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
extract_form = forms.extractDataForm(formdata=request.form,
|
||||
data={'extract_regex': request.form.get('extract_regex', '')}
|
||||
)
|
||||
history = watch.history
|
||||
dates = list(history.keys())
|
||||
|
||||
# If a "from_version" was requested, then find it (or the closest one)
|
||||
# Also set "from version" to be the closest version to the one that was last viewed.
|
||||
|
||||
best_last_viewed_timestamp = watch.get_from_version_based_on_last_viewed
|
||||
from_version_timestamp = best_last_viewed_timestamp if best_last_viewed_timestamp else dates[-2]
|
||||
from_version = request.args.get('from_version', from_version_timestamp )
|
||||
|
||||
# Use the current one if nothing was specified
|
||||
to_version = request.args.get('to_version', str(dates[-1]))
|
||||
|
||||
try:
|
||||
to_version_file_contents = watch.get_history_snapshot(timestamp=to_version)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read watch history to-version for version {to_version}: {str(e)}")
|
||||
to_version_file_contents = f"Unable to read to-version at {to_version}.\n"
|
||||
|
||||
try:
|
||||
from_version_file_contents = watch.get_history_snapshot(timestamp=from_version)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to read watch history from-version for version {from_version}: {str(e)}")
|
||||
from_version_file_contents = f"Unable to read to-version {from_version}.\n"
|
||||
# Resolve which two snapshots we're comparing (shared with all processors' diff pages).
|
||||
from changedetectionio.processors.difference_base import resolve_diff_versions
|
||||
diff_versions = resolve_diff_versions(watch, request)
|
||||
dates = diff_versions.dates
|
||||
from_version = diff_versions.from_version
|
||||
to_version = diff_versions.to_version
|
||||
from_version_file_contents = diff_versions.from_contents
|
||||
to_version_file_contents = diff_versions.to_contents
|
||||
|
||||
screenshot_url = watch.get_screenshot()
|
||||
|
||||
@@ -196,16 +180,14 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
content = apply_html_color_to_body(n_body=content)
|
||||
offscreen_content = render_template("diff-offscreen-options.html")
|
||||
|
||||
note = ''
|
||||
if str(from_version) != str(dates[-2]) or str(to_version) != str(dates[-1]):
|
||||
note = 'Note: You are not viewing the latest changes.'
|
||||
note = diff_versions.note
|
||||
|
||||
llm_configured = bool(
|
||||
datastore.data.get('settings', {}).get('application', {}).get('llm', {}).get('model')
|
||||
)
|
||||
|
||||
# Load cached AI diff summary for this exact from→to + prompt combination
|
||||
viewing_latest = str(to_version) == str(dates[-1])
|
||||
viewing_latest = diff_versions.viewing_latest
|
||||
llm_diff_summary = ''
|
||||
llm_summary_prompt = ''
|
||||
if llm_configured:
|
||||
|
||||
Reference in New Issue
Block a user