feat: Nachrichten als wichtig markieren (Stern-Button)

- MessageRecord: neues Property IsMarked
- DatabaseService: Migration is_marked, ToggleMessageMark(), GetMarkedMessages()
- HistoryEndpoint: PUT /api/messages/{id}/mark (Toggle)
- SystemPromptBuilder: markierte Nachrichten als Referenz im System-Prompt
- ChatEndpoint + SessionEndpoint: markierte Nachrichten laden und an Builder übergeben
- Frontend: Stern-Button (☆/★) auf jeder Nachricht, Toggle per API
- CSS: .marked-Klasse mit gelbem Seitenrand, Button-Styling
This commit is contained in:
Meleki committed 2026-03-01 01:30:21 +01:00
1 parent 21ca3a451b
commit 19f32442b1
11 files changed
+124 -13

No files matched your search

+5
View File
@@ -131,6 +131,11 @@ async function apiGetChain(chainId) {
return resp.json();
}
async function apiToggleMessageMark(messageId) {
const resp = await fetch(`/api/messages/${messageId}/mark`, { method: 'PUT' });
return resp.json();
}
async function apiGitStatus(sessionId) {
const resp = await fetch(`/api/sessions/${sessionId}/git-status`);
if (!resp.ok) return null;
+2 -2
View File
@@ -35,11 +35,11 @@ function renderMessages(msgs, addToConversation) {
switch (msg.type) {
case 'text':
if (msg.role === 'user') {
addUserBubble(msg.content, false, msg.createdAt);
addUserBubble(msg.content, false, msg.createdAt, msg.id, msg.isMarked);
if (msg.attachmentCount > 0) addAttachmentIndicator(msg.id, msg.attachmentCount, msg.attachmentNames);
if (addToConversation) messages.push({ role: 'user', content: msg.content });
} else if (msg.role === 'assistant') {
addAssistantBubble(msg.content, msg.createdAt);
addAssistantBubble(msg.content, msg.createdAt, msg.id, msg.isMarked);
if (addToConversation) messages.push({ role: 'assistant', content: msg.content });
}
break;
+30 -4
View File
@@ -33,6 +33,28 @@ function truncate(s, max) {
return s && s.length > max ? s.substring(0, max) + '...' : (s || '');
}
// ── Mark Button ──
function _addMarkButton(div, messageId, isMarked) {
if (!messageId) return; // kein DB-Eintrag → kein Button
const btn = document.createElement('button');
btn.className = 'msg-mark-btn icon-btn' + (isMarked ? ' marked' : '');
btn.innerHTML = isMarked ? '<i class="fa fa-star"></i>' : '<i class="fa fa-star-o"></i>';
btn.title = 'Als wichtig markieren';
btn.addEventListener('click', async (e) => {
e.stopPropagation();
try {
const result = await apiToggleMessageMark(messageId);
btn.classList.toggle('marked', result.isMarked);
btn.innerHTML = result.isMarked ? '<i class="fa fa-star"></i>' : '<i class="fa fa-star-o"></i>';
div.classList.toggle('marked', result.isMarked);
} catch (err) {
console.error('Mark toggle failed:', err);
}
});
div.prepend(btn);
}
// ── Reply Button ──
function _addReplyButton(div) {
@@ -129,25 +151,29 @@ function _renderUserTextContent(container, raw) {
container.appendChild(frag);
}
function addUserBubble(text, streamed = false, timestamp = '') {
function addUserBubble(text, streamed = false, timestamp = '', messageId = null, isMarked = false) {
const div = document.createElement('div');
div.className = 'msg user';
div.className = 'msg user' + (isMarked ? ' marked' : '');
div.dataset.role = 'user';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
if (streamed) div.classList.add('streamed');
renderUserContent(div, text);
_addReplyButton(div);
_addMarkButton(div, messageId, isMarked);
chatEl().appendChild(div);
scrollDown();
}
function addAssistantBubble(text, timestamp = '') {
function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = false) {
const div = document.createElement('div');
div.className = 'msg assistant markdown';
div.className = 'msg assistant markdown' + (isMarked ? ' marked' : '');
div.dataset.role = 'assistant';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
div.innerHTML = renderMarkdown(text);
_addReplyButton(div);
_addMarkButton(div, messageId, isMarked);
chatEl().appendChild(div);
scrollDown();
return div;