feat(#524): add compress affordance to context ring tooltip

When context usage reaches 50% (yellow), a subtle hint button appears
in the context ring tooltip suggesting /compress.  At 75%+ (red), the
hint intensifies with a warning style.

Clicking the button pre-fills /compress into the composer and focuses
it, so the user can add a focus topic or just hit send.  No auto-fire
— the user stays in control.

- static/ui.js: conditional visibility + click handler in _syncCtxIndicator
- static/index.html: ctxCompressBtn element inside ctxTooltip
- static/style.css: muted button style, red variant for ctx-high
- static/i18n.js: ctx_compress_hint / ctx_compress_action in all 7 locales

Closes #524
This commit is contained in:
bergeouss
2026-04-28 10:47:12 +00:00
committed by Hermes Agent
parent b0aed07fe0
commit 63dee0a87c
4 changed files with 52 additions and 0 deletions
+14
View File
@@ -69,6 +69,8 @@ const LOCALES = {
// commands.js
cmd_clear: 'Clear conversation messages',
cmd_compress: 'Manually compress conversation context (usage: /compress [focus topic])',
ctx_compress_hint: 'Compress context to free up space →',
ctx_compress_action: '⚠ Compress now to free context',
cmd_compact_alias: 'Legacy alias for /compress',
cmd_model: 'Switch model (e.g. /model gpt-4o)',
cmd_workspace: 'Switch workspace by name',
@@ -799,6 +801,8 @@ const LOCALES = {
clarify_send: 'Отправить',
cmd_compact_alias: 'Устаревший псевдоним для /compress',
cmd_compress: 'Сжать контекст беседы (использование: /compress [тема])',
ctx_compress_hint: 'Сжать контекст для освобождения места →',
ctx_compress_action: '⚠ Сжать сейчас для освобождения контекста',
command_label: 'Команда',
compress_complete_label: 'Сжатие завершено',
auto_compress_label: 'Автосжатие',
@@ -1389,6 +1393,8 @@ const LOCALES = {
cmd_help: 'Listar los comandos disponibles',
cmd_clear: 'Borrar los mensajes de la conversación',
cmd_compress: 'Comprimir manualmente el contexto de la conversación (uso: /compress [tema])',
ctx_compress_hint: 'Comprimir contexto para liberar espacio →',
ctx_compress_action: '⚠ Comprimir ahora para liberar contexto',
cmd_compact_alias: 'Alias antiguo de /compress',
cmd_compact: 'Comprimir contexto de la conversación',
cmd_model: 'Cambiar de modelo (p. ej. /model gpt-4o)',
@@ -2012,6 +2018,8 @@ const LOCALES = {
archive_extracted: (n, c) => `${n} Datei(en) aus ${c} Archiv(en) entpackt`,
cmd_clear: 'Konversationsverlauf löschen',
cmd_compress: 'Kontext manuell komprimieren (Nutzung: /compress [Thema])',
ctx_compress_hint: 'Kontext komprimieren um Platz zu schaffen →',
ctx_compress_action: '⚠ Jetzt komprimieren um Kontext freizugeben',
cmd_compact_alias: 'Alte Alias für /compress',
cmd_model: 'Modell wechseln (z.B. /model gpt-4o)',
cmd_workspace: 'Workspace nach Namen wechseln',
@@ -2416,6 +2424,8 @@ const LOCALES = {
cmd_help: '\u67e5\u770b\u53ef\u7528\u547d\u4ee4',
cmd_clear: '\u6e05\u7a7a\u5f53\u524d\u5bf9\u8bdd\u6d88\u606f',
cmd_compress: '\u624b\u52a8\u538b\u7f29\u5bf9\u8bdd\u4e0a\u4e0b\u6587\uff08\u7528\u6cd5\uff1a/compress [\u4e3b\u9898]\uff09',
ctx_compress_hint: '\u538b\u7f29\u4e0a\u4e0b\u6587\u4ee5\u91ca\u653e\u7a7a\u95f4 →',
ctx_compress_action: '\u26a0 \u7acb\u5373\u538b\u7f29\u4ee5\u91ca\u653e\u4e0a\u4e0b\u6587',
cmd_compact_alias: '\u65e7\u522b\u540d\uff1a/compress',
cmd_model: '\u5207\u6362\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09',
cmd_workspace: '\u6309\u540d\u79f0\u5207\u6362\u5de5\u4f5c\u533a',
@@ -3033,6 +3043,8 @@ const LOCALES = {
cmd_help: '\u67e5\u770b\u53ef\u7528\u547d\u4ee4',
cmd_clear: '\u6e05\u7a7a\u7576\u524d\u5c0d\u8a71\u8a0a\u606f',
cmd_compress: '\u624b\u52d5\u58d3\u7e2e\u5c0d\u8a71\u4e0a\u4e0b\u6587\uff08\u7528\u6cd5\uff1a/compress [\u4e3b\u984c]\uff09',
ctx_compress_hint: '\u58d3\u7e2e\u4e0a\u4e0b\u6587\u4ee5\u91cb\u653e\u7a7a\u9593 →',
ctx_compress_action: '\u26a0 \u7acb\u5373\u58d3\u7e2e\u4ee5\u91cb\u653e\u4e0a\u4e0b\u6587',
cmd_compact_alias: '\u820a\u5225\u540d\uff1a/compress',
cmd_model: '\u5207\u63db\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09',
cmd_workspace: '\u6309\u540d\u7a31\u5207\u63db\u5de5\u4f5c\u5340',
@@ -3712,6 +3724,8 @@ const LOCALES = {
// commands.js
cmd_clear: '대화 메시지 지우기',
cmd_compress: 'Manually compress conversation context (usage: /compress [focus topic])',
ctx_compress_hint: '\ucee8\ud14d\uc2a4\ud2b8 \uc555\ucd95\ud558\uba70 \uacf5\uac04 \ud655\ubcf4 →',
ctx_compress_action: '\u26a0 \uc9c0\uae08 \uc555\ucd95\ud558\uba70 \ucee8\ud14d\uc2a4\ud2b8 \ud655\ubcf4',
cmd_compact_alias: 'Legacy alias for /compress',
cmd_model: '모델 전환(예: /model gpt-4o)',
cmd_workspace: '이름으로 워크스페이스 전환',
+3
View File
@@ -413,6 +413,9 @@
<div class="ctx-tooltip-line" id="ctxTooltipTokens"></div>
<div class="ctx-tooltip-line" id="ctxTooltipThreshold"></div>
<div class="ctx-tooltip-line" id="ctxTooltipCost" style="display:none"></div>
<div class="ctx-tooltip-compress" id="ctxTooltipCompress" style="display:none">
<button class="ctx-compress-btn" id="ctxCompressBtn" type="button"></button>
</div>
</div>
</div>
<span class="bg-badge" id="bgBadge" style="display:none" title="Background tasks running">0</span>
+11
View File
@@ -726,6 +726,17 @@
.ctx-indicator-wrap:hover .ctx-tooltip,.ctx-indicator-wrap:focus-within .ctx-tooltip{opacity:1;transform:translateY(0);}
.ctx-tooltip-title{font-size:12px;font-weight:600;color:var(--text);margin-bottom:5px;}
.ctx-tooltip-line+.ctx-tooltip-line{margin-top:3px;}
<<<<<<< HEAD
=======
.ctx-tooltip-compress{margin-top:8px;padding-top:8px;border-top:1px solid var(--border2);}
.ctx-compress-btn{width:100%;padding:6px 10px;border:1px solid var(--border2);border-radius:8px;background:rgba(255,255,255,.05);color:var(--text);font-size:11px;cursor:pointer;text-align:left;transition:background .15s,border-color .15s;}
.ctx-compress-btn:hover{background:rgba(255,255,255,.1);border-color:var(--accent);}
.ctx-indicator.ctx-high .ctx-compress-btn{border-color:var(--error);color:var(--error);}
.ctx-indicator.ctx-high .ctx-compress-btn:hover{background:rgba(239,83,80,.12);}
.cancel-btn{width:34px;height:34px;border-radius:50%;background:var(--error);border:none;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,transform .15s,box-shadow .15s;box-shadow:0 2px 10px rgba(0,0,0,.18);}
.cancel-btn:hover{background:var(--error);transform:scale(1.06);box-shadow:0 4px 14px rgba(0,0,0,.25);filter:brightness(1.1);}
.cancel-btn:active{transform:scale(.96);}
>>>>>>> 0a0513c (feat(#524): add compress affordance to context ring tooltip)
.icon-btn{width:34px;height:34px;border-radius:8px;background:none;border:none;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .15s;}
.icon-btn{opacity:.75;}
.icon-btn:hover{background:rgba(255,255,255,.08);color:var(--text);opacity:1;}
+24
View File
@@ -641,6 +641,30 @@ function _syncCtxIndicator(usage){
if(center) center.textContent=hasCtxWindow?String(pct):'\u00b7';
el.classList.toggle('ctx-mid',pct>50&&pct<=75);
el.classList.toggle('ctx-high',pct>75);
// ── Compress affordance (#524) ──
// Show a hint in the tooltip when context usage is high so users
// discover /compress without having to know the slash command.
const compressWrap=$('ctxTooltipCompress');
const compressBtn=$('ctxCompressBtn');
if(compressWrap&&compressBtn){
if(pct>=75){
compressWrap.style.display='';
compressBtn.textContent=t('ctx_compress_action');
compressBtn.onclick=function(){
const ta=$('msg');
if(ta){ta.value='/compress ';ta.focus();autoResize();}
};
}else if(pct>=50){
compressWrap.style.display='';
compressBtn.textContent=t('ctx_compress_hint');
compressBtn.onclick=function(){
const ta=$('msg');
if(ta){ta.value='/compress ';ta.focus();autoResize();}
};
}else{
compressWrap.style.display='none';
}
}
let label=hasCtxWindow?`Context window ${pct}% used`:`${_fmtTokens(totalTok)} tokens used`;
if(cost) label+=` \u00b7 $${cost<0.01?cost.toFixed(4):cost.toFixed(2)}`;
el.setAttribute('aria-label',label);