Add max iterations
This commit is contained in:
@@ -446,6 +446,8 @@ type Request struct {
|
|||||||
Model string `json:"model,omitempty"`
|
Model string `json:"model,omitempty"`
|
||||||
// SystemMessage overrides the configured system message for this request.
|
// SystemMessage overrides the configured system message for this request.
|
||||||
SystemMessage string `json:"system_message,omitempty"`
|
SystemMessage string `json:"system_message,omitempty"`
|
||||||
|
// MaxIterations caps the number of agent loop turns (0 = unlimited).
|
||||||
|
MaxIterations int `json:"max_iterations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response is the response format for chat and voice endpoints.
|
// Response is the response format for chat and voice endpoints.
|
||||||
@@ -541,7 +543,7 @@ func (svc *Service) chat(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res, err := llmc.Completions(
|
res, err := llmc.Completions(
|
||||||
ctx, model, req.SystemMessage, 0, req.Messages,
|
ctx, model, req.SystemMessage, req.MaxIterations, req.Messages,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(
|
log.ErrorContext(
|
||||||
@@ -713,7 +715,7 @@ func (svc *Service) chatStream(w http.ResponseWriter, r *http.Request) {
|
|||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
errs <- llmc.CompletionsStream(
|
errs <- llmc.CompletionsStream(
|
||||||
ctx, model, req.SystemMessage, 0, req.Messages, events,
|
ctx, model, req.SystemMessage, req.MaxIterations, req.Messages, events,
|
||||||
)
|
)
|
||||||
close(errs)
|
close(errs)
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -134,4 +134,10 @@
|
|||||||
<path d="M7 15h6"/>
|
<path d="M7 15h6"/>
|
||||||
<path d="M7 7h8"/>
|
<path d="M7 7h8"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="settings-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M14 17H5" />
|
||||||
|
<path d="M19 7h-9" />
|
||||||
|
<circle cx="17" cy="17" r="3" />
|
||||||
|
<circle cx="7" cy="7" r="3" />
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -661,6 +661,18 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#compose-settings-close {
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
#compose-settings-close:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
.settings-tabs {
|
.settings-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -715,6 +727,14 @@ body {
|
|||||||
margin-bottom: var(--s-2);
|
margin-bottom: var(--s-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-label__sub {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--s-2);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-select {
|
.settings-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: var(--s2);
|
height: var(--s2);
|
||||||
@@ -735,6 +755,10 @@ body {
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-field {
|
||||||
|
margin-bottom: var(--s1);
|
||||||
|
}
|
||||||
|
|
||||||
.settings-textarea {
|
.settings-textarea {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { WakeLock } from './wakelock.js';
|
|||||||
|
|
||||||
const STREAM_ENDPOINT = '/v1/chat/voice/stream';
|
const STREAM_ENDPOINT = '/v1/chat/voice/stream';
|
||||||
const SYSTEM_MESSAGE_KEY = 'odidere_system_message';
|
const SYSTEM_MESSAGE_KEY = 'odidere_system_message';
|
||||||
|
const MAX_TURNS_KEY = 'odidere_max_turns';
|
||||||
const ICONS_URL = '/static/icons.svg';
|
const ICONS_URL = '/static/icons.svg';
|
||||||
|
|
||||||
const StreamEventDelta = 'delta';
|
const StreamEventDelta = 'delta';
|
||||||
@@ -102,6 +103,13 @@ class Odidere {
|
|||||||
this.$settingsClose = document.getElementById('settings-close');
|
this.$settingsClose = document.getElementById('settings-close');
|
||||||
this.$settingsTabs = document.querySelectorAll('.settings-tab');
|
this.$settingsTabs = document.querySelectorAll('.settings-tab');
|
||||||
this.$saveSystemMessage = document.getElementById('save-system-message');
|
this.$saveSystemMessage = document.getElementById('save-system-message');
|
||||||
|
this.$composeSettingsOverlay = document.getElementById(
|
||||||
|
'compose-settings-overlay',
|
||||||
|
);
|
||||||
|
this.$composeSettingsClose = document.getElementById(
|
||||||
|
'compose-settings-close',
|
||||||
|
);
|
||||||
|
this.$maxTurnsInput = document.getElementById('max-turns-input');
|
||||||
this.$compose = document.querySelector('.compose');
|
this.$compose = document.querySelector('.compose');
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
@@ -241,6 +249,18 @@ class Odidere {
|
|||||||
this.settings.saveSystemMessage(),
|
this.settings.saveSystemMessage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Compose settings ---
|
||||||
|
this.$composeSettingsClose.addEventListener('click', () =>
|
||||||
|
this._closeComposeSettings(),
|
||||||
|
);
|
||||||
|
this.$composeSettingsOverlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === this.$composeSettingsOverlay)
|
||||||
|
this._closeComposeSettings();
|
||||||
|
});
|
||||||
|
this.$maxTurnsInput.addEventListener('input', () => {
|
||||||
|
this._setMaxTurns(this.$maxTurnsInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
// --- Model/voice change persistence ---
|
// --- Model/voice change persistence ---
|
||||||
this.settings.$model.addEventListener('change', () =>
|
this.settings.$model.addEventListener('change', () =>
|
||||||
this.settings.saveModelSelection(),
|
this.settings.saveModelSelection(),
|
||||||
@@ -297,6 +317,9 @@ class Odidere {
|
|||||||
case 'settings':
|
case 'settings':
|
||||||
this.settings.open();
|
this.settings.open();
|
||||||
break;
|
break;
|
||||||
|
case 'compose-settings':
|
||||||
|
this._openComposeSettings();
|
||||||
|
break;
|
||||||
case 'attach':
|
case 'attach':
|
||||||
// Handled by direct listener on $attach.
|
// Handled by direct listener on $attach.
|
||||||
break;
|
break;
|
||||||
@@ -599,6 +622,10 @@ class Odidere {
|
|||||||
if (systemMessage) {
|
if (systemMessage) {
|
||||||
payload.system_message = systemMessage;
|
payload.system_message = systemMessage;
|
||||||
}
|
}
|
||||||
|
const maxTurns = this._getMaxTurns();
|
||||||
|
if (maxTurns > 0) {
|
||||||
|
payload.max_iterations = maxTurns;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear attachments and location after building payload.
|
// Clear attachments and location after building payload.
|
||||||
this.clearAttachments();
|
this.clearAttachments();
|
||||||
@@ -1100,6 +1127,30 @@ class Odidere {
|
|||||||
this.$location.classList.remove('compose__action-btn--location', 'active');
|
this.$location.classList.remove('compose__action-btn--location', 'active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// COMPOSE SETTINGS
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
_openComposeSettings() {
|
||||||
|
this.$maxTurnsInput.value = this._getMaxTurns();
|
||||||
|
this.$composeSettingsOverlay.classList.add('open');
|
||||||
|
this.$composeSettingsClose.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
_closeComposeSettings() {
|
||||||
|
this.$composeSettingsOverlay.classList.remove('open');
|
||||||
|
this.$composeSettings.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMaxTurns() {
|
||||||
|
const stored = localStorage.getItem(MAX_TURNS_KEY);
|
||||||
|
return stored ? Number(stored) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setMaxTurns(value) {
|
||||||
|
localStorage.setItem(MAX_TURNS_KEY, String(value));
|
||||||
|
}
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// TWO-PRESS SAFETY PATTERNS
|
// TWO-PRESS SAFETY PATTERNS
|
||||||
// ====================
|
// ====================
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
{{ template "templates" . }}
|
{{ template "templates" . }}
|
||||||
{{ template "modal/settings" . }}
|
{{ template "modal/settings" . }}
|
||||||
{{ template "modal/edit" . }}
|
{{ template "modal/edit" . }}
|
||||||
|
{{ template "modal/compose-settings" . }}
|
||||||
</body>
|
</body>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@
|
|||||||
<div class="compose__attachments" id="attachments"></div>
|
<div class="compose__attachments" id="attachments"></div>
|
||||||
<div class="compose__actions">
|
<div class="compose__actions">
|
||||||
<div class="compose__actions-left">
|
<div class="compose__actions-left">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="compose__action-btn"
|
||||||
|
id="compose-settings"
|
||||||
|
data-action="compose-settings"
|
||||||
|
aria-label="Compose settings"
|
||||||
|
>
|
||||||
|
<svg class="icon"><use href="/static/icons.svg#settings-2"></use></svg>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="compose__action-btn"
|
class="compose__action-btn"
|
||||||
|
|||||||
Reference in New Issue
Block a user