Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"channels": ["general"],
"history_limit": "all",
"contrast": "normal",
"show_msg_numbers": False,
"custom_roles": [],
}

Expand Down Expand Up @@ -1251,6 +1252,8 @@ async def websocket_endpoint(websocket: WebSocket):
pass
if "contrast" in new and new["contrast"] in ("normal", "high"):
room_settings["contrast"] = new["contrast"]
if "show_msg_numbers" in new:
room_settings["show_msg_numbers"] = bool(new["show_msg_numbers"])
if "rules_refresh_interval" in new:
try:
ri = int(new["rules_refresh_interval"])
Expand Down
14 changes: 12 additions & 2 deletions static/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ function appendMessage(msg) {
).join('') + '</div>';
}
}
el.innerHTML = `<div class="todo-strip"></div>${isSelf ? '' : avatarHtml}<div class="chat-bubble" style="--bubble-color: ${senderColor}">${replyHtml}<div class="bubble-header"><span class="msg-sender" style="color: ${senderColor}">${escapeHtml(msg.sender)}</span>${rolePillHtml}<span class="msg-time">${msg.time || ''}</span></div><div class="msg-text">${textHtml}</div>${choicesHtml}${attachmentsHtml}<button class="convert-job-pill" onclick="startJobFromMessage(${msg.id}); event.stopPropagation();" title="Convert to job">convert to job</button><button class="bubble-copy" onclick="copyMessage(${msg.id}, event)" title="Copy message"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div><div class="msg-actions"><button class="reply-btn" onclick="startReply(${msg.id}, event)">reply</button><button class="todo-hint" onclick="todoCycle(${msg.id}); event.stopPropagation();">${statusLabel}</button><button class="delete-btn" onclick="deleteClick(${msg.id}, event)" title="Delete">del</button></div>`;
el.innerHTML = `<div class="todo-strip"></div>${isSelf ? '' : avatarHtml}<div class="chat-bubble" style="--bubble-color: ${senderColor}">${replyHtml}<div class="bubble-header"><span class="msg-sender" style="color: ${senderColor}">${escapeHtml(msg.sender)}</span>${rolePillHtml}<span class="msg-time">${msg.time || ''}</span><span class="msg-num">#${msg.id}</span></div><div class="msg-text">${textHtml}</div>${choicesHtml}${attachmentsHtml}<button class="convert-job-pill" onclick="startJobFromMessage(${msg.id}); event.stopPropagation();" title="Convert to job">convert to job</button><button class="bubble-copy" onclick="copyMessage(${msg.id}, event)" title="Copy message"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div><div class="msg-actions"><button class="reply-btn" onclick="startReply(${msg.id}, event)">reply</button><button class="todo-hint" onclick="todoCycle(${msg.id}); event.stopPropagation();">${statusLabel}</button><button class="delete-btn" onclick="deleteClick(${msg.id}, event)" title="Delete">del</button></div>`;
if (todoStatus) el.classList.add('msg-todo', `msg-todo-${todoStatus}`);
if (msg.metadata?.session_output) el.classList.add('session-output');

Expand Down Expand Up @@ -1754,6 +1754,10 @@ function applySettings(data) {
document.body.classList.toggle('high-contrast', data.contrast === 'high');
document.getElementById('setting-contrast').value = data.contrast;
}
if (data.show_msg_numbers !== undefined) {
document.body.classList.toggle('show-msg-numbers', !!data.show_msg_numbers);
document.getElementById('setting-msg-numbers').value = data.show_msg_numbers ? 'show' : 'hide';
}
if (data.rules_refresh_interval !== undefined) {
document.getElementById('setting-rules-refresh').value = String(data.rules_refresh_interval);
}
Expand Down Expand Up @@ -1862,6 +1866,7 @@ function saveSettings() {
const histVal = document.getElementById('setting-history').value;
const newHistory = histVal === 'all' ? 'all' : (parseInt(histVal) || 50);
const newContrast = document.getElementById('setting-contrast').value;
const newMsgNumbers = document.getElementById('setting-msg-numbers').value === 'show';
const newRulesRefresh = document.getElementById('setting-rules-refresh').value;

if (ws && ws.readyState === WebSocket.OPEN) {
Expand All @@ -1873,6 +1878,7 @@ function saveSettings() {
max_agent_hops: parseInt(newHops) || 4,
history_limit: newHistory,
contrast: newContrast,
show_msg_numbers: newMsgNumbers,
rules_refresh_interval: parseInt(newRulesRefresh) || 0,
}
}));
Expand All @@ -1896,13 +1902,17 @@ function setupSettingsKeys() {
}

// Auto-save on change for selects, escape to close
for (const id of ['setting-font', 'setting-history', 'setting-contrast', 'setting-rules-refresh']) {
for (const id of ['setting-font', 'setting-history', 'setting-contrast', 'setting-msg-numbers', 'setting-rules-refresh']) {
const el = document.getElementById(id);
el.addEventListener('change', () => {
// Apply contrast immediately (don't wait for server round-trip)
if (id === 'setting-contrast') {
document.body.classList.toggle('high-contrast', el.value === 'high');
}
// Apply msg-numbers immediately too
if (id === 'setting-msg-numbers') {
document.body.classList.toggle('show-msg-numbers', el.value === 'show');
}
saveSettings();
});
el.addEventListener('keydown', (e) => {
Expand Down
7 changes: 7 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ <h1 id="room-title">agentchattr</h1>
<option value="high">High</option>
</select>
</div>
<div class="settings-field">
<label for="setting-msg-numbers">Msg numbers</label>
<select id="setting-msg-numbers" title="Show each message's id (#id) in its header - the same number agents use to reference messages">
<option value="hide">Hide</option>
<option value="show">Show</option>
</select>
</div>
<div class="settings-field">
<label for="setting-channel-sidebar">Channels</label>
<select id="setting-channel-sidebar" title="Show channels in a sidebar (Discord/Slack-style) instead of a top bar">
Expand Down
14 changes: 14 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,20 @@ body.channels-in-sidebar #channel-bar {
color: var(--text-dim);
}

/* Message id badge (toggled via Settings -> "Msg numbers"). Always rendered so
the id stays in the DOM; visibility is gated by a body class. */
.msg-num {
font-size: 11px;
color: var(--text-dim);
font-variant-numeric: tabular-nums;
opacity: 0.6;
display: none;
}

body.show-msg-numbers .msg-num {
display: inline;
}

.msg-sender {
font-weight: 700;
font-size: 13px;
Expand Down