diff --git a/internal/service/static/icons.svg b/internal/service/static/icons.svg index f1bdf43..c06351b 100644 --- a/internal/service/static/icons.svg +++ b/internal/service/static/icons.svg @@ -140,4 +140,10 @@ + + + + + + diff --git a/internal/service/static/main.css b/internal/service/static/main.css index dce9059..8d155f8 100644 --- a/internal/service/static/main.css +++ b/internal/service/static/main.css @@ -1229,3 +1229,15 @@ body { white-space: pre-wrap; word-break: break-word; } + +.message--system .message__icon { + color: var(--color-text-muted); +} + +.message--system .message__icon--system { + color: var(--color-text-muted); +} + +.message__placeholder { + color: var(--color-text-muted); +} diff --git a/internal/service/static/main.js b/internal/service/static/main.js index 7325f25..440f176 100644 --- a/internal/service/static/main.js +++ b/internal/service/static/main.js @@ -83,6 +83,7 @@ class Odidere { this._currentRun = null; this.currentController = null; this.totalTokens = 0; + this.systemMessage = ''; // Attachments & location this.attachments = []; @@ -122,6 +123,8 @@ class Odidere { init() { this.conversation.load(); + this.systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY) || ''; + this.renderer.renderSystemMessage(this.systemMessage); if (this.conversation.size > 0) { this.renderer.renderMessages(this.conversation.getActivePath()); } @@ -172,6 +175,8 @@ class Odidere { this.conversation.clear(); this.renderer.setAutoScroll(true); this.renderer.clearChat(); + this.systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY) || ''; + this.renderer.renderSystemMessage(this.systemMessage); this.clearAttachments(); this.clearLocation(); this.$textInput.value = ''; @@ -628,9 +633,8 @@ class Odidere { const { provider, model } = this.settings.getModel(); const payload = { messages, provider, model }; - const systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY); - if (systemMessage) { - payload.system_message = systemMessage; + if (this.systemMessage) { + payload.system_message = this.systemMessage; } const maxTurns = this._getMaxTurns(); if (maxTurns > 0) { @@ -1342,10 +1346,67 @@ class Odidere { // ==================== async _openEdit(messageId, field, toolCallId) { + if (messageId === 'system') { + const saved = await this._openSystemEdit(); + if (saved) { + this.renderer.reRenderSystemMessage(this.systemMessage); + } + return; + } const saved = await this.editModal.open(messageId, field, toolCallId); if (saved) this.renderer.reRenderActivePath(); } + async _openSystemEdit() { + return new Promise((resolve) => { + const $tpl = this.document.getElementById('tpl-edit-modal'); + if (!$tpl) return resolve(false); + + const $modal = $tpl.content.cloneNode(true).firstElementChild; + const $overlay = this.document.createElement('div'); + $overlay.className = 'modal-overlay'; + $overlay.appendChild($modal); + + $modal.querySelector('h2').textContent = 'Edit System Message'; + + const textarea = $modal.querySelector('#edit-textarea'); + textarea.value = this.systemMessage || ''; + + const saveBtn = $modal.querySelector('#edit-save'); + const closeBtn = $modal.querySelector('.modal__close'); + + const close = () => { + $overlay.remove(); + this.document.removeEventListener('keydown', handleEscape); + resolve(false); + }; + + const save = () => { + const newText = textarea.value.trim(); + const settingsDefault = localStorage.getItem(SYSTEM_MESSAGE_KEY) || ''; + this.systemMessage = newText || settingsDefault; + $overlay.remove(); + this.document.removeEventListener('keydown', handleEscape); + resolve(true); + }; + + closeBtn.addEventListener('click', close); + saveBtn.addEventListener('click', save); + + $overlay.addEventListener('click', (e) => { + if (e.target === $overlay) close(); + }); + + const handleEscape = (e) => { + if (e.key === 'Escape') close(); + }; + this.document.addEventListener('keydown', handleEscape); + + this.document.body.appendChild($overlay); + textarea.focus(); + }); + } + // ==================== // STOP CONVERSATION // ==================== diff --git a/internal/service/static/render.js b/internal/service/static/render.js index ebfc6e4..0508834 100644 --- a/internal/service/static/render.js +++ b/internal/service/static/render.js @@ -41,6 +41,7 @@ export class Renderer { 'tpl-reasoning-message', ); this.$tplToolMessage = document.getElementById('tpl-tool-message'); + this.$tplSystemMessage = document.getElementById('tpl-system-message'); } // --- Full / incremental render --- @@ -611,6 +612,57 @@ export class Renderer { this.scrollToBottom(); } + /** + * renderSystemMessage renders the system message block. + * @param {string} content + */ + renderSystemMessage(content) { + this._renderSystemMessage(content); + } + + /** + * reRenderSystemMessage replaces the system message block in the DOM. + * @param {string} content + */ + reRenderSystemMessage(content) { + const $old = this.$chat.querySelector('[data-id="system"]'); + if (!$old) return; + + const $msg = + this.$tplSystemMessage.content.cloneNode(true).firstElementChild; + $msg.dataset.id = 'system'; + + const $content = $msg.querySelector('.message__content'); + if (content?.trim()) { + $content.textContent = content; + $msg.dataset.copyText = content; + } else { + $content.innerHTML = + 'System Default'; + $msg.dataset.copyText = ''; + } + + $old.replaceWith($msg); + } + + _renderSystemMessage(content, _meta = null) { + const $msg = + this.$tplSystemMessage.content.cloneNode(true).firstElementChild; + $msg.dataset.id = 'system'; + + const $content = $msg.querySelector('.message__content'); + if (content?.trim()) { + $content.textContent = content; + $msg.dataset.copyText = content; + } else { + $content.innerHTML = + 'System Default'; + $msg.dataset.copyText = ''; + } + + this.$chat.insertBefore($msg, this.$chatLoading); + } + _populateDebugPanel($dl, meta) { if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice); if (meta?.usage) { diff --git a/internal/service/templates/static/templates.gohtml b/internal/service/templates/static/templates.gohtml index 68ea0e5..f6bb369 100644 --- a/internal/service/templates/static/templates.gohtml +++ b/internal/service/templates/static/templates.gohtml @@ -275,4 +275,39 @@ + + {{ end }}