Add conversation system message
This commit is contained in:
@@ -140,4 +140,10 @@
|
|||||||
<circle cx="17" cy="17" r="3" />
|
<circle cx="17" cy="17" r="3" />
|
||||||
<circle cx="7" cy="7" r="3" />
|
<circle cx="7" cy="7" r="3" />
|
||||||
</symbol>
|
</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>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.6 KiB |
@@ -1229,3 +1229,15 @@ body {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
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._currentRun = null;
|
||||||
this.currentController = null;
|
this.currentController = null;
|
||||||
this.totalTokens = 0;
|
this.totalTokens = 0;
|
||||||
|
this.systemMessage = '';
|
||||||
|
|
||||||
// Attachments & location
|
// Attachments & location
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
@@ -122,6 +123,8 @@ class Odidere {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.conversation.load();
|
this.conversation.load();
|
||||||
|
this.systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY) || '';
|
||||||
|
this.renderer.renderSystemMessage(this.systemMessage);
|
||||||
if (this.conversation.size > 0) {
|
if (this.conversation.size > 0) {
|
||||||
this.renderer.renderMessages(this.conversation.getActivePath());
|
this.renderer.renderMessages(this.conversation.getActivePath());
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,8 @@ class Odidere {
|
|||||||
this.conversation.clear();
|
this.conversation.clear();
|
||||||
this.renderer.setAutoScroll(true);
|
this.renderer.setAutoScroll(true);
|
||||||
this.renderer.clearChat();
|
this.renderer.clearChat();
|
||||||
|
this.systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY) || '';
|
||||||
|
this.renderer.renderSystemMessage(this.systemMessage);
|
||||||
this.clearAttachments();
|
this.clearAttachments();
|
||||||
this.clearLocation();
|
this.clearLocation();
|
||||||
this.$textInput.value = '';
|
this.$textInput.value = '';
|
||||||
@@ -628,9 +633,8 @@ class Odidere {
|
|||||||
|
|
||||||
const { provider, model } = this.settings.getModel();
|
const { provider, model } = this.settings.getModel();
|
||||||
const payload = { messages, provider, model };
|
const payload = { messages, provider, model };
|
||||||
const systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY);
|
if (this.systemMessage) {
|
||||||
if (systemMessage) {
|
payload.system_message = this.systemMessage;
|
||||||
payload.system_message = systemMessage;
|
|
||||||
}
|
}
|
||||||
const maxTurns = this._getMaxTurns();
|
const maxTurns = this._getMaxTurns();
|
||||||
if (maxTurns > 0) {
|
if (maxTurns > 0) {
|
||||||
@@ -1342,10 +1346,67 @@ class Odidere {
|
|||||||
// ====================
|
// ====================
|
||||||
|
|
||||||
async _openEdit(messageId, field, toolCallId) {
|
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);
|
const saved = await this.editModal.open(messageId, field, toolCallId);
|
||||||
if (saved) this.renderer.reRenderActivePath();
|
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
|
// STOP CONVERSATION
|
||||||
// ====================
|
// ====================
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export class Renderer {
|
|||||||
'tpl-reasoning-message',
|
'tpl-reasoning-message',
|
||||||
);
|
);
|
||||||
this.$tplToolMessage = document.getElementById('tpl-tool-message');
|
this.$tplToolMessage = document.getElementById('tpl-tool-message');
|
||||||
|
this.$tplSystemMessage = document.getElementById('tpl-system-message');
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Full / incremental render ---
|
// --- Full / incremental render ---
|
||||||
@@ -611,6 +612,57 @@ export class Renderer {
|
|||||||
this.scrollToBottom();
|
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) {
|
_populateDebugPanel($dl, meta) {
|
||||||
if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice);
|
if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice);
|
||||||
if (meta?.usage) {
|
if (meta?.usage) {
|
||||||
|
|||||||
@@ -275,4 +275,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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 }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user