From 63dee0a87cce34cd0c75eff373be5f4e95b1540b Mon Sep 17 00:00:00 2001 From: bergeouss Date: Tue, 28 Apr 2026 10:47:12 +0000 Subject: [PATCH] feat(#524): add compress affordance to context ring tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- static/i18n.js | 14 ++++++++++++++ static/index.html | 3 +++ static/style.css | 11 +++++++++++ static/ui.js | 24 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/static/i18n.js b/static/i18n.js index c7964465..96cabd03 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -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: '이름으로 워크스페이스 전환', diff --git a/static/index.html b/static/index.html index 295cdf5b..ae6e2c56 100644 --- a/static/index.html +++ b/static/index.html @@ -413,6 +413,9 @@
+ diff --git a/static/style.css b/static/style.css index e2dd9c75..d3e818e5 100644 --- a/static/style.css +++ b/static/style.css @@ -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;} diff --git a/static/ui.js b/static/ui.js index ed197c9a..947a2175 100644 --- a/static/ui.js +++ b/static/ui.js @@ -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);