mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-05-30 05:20:57 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d7eb0de71 | |||
| 33c150fe7b |
@@ -356,7 +356,7 @@ window.watchOverviewI18n = {
|
||||
{#last_checked becomes fetch-start-time#}
|
||||
<td class="last-checked" data-timestamp="{{ watch.last_checked }}" data-fetchduration={{ watch.fetch_time }} data-eta_complete="{{ watch.last_checked+watch.fetch_time }}" data-label="{{ _('Last Checked') }}">
|
||||
<div class="spinner-wrapper" style="display:none;" >
|
||||
<span class="spinner"></span><span class="status-text"> {{ _('Checking now') }}</span>
|
||||
<span class="spinner"></span><span class="status-text"> {{ watch['__check_status'] or _('Checking now') }}</span>
|
||||
</div>
|
||||
<span class="innertext">{{watch|format_last_checked_time|safe}}</span>
|
||||
</td>
|
||||
|
||||
@@ -49,6 +49,9 @@ def completion(model: str, messages: list, api_key: str = None,
|
||||
|
||||
_retryable = (litellm.Timeout, litellm.APIConnectionError)
|
||||
|
||||
logger.trace("Sending payload to LLM.. ")
|
||||
logger.trace(messages)
|
||||
|
||||
for attempt in range(1, DEFAULT_RETRIES + 1):
|
||||
try:
|
||||
response = litellm.completion(**kwargs)
|
||||
|
||||
@@ -1064,6 +1064,7 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
Prepare watch data for commit.
|
||||
|
||||
Excludes processor_config_* keys (stored in separate files).
|
||||
Excludes __-prefixed keys (transient in-memory state — must not persist to disk).
|
||||
Normalizes browser_steps to empty list if no meaningful steps.
|
||||
"""
|
||||
import copy
|
||||
@@ -1077,8 +1078,11 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
else:
|
||||
snapshot = dict(self)
|
||||
|
||||
# Exclude processor config keys (stored separately)
|
||||
watch_dict = {k: copy.deepcopy(v) for k, v in snapshot.items() if not k.startswith('processor_config_')}
|
||||
# Exclude processor config keys (stored separately) and __-prefixed transient keys
|
||||
watch_dict = {
|
||||
k: copy.deepcopy(v) for k, v in snapshot.items()
|
||||
if not k.startswith('processor_config_') and not k.startswith('__')
|
||||
}
|
||||
|
||||
# Normalize browser_steps: if no meaningful steps, save as empty list
|
||||
if not self.has_browser_steps:
|
||||
|
||||
@@ -335,6 +335,13 @@ class watch_base(dict):
|
||||
if self.__watch_was_edited:
|
||||
return # Already marked as edited
|
||||
|
||||
# __-prefixed keys are transient in-memory state (e.g. __check_status set by
|
||||
# set_watch_minitext_status). They never persist to disk and must not trigger
|
||||
# the edited flag — otherwise just observing a check in progress would force
|
||||
# the next run to bypass the unchanged-content skip.
|
||||
if isinstance(key, str) and key.startswith('__'):
|
||||
return
|
||||
|
||||
# Import from shared schema utilities (no circular dependency)
|
||||
from .schema_utils import get_readonly_watch_fields
|
||||
readonly_fields = get_readonly_watch_fields()
|
||||
|
||||
@@ -9,9 +9,16 @@ from changedetectionio.pluggy_interface import apply_update_handler_alter, apply
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Allow alphanumerics, space, and a small set of punctuation that appears in legitimate
|
||||
# status strings ("Querying AI/LLM (intent)..", "Fetching page.."). Anything that could
|
||||
# be HTML-active (<, >, &, ", ', =, ;, {, }, `, \) is stripped.
|
||||
_MINITEXT_STATUS_SAFE_RE = re.compile(r'[^A-Za-z0-9 ().,/:\-]')
|
||||
_MINITEXT_STATUS_MAX_LEN = 80
|
||||
|
||||
from loguru import logger
|
||||
|
||||
# Async version of update_worker
|
||||
@@ -20,6 +27,22 @@ from loguru import logger
|
||||
IN_PYTEST = "pytest" in sys.modules or "PYTEST_CURRENT_TEST" in os.environ
|
||||
DEFER_SLEEP_TIME_ALREADY_QUEUED = 0.3 if IN_PYTEST else 10.0
|
||||
|
||||
|
||||
def set_watch_minitext_status(watch, status):
|
||||
"""
|
||||
Set a transient status line for a watch (e.g. "Fetching page..", "Querying AI/LLM..").
|
||||
|
||||
Writes to watch['__check_status'] so a client reloading the page can render the
|
||||
last known status, and fires the realtime signal so already-connected clients
|
||||
update live. __-prefixed key is filtered from disk by Watch._get_commit_data().
|
||||
|
||||
Status is sanitized to alphanumerics, space, and safe punctuation only.
|
||||
"""
|
||||
safe_status = _MINITEXT_STATUS_SAFE_RE.sub('', str(status))[:_MINITEXT_STATUS_MAX_LEN]
|
||||
watch['__check_status'] = safe_status
|
||||
signal('watch_small_status_comment').send(watch_uuid=watch['uuid'], status=safe_status)
|
||||
|
||||
|
||||
async def async_update_worker(worker_id, q, notification_q, app, datastore, executor=None):
|
||||
"""
|
||||
Async worker function that processes watch check jobs from the queue.
|
||||
@@ -159,8 +182,7 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
# Allow plugins to modify/wrap the update_handler
|
||||
update_handler = apply_update_handler_alter(update_handler, watch, datastore)
|
||||
|
||||
update_signal = signal('watch_small_status_comment')
|
||||
update_signal.send(watch_uuid=uuid, status="Fetching page..")
|
||||
set_watch_minitext_status(watch, "Fetching page..")
|
||||
|
||||
# All fetchers are now async, so call directly
|
||||
await update_handler.call_browser()
|
||||
@@ -446,6 +468,7 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
# Step 1: AI Change Intent — may suppress notification
|
||||
_llm_intent, _llm_intent_source = resolve_intent(watch, datastore)
|
||||
if _llm_intent:
|
||||
set_watch_minitext_status(watch, "AI/LLM (rules)..")
|
||||
_llm_result = await loop.run_in_executor(
|
||||
executor,
|
||||
lambda diff=_diff_text, snap=contents: evaluate_change(
|
||||
@@ -465,6 +488,7 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
|
||||
# Step 2: AI Change Summary — runs for any LLM-configured watch with a change
|
||||
if changed_detected:
|
||||
set_watch_minitext_status(watch, "AI/LLM (summary)..")
|
||||
_change_summary = await loop.run_in_executor(
|
||||
executor,
|
||||
lambda diff=_diff_text, snap=contents: summarise_change(
|
||||
@@ -669,6 +693,8 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
finally:
|
||||
# Send completion signal - retrieve by name to ensure thread-safe access
|
||||
if watch:
|
||||
# Clear transient in-memory status — check is done
|
||||
watch.pop('__check_status', None)
|
||||
watch_check_update = signal('watch_check_update')
|
||||
watch_check_update.send(watch_uuid=watch['uuid'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user