feat: off the record mode (OTR)

Adds a third input mode toggle alongside Note and Agent. When active:
- Textarea gets a subtle purple tint with dashed border
- OTR button highlights purple
- Placeholder reads "Off the record — not logged or distilled…"
- off_record=True is sent to /chat; session_logger is skipped
- In-memory session context is preserved within the session

Switching to Note or Agent mode deactivates OTR, and vice versa.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-26 21:07:21 -04:00
parent b3f40cf437
commit fa04b5e6b0
4 changed files with 53 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ class ChatRequest(BaseModel):
include_long: bool = True
include_mid: bool = True
include_short: bool = True
off_record: bool = False # skip session log (in-memory context preserved)
user: str = "scott"
persona: str = "inara"
@@ -92,7 +93,8 @@ async def _stream_chat(req: ChatRequest):
response_text, actual_backend = task.result()
history.append({"role": "assistant", "content": response_text})
save_session(session_id, history)
log_turn(session_id, req.message, response_text)
if not req.off_record:
log_turn(session_id, req.message, response_text)
requested = req.model or settings.primary_backend
payload = {

View File

@@ -80,11 +80,35 @@
agentModeBtnEl.addEventListener('click', () => {
agentMode = !agentMode;
if (agentMode) { otr_mode = false; update_otr_ui(); }
localStorage.setItem('agentMode', agentMode);
updateAgentModeUI();
inputEl.focus();
});
// ── Off the record mode ──────────────────────────────────────
let otr_mode = false;
const otr_btn_el = document.getElementById('otr-btn');
function update_otr_ui() {
otr_btn_el.classList.toggle('active', otr_mode);
inputEl.classList.toggle('otr-mode', otr_mode);
updateInputPlaceholder();
}
otr_btn_el.addEventListener('click', () => {
otr_mode = !otr_mode;
if (otr_mode) {
agentMode = false;
noteMode = false;
localStorage.setItem('agentMode', false);
updateAgentModeUI();
updateInputMode();
}
update_otr_ui();
inputEl.focus();
});
// ── Note mode ────────────────────────────────────────────────
let noteMode = false;
let notePublic = false;
@@ -124,6 +148,8 @@
inputEl.placeholder = ctrlEnterMode
? `Task for ${personaLabel}… (Gemini tool loop — Ctrl+Enter to run)`
: `Task for ${personaLabel}… (Gemini tool loop)`;
} else if (otr_mode) {
inputEl.placeholder = `Off the record — not logged or distilled…`;
} else {
inputEl.placeholder = ctrlEnterMode
? `Message ${personaLabel}… (Ctrl+Enter to send)`
@@ -133,6 +159,7 @@
noteBtnEl.addEventListener('click', () => {
noteMode = !noteMode;
if (noteMode) { otr_mode = false; update_otr_ui(); }
updateInputMode();
inputEl.focus();
});
@@ -690,6 +717,7 @@
include_long: memLong,
include_mid: memMid,
include_short: memShort,
off_record: otr_mode,
user: CORTEX_USER,
persona: CORTEX_PERSONA,
}),

View File

@@ -131,6 +131,7 @@
<!-- Note mode controls -->
<button id="note-type-btn">private</button>
<button id="note-btn">Note</button>
<button id="otr-btn" title="Off the record — not logged or distilled into memory">OTR</button>
<button id="agent-mode-btn" title="Agent mode — Gemini tool loop + Claude response">Agent</button>
<button id="send">Send</button>
<button id="stop">Stop</button>

View File

@@ -571,6 +571,27 @@
#note-btn.active { border-color: rgba(180, 130, 40, 0.6); color: #c9a84c; }
#note-btn.active.public { border-color: rgba(40, 170, 150, 0.6); color: #4abfb0; }
/* OTR button */
#otr-btn {
background: var(--bg);
border: 1px solid var(--border);
color: var(--muted);
border-radius: 8px;
padding: 8px 0;
cursor: pointer;
font-size: 0.85rem;
text-align: center;
transition: border-color 0.15s, color 0.15s;
}
#otr-btn:hover { border-color: var(--muted); color: var(--text); }
#otr-btn.active { border-color: rgba(120, 80, 160, 0.6); color: #a87fd4; }
/* OTR textarea styling */
#input.otr-mode {
border-color: rgba(120, 80, 160, 0.4);
background: rgba(120, 80, 160, 0.04);
}
/* Send button */
#send {
background: var(--user-bg);