Add conversation system message
This commit is contained in:
@@ -140,4 +140,10 @@
|
||||
<circle cx="17" cy="17" r="3" />
|
||||
<circle cx="7" cy="7" r="3" />
|
||||
</symbol>
|
||||
<symbol id="scroll-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 12h-5"/>
|
||||
<path d="M15 8h-5"/>
|
||||
<path d="M19 17V5a2 2 0 0 0-2-2H4"/>
|
||||
<path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.6 KiB |
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// ====================
|
||||
|
||||
@@ -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 =
|
||||
'<em class="message__placeholder">System Default</em>';
|
||||
$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 =
|
||||
'<em class="message__placeholder">System Default</em>';
|
||||
$msg.dataset.copyText = '';
|
||||
}
|
||||
|
||||
this.$chat.insertBefore($msg, this.$chatLoading);
|
||||
}
|
||||
|
||||
_populateDebugPanel($dl, meta) {
|
||||
if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice);
|
||||
if (meta?.usage) {
|
||||
|
||||
@@ -275,4 +275,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-system-message">
|
||||
<div class="message message--system">
|
||||
<div class="message__body">
|
||||
<details class="message__collapsible">
|
||||
<summary class="message__collapsible-summary">
|
||||
<svg class="message__icon message__icon--system" aria-hidden="true"><use href="/static/icons.svg#scroll-text"></use></svg>
|
||||
<span class="message__collapsible-label">System</span>
|
||||
</summary>
|
||||
<div class="message__content"></div>
|
||||
</details>
|
||||
<div class="message__actions">
|
||||
<svg class="message__icon" aria-hidden="true"><use href="/static/icons.svg#scroll-text"></use></svg>
|
||||
<div class="message__actions-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="edit"
|
||||
aria-label="Edit system message"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#edit"></use></svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="copy"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#copy"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user