feature: allow a watch or group to append to the inherited AI Change Summary prompt (#4294)
Build and push containers / metadata (push) Canceled after 0s
Build and push containers / build-push-containers (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Canceled after 0s
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Canceled after 0s
ChangeDetection.io App Test / lint-code (push) Canceled after 0s
ChangeDetection.io App Test / lint-translations (push) Canceled after 0s
ChangeDetection.io App Test / lint-template-i18n (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-10 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-11 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-12 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-13 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-14 (push) Canceled after 0s

Co-authored-by: snowyukitty <270071858+snowyukitty@users.noreply.github.com>
Co-authored-by: dgtlmoon <dgtlmoon@gmail.com>
This commit is contained in:
snowyukitty
2026-08-20 15:14:59 +02:00
committed by GitHub
co-authored by snowyukitty dgtlmoon
parent 6b9040256a
commit 01cf56c8fb
25 changed files with 712 additions and 8 deletions
+15 -1
View File
@@ -1,5 +1,6 @@
from wtforms import (
Form,
RadioField,
StringField,
SubmitField,
TextAreaField,
@@ -10,7 +11,11 @@ from flask_babel import lazy_gettext as _l
from changedetectionio.processors.restock_diff.forms import processor_settings_form as restock_settings_form
from changedetectionio.llm.ui_strings import LLM_INTENT_TAG_PLACEHOLDER
from changedetectionio.llm.evaluator import DEFAULT_CHANGE_SUMMARY_PROMPT
from changedetectionio.llm.evaluator import (
DEFAULT_CHANGE_SUMMARY_PROMPT,
LLM_PROMPT_MODE_APPEND,
LLM_PROMPT_MODE_REPLACE,
)
class group_restock_settings_form(restock_settings_form):
overrides_watch = BooleanField(_l('Activate for individual watches in this tag/group?'), default=False)
@@ -26,6 +31,15 @@ class group_restock_settings_form(restock_settings_form):
render_kw={"rows": "5", "placeholder": DEFAULT_CHANGE_SUMMARY_PROMPT},
default='')
llm_change_summary_mode = RadioField(
_l('How this prompt combines with the inherited one'),
choices=[
(LLM_PROMPT_MODE_REPLACE, _l('Replace the inherited prompt')),
(LLM_PROMPT_MODE_APPEND, _l('Append to the inherited prompt')),
],
default=LLM_PROMPT_MODE_REPLACE,
)
class SingleTag(Form):
name = StringField(_l('Tag name'), [validators.InputRequired()], render_kw={"placeholder": _l("Name")})
+16 -1
View File
@@ -6,7 +6,13 @@ from flask_babel import lazy_gettext as _l, gettext
from changedetectionio.blueprint.rss import RSS_FORMAT_TYPES, RSS_TEMPLATE_TYPE_OPTIONS, RSS_TEMPLATE_HTML_DEFAULT
from changedetectionio.llm.ui_strings import LLM_INTENT_WATCH_PLACEHOLDER
from changedetectionio.llm.evaluator import DEFAULT_CHANGE_SUMMARY_PROMPT, LLM_DEFAULT_MAX_SUMMARY_TOKENS, LLM_DEFAULT_THINKING_BUDGET
from changedetectionio.llm.evaluator import (
DEFAULT_CHANGE_SUMMARY_PROMPT,
LLM_DEFAULT_MAX_SUMMARY_TOKENS,
LLM_DEFAULT_THINKING_BUDGET,
LLM_PROMPT_MODE_APPEND,
LLM_PROMPT_MODE_REPLACE,
)
from changedetectionio.conditions.form import ConditionFormRow
from changedetectionio.notification_service import NotificationContextData
from changedetectionio.strtobool import strtobool
@@ -886,6 +892,15 @@ class processor_text_json_diff_form(commonSettingsForm):
render_kw={"rows": "5", "placeholder": DEFAULT_CHANGE_SUMMARY_PROMPT},
default='')
llm_change_summary_mode = RadioField(
_l('How this prompt combines with the inherited one'),
choices=[
(LLM_PROMPT_MODE_REPLACE, _l('Replace the inherited prompt')),
(LLM_PROMPT_MODE_APPEND, _l('Append to the inherited prompt')),
],
default=LLM_PROMPT_MODE_REPLACE,
)
include_filters = StringListField(_l('CSS/JSONPath/JQ/XPath Filters'), [ValidateCSSJSONXPATHInput()], default='')
subtractive_selectors = StringListField(_l('Remove elements'), [ValidateCSSJSONXPATHInput(allow_json=False)])
+51 -6
View File
@@ -149,6 +149,13 @@ DEFAULT_CHANGE_SUMMARY_PROMPT = (
"Do not give partial listings such as 'Examples include:', always be thorough."
)
# How a watch's or tag's llm_change_summary combines with the prompt it inherits.
# 'replace' is the default and the historical behaviour; 'append' lets a watch add a line
# or two to the inherited prompt instead of holding a full private copy of it, so later
# edits to the global prompt still reach that watch. Re #4251.
LLM_PROMPT_MODE_REPLACE = 'replace'
LLM_PROMPT_MODE_APPEND = 'append'
def _summary_max_tokens(diff: str, max_cap: int = LLM_DEFAULT_MAX_SUMMARY_TOKENS) -> int:
"""Scale completion tokens to diff size: floor 400, ~1 token per 4 chars, ceiling max_cap."""
@@ -539,16 +546,54 @@ def run_setup(watch, datastore, snapshot_text: str) -> None:
# AI Change Summary — human-readable description of what changed
# ---------------------------------------------------------------------------
def _first_tag_with_field(watch, datastore, field: str):
"""Return (value, tag) for the first linked tag with a non-empty `field`, else ('', None).
Same first-match-wins order as resolve_llm_field(); this variant also hands back the
tag itself so the caller can read sibling keys such as the prompt mode.
"""
for tag_uuid in watch.get('tags', []):
tag = datastore.data['settings']['application'].get('tags', {}).get(tag_uuid)
if tag:
value = (tag.get(field) or '').strip()
if value:
return value, tag
return '', None
def _apply_prompt_layer(inherited: str, value: str, mode: str) -> str:
"""Fold one cascade level's prompt onto what it inherited.
'append' keeps the inherited prompt and adds `value` after it, so a watch can add a
sentence or two without pinning a private copy of the prompt above it (see #4251).
Anything else replaces, which is the historical behaviour and stays the default.
"""
if not value:
return inherited
if mode == LLM_PROMPT_MODE_APPEND and inherited:
return f"{inherited}\n\n{value}"
return value
def get_effective_summary_prompt(watch, datastore) -> str:
"""Return the prompt that summarise_change will use.
Cascade: watch tag global settings default hardcoded fallback.
Cascade: hardcoded fallback global settings default tag watch. Each level with a
value either replaces what it inherited or appends to it, per its own
`llm_change_summary_mode`. With every level left on the default 'replace' this reduces
to the original watch tag global hardcoded first-non-empty-wins behaviour.
"""
prompt, _ = resolve_llm_field(watch, datastore, 'llm_change_summary')
if prompt:
return prompt
global_default = get_llm_settings(datastore).change_summary_default.strip()
return global_default or DEFAULT_CHANGE_SUMMARY_PROMPT
prompt = get_llm_settings(datastore).change_summary_default.strip() or DEFAULT_CHANGE_SUMMARY_PROMPT
tag_value, tag = _first_tag_with_field(watch, datastore, 'llm_change_summary')
if tag_value:
prompt = _apply_prompt_layer(prompt, tag_value, tag.get('llm_change_summary_mode'))
watch_value = (watch.get('llm_change_summary') or '').strip()
if watch_value:
prompt = _apply_prompt_layer(prompt, watch_value, watch.get('llm_change_summary_mode'))
return prompt
def compute_summary_cache_key(diff_text: str, prompt: str) -> str:
+1
View File
@@ -191,6 +191,7 @@ class watch_base(dict):
# LLM intent-based evaluation
'llm_intent': '', # Plain-English description of what the user cares about (change filter)
'llm_change_summary': '', # Prompt for AI Change Summary — replaces {{ diff }} in notifications
'llm_change_summary_mode': 'replace', # 'replace' the inherited prompt, or 'append' to it
'llm_prefilter': None, # CSS selector derived at setup time (semantic only, e.g. "footer")
'llm_evaluation_cache': {}, # {sha256(intent+diff): {important, summary}} - evaluated once, cached
'fetch_backend': 'system', # plaintext, playwright etc
@@ -97,6 +97,23 @@
<div style="margin-top: 0.3em;">
<a href="#" class="pure-button button-xsmall" onclick="var t=document.getElementById('llm_change_summary'); if(!t.value&amp;&amp;t.placeholder) t.value=t.placeholder; return false;">{{ _('Modify default prompt') }}</a>
</div>
<div class="pure-control-group" style="margin-top: 0.6em;">
<label>{{ form.llm_change_summary_mode.label.text }}</label>
<div>
{% for subfield in form.llm_change_summary_mode %}
<label class="pure-radio" style="display:block; font-weight:normal; margin-bottom:0.3em;">
{{ subfield() }} {{ subfield.label.text }}
</label>
{% endfor %}
</div>
<span class="pure-form-message-inline">
{% if watch is defined and watch %}
{{ _('Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later edits to that prompt still reach this watch.') }}
{% else %}
{{ _('Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt still reach this group.') }}
{% endif %}
</span>
</div>
{% if watch is defined and watch %}
<div class="pure-form-message-inline">
<strong>{{ _('Examples:') }}</strong>
@@ -556,3 +556,87 @@ class TestSummaryCacheKey:
ds = _make_datastore(tags={'t1': tag})
watch = _make_watch(llm_change_summary='', tags=['t1'])
assert get_effective_summary_prompt(watch, ds) == 'tag-level prompt'
# ---------------------------------------------------------------------------
# llm_change_summary_mode — append vs replace (#4251)
# ---------------------------------------------------------------------------
class TestSummaryPromptAppendMode:
"""A watch/tag may add to the prompt it inherits instead of holding a private copy.
Everything here must leave the legacy 'replace' path byte-identical that is what
the TestSummaryCacheKey cases above pin.
"""
def test_global_default_used_as_base_when_nothing_else_set(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'})
assert get_effective_summary_prompt(_make_watch(), ds) == 'GLOBAL'
def test_watch_appends_to_global_default(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'})
watch = _make_watch(llm_change_summary='Also mention the SKU.')
watch['llm_change_summary_mode'] = 'append'
assert get_effective_summary_prompt(watch, ds) == 'GLOBAL\n\nAlso mention the SKU.'
def test_watch_appends_to_hardcoded_default_when_no_global_set(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt, DEFAULT_CHANGE_SUMMARY_PROMPT
ds = _make_datastore()
watch = _make_watch(llm_change_summary='Also mention the SKU.')
watch['llm_change_summary_mode'] = 'append'
result = get_effective_summary_prompt(watch, ds)
assert result == f'{DEFAULT_CHANGE_SUMMARY_PROMPT}\n\nAlso mention the SKU.'
def test_watch_append_targets_the_tag_prompt_when_a_tag_supplies_one(self):
"""The watch appends to what it would otherwise have inherited — here the tag."""
from changedetectionio.llm.evaluator import get_effective_summary_prompt
tag = {'title': 'grp', 'llm_change_summary': 'TAG'}
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'}, tags={'t1': tag})
watch = _make_watch(llm_change_summary='WATCH', tags=['t1'])
watch['llm_change_summary_mode'] = 'append'
assert get_effective_summary_prompt(watch, ds) == 'TAG\n\nWATCH'
def test_tag_and_watch_can_both_append_forming_a_chain(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt
tag = {'title': 'grp', 'llm_change_summary': 'TAG', 'llm_change_summary_mode': 'append'}
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'}, tags={'t1': tag})
watch = _make_watch(llm_change_summary='WATCH', tags=['t1'])
watch['llm_change_summary_mode'] = 'append'
assert get_effective_summary_prompt(watch, ds) == 'GLOBAL\n\nTAG\n\nWATCH'
def test_tag_appends_while_watch_replaces(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt
tag = {'title': 'grp', 'llm_change_summary': 'TAG', 'llm_change_summary_mode': 'append'}
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'}, tags={'t1': tag})
watch = _make_watch(llm_change_summary='WATCH', tags=['t1'])
assert get_effective_summary_prompt(watch, ds) == 'WATCH'
def test_append_mode_with_empty_text_changes_nothing(self):
from changedetectionio.llm.evaluator import get_effective_summary_prompt
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'})
watch = _make_watch(llm_change_summary='')
watch['llm_change_summary_mode'] = 'append'
assert get_effective_summary_prompt(watch, ds) == 'GLOBAL'
def test_missing_mode_key_behaves_as_replace(self):
"""Watches stored before this feature have no mode key at all."""
from changedetectionio.llm.evaluator import get_effective_summary_prompt
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'})
watch = _make_watch(llm_change_summary='WATCH')
assert 'llm_change_summary_mode' not in watch
assert get_effective_summary_prompt(watch, ds) == 'WATCH'
def test_append_changes_the_cache_key(self):
"""Toggling the mode must invalidate cached summaries, not silently reuse them."""
from changedetectionio.llm.evaluator import get_effective_summary_prompt, compute_summary_cache_key
ds = _make_datastore(llm_cfg={'change_summary_default': 'GLOBAL'})
replacing = _make_watch(llm_change_summary='WATCH')
appending = _make_watch(llm_change_summary='WATCH')
appending['llm_change_summary_mode'] = 'append'
key_replace = compute_summary_cache_key('diff', get_effective_summary_prompt(replacing, ds))
key_append = compute_summary_cache_key('diff', get_effective_summary_prompt(appending, ds))
assert key_replace != key_append
@@ -319,6 +319,99 @@ def test_watch_prompt_overrides_tag_and_global(
delete_all_watches(client)
def test_append_mode_saved_via_edit_form_and_applied(
client, live_server, measure_memory_usage, datastore_path):
"""
Choosing "add to the end of the inherited prompt" in the watch edit form persists,
and the watch's text is then appended to the global default rather than replacing it.
Re #4251.
"""
from changedetectionio.llm.evaluator import get_effective_summary_prompt
_set_response(datastore_path, HTML_V1)
_configure_llm(client)
ds = client.application.config.get('DATASTORE')
_set_global_default(ds, 'Global: summarise as one sentence.')
test_url = url_for('test_endpoint', _external=True)
uuid = ds.add_watch(url=test_url)
res = client.post(
url_for("ui.ui_edit.edit_page", uuid=uuid),
data={
"url": test_url,
"fetch_backend": "html_requests",
"time_between_check_use_default": "y",
"llm_change_summary": "Also flag anything mentioning a recall.",
"llm_change_summary_mode": "append",
},
follow_redirects=True,
)
assert b"Updated watch." in res.data
watch = ds.data['watching'][uuid]
assert watch.get('llm_change_summary_mode') == 'append'
assert get_effective_summary_prompt(watch, ds) == (
'Global: summarise as one sentence.\n\nAlso flag anything mentioning a recall.'
)
delete_all_watches(client)
def test_edit_form_defaults_to_replace_preserving_old_behaviour(
client, live_server, measure_memory_usage, datastore_path):
"""
A form submitted without the mode field (the pre-#4251 shape) must still replace,
so upgrading does not silently change what existing watches send to the LLM.
"""
from changedetectionio.llm.evaluator import get_effective_summary_prompt
_set_response(datastore_path, HTML_V1)
_configure_llm(client)
ds = client.application.config.get('DATASTORE')
_set_global_default(ds, 'Global: summarise as one sentence.')
test_url = url_for('test_endpoint', _external=True)
uuid = ds.add_watch(url=test_url)
res = client.post(
url_for("ui.ui_edit.edit_page", uuid=uuid),
data={
"url": test_url,
"fetch_backend": "html_requests",
"time_between_check_use_default": "y",
"llm_change_summary": "Only tell me the new price.",
},
follow_redirects=True,
)
assert b"Updated watch." in res.data
watch = ds.data['watching'][uuid]
assert get_effective_summary_prompt(watch, ds) == 'Only tell me the new price.'
delete_all_watches(client)
def test_edit_page_renders_the_prompt_mode_radio(
client, live_server, measure_memory_usage, datastore_path):
"""Both radio options must be present on the watch edit page."""
_set_response(datastore_path, HTML_V1)
_configure_llm(client)
ds = client.application.config.get('DATASTORE')
test_url = url_for('test_endpoint', _external=True)
uuid = ds.add_watch(url=test_url)
res = client.get(url_for("ui.ui_edit.edit_page", uuid=uuid))
body = res.data.decode('utf-8', errors='replace')
assert 'name="llm_change_summary_mode"' in body
assert 'value="replace"' in body
assert 'value="append"' in body
delete_all_watches(client)
def test_hardcoded_fallback_when_nothing_set(
client, live_server, measure_memory_usage, datastore_path):
"""
@@ -382,3 +382,49 @@ def test_tag_edit_page_shows_ai_section(
f"{field} must not be readonly in tag edit context; snippet: {snippet!r}"
delete_all_watches(client)
def test_tag_edit_page_shows_prompt_mode_radio(
client, live_server, measure_memory_usage, datastore_path):
"""
A group must also be able to append to the global prompt rather than replace it,
so the mode radio has to render on the tag edit page too. Re #4251.
"""
ds = client.application.config.get('DATASTORE')
_configure_llm(ds)
tag_uuid = ds.add_tag('Append Group')
res = client.get(url_for('tags.form_tag_edit', uuid=tag_uuid))
assert res.status_code == 200
body = res.data.decode('utf-8', errors='replace')
assert 'name="llm_change_summary_mode"' in body, \
"prompt mode radio missing from tag edit page"
assert 'value="replace"' in body
assert 'value="append"' in body
delete_all_watches(client)
def test_tag_append_mode_persists_and_applies(
client, live_server, measure_memory_usage, datastore_path):
"""A group set to append adds its text to the global prompt for its watches."""
from changedetectionio.llm.evaluator import get_effective_summary_prompt
ds = client.application.config.get('DATASTORE')
_configure_llm(ds)
ds.data['settings']['application']['llm']['change_summary_default'] = 'GLOBAL RULES'
api_token = _api_token(client)
test_url = url_for('test_endpoint', _external=True)
watch_uuid = _create_watch(client, test_url, api_token)
tag_uuid = _add_tag_with_llm(ds, 'Append Group', llm_change_summary='Group extra line.')
ds.data['settings']['application']['tags'][tag_uuid]['llm_change_summary_mode'] = 'append'
_link_watch_to_tag(ds, watch_uuid, tag_uuid)
watch = ds.data['watching'][watch_uuid]
assert get_effective_summary_prompt(watch, ds) == 'GLOBAL RULES\n\nGroup extra line.'
delete_all_watches(client)
@@ -3162,6 +3162,18 @@ msgstr "AI záměr změny"
msgid "AI Change Summary"
msgstr "AI souhrn změny"
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/JSONPath/JQ/xPath filtry"
@@ -4555,6 +4567,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3211,6 +3211,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/xPath-Filter"
@@ -4609,6 +4621,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3154,6 +3154,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr ""
@@ -4547,6 +4559,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3154,6 +3154,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr ""
@@ -4547,6 +4559,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3227,6 +3227,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Filtros CSS/JSONPath/JQ/XPath"
@@ -4624,6 +4636,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3167,6 +3167,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Filtre CSS/JSONPath/JQ/XPath"
@@ -4562,6 +4574,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3156,6 +3156,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Filtri CSS/JSONPath/JQ/XPath"
@@ -4549,6 +4561,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3173,6 +3173,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/JSONPath/JQ/XPath フィルタ"
@@ -4580,6 +4592,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3164,6 +3164,18 @@ msgstr "AI 판단 기준"
msgid "AI Change Summary"
msgstr "AI 변경 요약"
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/JSONPath/JQ/XPath 필터"
@@ -4557,6 +4569,18 @@ msgstr "변경이 감지되면 AI가 지시에 따라 이를 설명하고 알림
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr "이 그룹의 모든 모니터링 알림에서 변경 사항을 어떻게 요약할지 설명하세요."
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr "추가된 각 새 항목을 이름과 가격과 함께 나열하세요. 영어로 번역하세요."
@@ -3153,6 +3153,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr ""
@@ -4546,6 +4558,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3204,6 +3204,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Filtros CSS/JSONPath/JQ/XPath"
@@ -4599,6 +4611,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3272,6 +3272,18 @@ msgstr "Изменение намерения ИИ"
msgid "AI Change Summary"
msgstr "Обзор изменений ИИ"
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Фильтры CSS/JSONPath/JQ/XPath"
@@ -4687,6 +4699,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr "Опишите, как изменения должны отражаться в уведомлениях для всех часов в этой группе."
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr "Перечислите каждый новый добавленный товар с его названием и ценой. Перевести на английский."
@@ -3207,6 +3207,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/JSONPath/JQ/XPath Filtreleri"
@@ -4604,6 +4616,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3186,6 +3186,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "Фільтри CSS/JSONPath/JQ/XPath"
@@ -4581,6 +4593,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3160,6 +3160,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS/JSONPath/JQ/XPath 过滤器"
@@ -4555,6 +4567,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
@@ -3160,6 +3160,18 @@ msgstr ""
msgid "AI Change Summary"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "How this prompt combines with the inherited one"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Replace the inherited prompt"
msgstr ""
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
msgid "Append to the inherited prompt"
msgstr ""
#: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters"
msgstr "CSS / JSONPath / JQ / XPath 過濾器"
@@ -4553,6 +4565,18 @@ msgstr ""
msgid "Describe how changes should be summarised in notifications for all watches in this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from the group or from global settings and adds your text after it, so later "
"edits to that prompt still reach this watch."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid ""
"Appending keeps the prompt inherited from global settings and adds your text after it, so later edits to that prompt "
"still reach this group."
msgstr ""
#: changedetectionio/templates/edit/include_llm_intent.html
msgid "List each new item added with its name and price. Translate to English."
msgstr ""
+5
View File
@@ -605,6 +605,11 @@ components:
maxLength: 2000
default: ''
description: Instructions for the AI to summarise changes in notifications. When set, replaces {{diff}} with a human-readable description.
llm_change_summary_mode:
type: string
enum: ['replace', 'append']
default: 'replace'
description: Whether llm_change_summary replaces the prompt inherited from the tag/global settings, or is appended to the end of it.
llm_prefilter:
type: [string, 'null']
readOnly: true