v0.17.17: timestamps on user messages, restore thumbs on AI (not web search), Shift+Enter triggers web search
This commit is contained in:
@@ -9,7 +9,7 @@ import logging
|
|||||||
|
|
||||||
log = logging.getLogger("caic")
|
log = logging.getLogger("caic")
|
||||||
|
|
||||||
VERSION = "v0.17.16"
|
VERSION = "v0.17.17"
|
||||||
OLLAMA_BASE = os.environ.get("OLLAMA_BASE", "http://localhost:11434")
|
OLLAMA_BASE = os.environ.get("OLLAMA_BASE", "http://localhost:11434")
|
||||||
LLAMA_SERVER_BASE = os.environ.get("LLAMA_SERVER_BASE", "http://192.168.50.108:8081")
|
LLAMA_SERVER_BASE = os.environ.get("LLAMA_SERVER_BASE", "http://192.168.50.108:8081")
|
||||||
SEARXNG_BASE = "http://localhost:8888"
|
SEARXNG_BASE = "http://localhost:8888"
|
||||||
|
|||||||
+26
-3
@@ -98,7 +98,7 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var(
|
|||||||
.badge.off { border-color:rgba(255,51,85,0.3); color:var(--danger); background:rgba(255,51,85,0.08); }
|
.badge.off { border-color:rgba(255,51,85,0.3); color:var(--danger); background:rgba(255,51,85,0.08); }
|
||||||
.badge.admin { border-color:var(--border); color:var(--text-muted); }
|
.badge.admin { border-color:var(--border); color:var(--text-muted); }
|
||||||
.badge.admin:hover { border-color:var(--accent-dim); color:var(--accent); }
|
.badge.admin:hover { border-color:var(--accent-dim); color:var(--accent); }
|
||||||
.status-dot { width:6px; height:6px; border-radius:50%; background:var(--success); display:inline-block; flex-shrink:0; }
|
.role-label .msg-time { color:var(--text-muted); font-weight:400; font-size:10px; margin-left:6px; }
|
||||||
.status-dot.offline { background:var(--danger); }
|
.status-dot.offline { background:var(--danger); }
|
||||||
.status-dot.warning { background:var(--warning); }
|
.status-dot.warning { background:var(--warning); }
|
||||||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||||||
@@ -409,7 +409,7 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var(
|
|||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input type="file" id="fileInput" style="display:none" accept=".txt,.md,.pdf,.json,.py,.html,.png,.jpg,.jpeg,.gif,.svg,.webp" onchange="onFileSelected(event)">
|
<input type="file" id="fileInput" style="display:none" accept=".txt,.md,.pdf,.json,.py,.html,.png,.jpg,.jpeg,.gif,.svg,.webp" onchange="onFileSelected(event)">
|
||||||
<button class="paperclip-btn" id="paperclipBtn" onclick="document.getElementById('fileInput').click()" title="Attach file">📎</button>
|
<button class="paperclip-btn" id="paperclipBtn" onclick="document.getElementById('fileInput').click()" title="Attach file">📎</button>
|
||||||
<textarea id="userInput" placeholder="Type a message... (Shift+Enter for new line)" rows="1" autofocus></textarea>
|
<textarea id="userInput" placeholder="Type a message... (Shift+Enter to search, Enter to send)" rows="1" autofocus></textarea>
|
||||||
<button class="send-btn" id="sendBtn" onclick="sendMessage()">SEND</button>
|
<button class="send-btn" id="sendBtn" onclick="sendMessage()">SEND</button>
|
||||||
<button class="search-btn" id="searchBtn" onclick="sendSearch()" title="Search the web" style="display:none">🔍</button>
|
<button class="search-btn" id="searchBtn" onclick="sendSearch()" title="Search the web" style="display:none">🔍</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1408,7 +1408,9 @@ function appendMessage(role, content, animate, isSearch = false, afterEl = null)
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'message ' + role + (isSearch && role === 'assistant' ? ' search-result' : '');
|
div.className = 'message ' + role + (isSearch && role === 'assistant' ? ' search-result' : '');
|
||||||
if (!animate) div.style.animation = 'none';
|
if (!animate) div.style.animation = 'none';
|
||||||
div.innerHTML = `<div class="avatar">${role === 'user' ? 'YOU' : 'AI'}</div><div class="content"><div class="role-label">${role}</div><div class="text">${content ? renderMarkdown(content) : ''}</div>${role === 'assistant' ? '<div class="msg-toolbar"></div>' : ''}</div>`;
|
const now = new Date();
|
||||||
|
const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
div.innerHTML = `<div class="avatar">${role === 'user' ? 'YOU' : 'AI'}</div><div class="content"><div class="role-label">${role}${role === 'user' ? ' <span class="msg-time">' + timeStr + '</span>' : ''}</div><div class="text">${content ? renderMarkdown(content) : ''}</div>${role === 'assistant' ? '<div class="msg-toolbar"></div>' : ''}</div>`;
|
||||||
if (afterEl) {
|
if (afterEl) {
|
||||||
afterEl.parentNode.insertBefore(div, afterEl.nextSibling);
|
afterEl.parentNode.insertBefore(div, afterEl.nextSibling);
|
||||||
} else {
|
} else {
|
||||||
@@ -1499,6 +1501,26 @@ function addMessageToolbar(msgDiv) {
|
|||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
};
|
};
|
||||||
toolbar.append(copyBtn, printBtn, saveBtn, sep());
|
toolbar.append(copyBtn, printBtn, saveBtn, sep());
|
||||||
|
if (!msgDiv.classList.contains('search-result')) {
|
||||||
|
const rating = { value: 0 };
|
||||||
|
const upBtn = document.createElement('button');
|
||||||
|
upBtn.innerHTML = '👍';
|
||||||
|
upBtn.title = 'Rate up';
|
||||||
|
upBtn.onclick = () => {
|
||||||
|
rating.value = rating.value === 1 ? 0 : 1;
|
||||||
|
upBtn.classList.toggle('active', rating.value === 1);
|
||||||
|
dnBtn.classList.toggle('active', false);
|
||||||
|
};
|
||||||
|
const dnBtn = document.createElement('button');
|
||||||
|
dnBtn.innerHTML = '👎';
|
||||||
|
dnBtn.title = 'Rate down';
|
||||||
|
dnBtn.onclick = () => {
|
||||||
|
rating.value = rating.value === -1 ? 0 : -1;
|
||||||
|
dnBtn.classList.toggle('active', rating.value === -1);
|
||||||
|
upBtn.classList.toggle('active', false);
|
||||||
|
};
|
||||||
|
toolbar.append(upBtn, dnBtn);
|
||||||
|
}
|
||||||
function sep() { const s = document.createElement('span'); s.className = 'sep'; return s; }
|
function sep() { const s = document.createElement('span'); s.className = 'sep'; return s; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1525,6 +1547,7 @@ let historyDraft = '';
|
|||||||
userInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 200) + 'px'; });
|
userInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 200) + 'px'; });
|
||||||
userInput.addEventListener('keydown', e => {
|
userInput.addEventListener('keydown', e => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); return; }
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); return; }
|
||||||
|
if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); sendSearch(); return; }
|
||||||
if (e.key === 'ArrowUp') {
|
if (e.key === 'ArrowUp') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (messageHistory.length === 0) return;
|
if (messageHistory.length === 0) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user