|
63 | 63 | {{ compaction_thinking_level|default('', true)|tojson|forceescape }}, |
64 | 64 | {{ vision_enabled|default('false', true)|tojson|forceescape }}, |
65 | 65 | {{ vision_provider|default('anthropic', true)|tojson|forceescape }}, |
66 | | - {{ vision_model|default('claude-haiku-4-5', true)|tojson|forceescape }} |
| 66 | + {{ vision_model|default('claude-haiku-4-5', true)|tojson|forceescape }}, |
| 67 | + {{ rd_enabled|default('false', true)|tojson|forceescape }}, |
| 68 | + {{ rd_provider|default('deepseek', true)|tojson|forceescape }}, |
| 69 | + {{ rd_model|default('deepseek-v4-flash', true)|tojson|forceescape }}, |
| 70 | + {{ rd_thinking_level|default('', true)|tojson|forceescape }}, |
| 71 | + {{ rd_group_only|default('true', true)|tojson|forceescape }}, |
| 72 | + {{ rd_max_replies|default('6', true)|tojson|forceescape }}, |
| 73 | + {{ rd_window_seconds|default('120', true)|tojson|forceescape }} |
67 | 74 | )"> |
68 | 75 | <div class="card"> |
69 | 76 | <h2 class="text-base mb-1">System Prompt Controls</h2> |
@@ -480,6 +487,89 @@ <h2 class="text-base mb-1">Task Reflection</h2> |
480 | 487 | </template> |
481 | 488 | </div> |
482 | 489 |
|
| 490 | + <div class="card"> |
| 491 | + <h2 class="text-base mb-1">Reply Decision</h2> |
| 492 | + <p class="text-muted text-xs mb-4">In shared/group chats with several bots and people, decide per message whether to reply at all — staying quiet for messages aimed at another bot, caught in a bot-to-bot reaction loop, or that the agent can't usefully add to. Off by default; uses a cheap/fast model for the yes/no call.</p> |
| 493 | + |
| 494 | + <form class="space-y-4" @submit.prevent> |
| 495 | + <div class="flex items-center gap-2"> |
| 496 | + <label class="label mb-0">Enabled</label> |
| 497 | + <input type="checkbox" x-model="rdEnabled" class="rounded border-border"> |
| 498 | + </div> |
| 499 | + |
| 500 | + <div> |
| 501 | + <label class="label">Provider</label> |
| 502 | + <select class="input-sm" style="max-width:500px" x-model="rdProvider" @change="resetBackgroundModel('rd')" x-effect="$el.value = rdProvider" :disabled="!rdEnabled"> |
| 503 | + <template x-for="item in providerOptions" :key="'rd-' + item.value"> |
| 504 | + <option :value="item.value" :selected="item.value === rdProvider" x-text="item.label"></option> |
| 505 | + </template> |
| 506 | + </select> |
| 507 | + </div> |
| 508 | + <div> |
| 509 | + <label class="label">Model</label> |
| 510 | + <input type="text" class="input-sm" style="max-width:500px" x-model="rdModel" :disabled="!rdEnabled" |
| 511 | + list="dl-model-rd" placeholder="Type or pick a model"> |
| 512 | + <datalist id="dl-model-rd"> |
| 513 | + <template x-for="item in modelOptions(rdProvider)" :key="'rd-m-' + item.value"> |
| 514 | + <option :value="item.value"></option> |
| 515 | + </template> |
| 516 | + </datalist> |
| 517 | + <p class="text-muted text-xs mt-1">Model used to decide whether a group-chat message warrants a reply.</p> |
| 518 | + </div> |
| 519 | +{{ think('rdThinkingLevel', 'rdProvider', 'rdModel', 'dl-think-rd', true) }} |
| 520 | + |
| 521 | + <div class="flex items-center gap-2"> |
| 522 | + <label class="label mb-0">Group chats only</label> |
| 523 | + <input type="checkbox" x-model="rdGroupOnly" class="rounded border-border" :disabled="!rdEnabled"> |
| 524 | + <span class="text-muted text-xs">When on, 1:1 chats always get a reply; only group chats are gated.</span> |
| 525 | + </div> |
| 526 | + <div class="flex flex-wrap gap-4"> |
| 527 | + <div> |
| 528 | + <label class="label">Max replies per window</label> |
| 529 | + <input type="number" min="1" class="input-sm" style="max-width:160px" x-model="rdMaxReplies" :disabled="!rdEnabled"> |
| 530 | + </div> |
| 531 | + <div> |
| 532 | + <label class="label">Window (seconds)</label> |
| 533 | + <input type="number" min="1" class="input-sm" style="max-width:160px" x-model="rdWindowSeconds" :disabled="!rdEnabled"> |
| 534 | + </div> |
| 535 | + </div> |
| 536 | + <p class="text-muted text-xs">Hard backstop: never send more than this many auto-replies into one chat per rolling window — guarantees a runaway loop terminates even if the model keeps voting to reply.</p> |
| 537 | + </form> |
| 538 | + |
| 539 | + <div class="flex items-center gap-2 mt-4"> |
| 540 | + <button class="btn-primary btn-sm" |
| 541 | + @click=" |
| 542 | + fetch('/config', { |
| 543 | + method: 'PATCH', |
| 544 | + headers: { |
| 545 | + 'Content-Type': 'application/json', |
| 546 | + 'Authorization': 'Bearer ' + (localStorage.getItem('admin_api_key') || '') |
| 547 | + }, |
| 548 | + body: JSON.stringify({values: { |
| 549 | + 'reply_decision.enabled': rdEnabled ? 'true' : 'false', |
| 550 | + 'reply_decision.provider': rdProvider, |
| 551 | + 'reply_decision.model': rdModel, |
| 552 | + 'reply_decision.thinking_level': rdThinkingLevel, |
| 553 | + 'reply_decision.group_only': rdGroupOnly ? 'true' : 'false', |
| 554 | + 'reply_decision.max_replies_per_window': String(rdMaxReplies), |
| 555 | + 'reply_decision.window_seconds': String(rdWindowSeconds) |
| 556 | + }}) |
| 557 | + }) |
| 558 | + .then(r => { rdResultOk = r.ok; return r.json(); }) |
| 559 | + .then(d => { |
| 560 | + rdResult = rdResultOk ? 'Reply decision settings saved' : (d.detail || 'Error'); |
| 561 | + if (rdResultOk && window.showToast) { window.showToast('Reply decision settings saved'); } |
| 562 | + }) |
| 563 | + .catch(e => { rdResultOk = false; rdResult = e.message; }) |
| 564 | + "> |
| 565 | + Save reply decision |
| 566 | + </button> |
| 567 | + </div> |
| 568 | + <template x-if="rdResult"> |
| 569 | + <div :class="rdResultOk ? 'alert-success' : 'alert-error'" class="mt-2" x-text="rdResult"></div> |
| 570 | + </template> |
| 571 | + </div> |
| 572 | + |
483 | 573 | <div class="card"> |
484 | 574 | <h2 class="text-base mb-1">History Compaction</h2> |
485 | 575 | <p class="text-muted text-xs mb-4">Model used to summarize older conversation turns when a session's context grows large. Enable compaction and set the trigger threshold in the <strong>History</strong> tab.</p> |
|
0 commit comments