feat: add configurable generation timeout
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -44,6 +44,34 @@ func New(s store.Store, dataDir string, logger *logrus.Logger, llmEndpoint, llmK
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeSettings(settings *store.Settings) {
|
||||||
|
// Default Text Generation Timeout
|
||||||
|
if settings.TextGenerationTimeoutMinutes == 0 {
|
||||||
|
settings.TextGenerationTimeoutMinutes = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Text Generation Timeout
|
||||||
|
switch settings.TextGenerationTimeoutMinutes {
|
||||||
|
case 1, 5, 10, 15, 30:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
settings.TextGenerationTimeoutMinutes = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) textGenerationTimeout() time.Duration {
|
||||||
|
// Load Settings
|
||||||
|
settings, err := a.store.GetSettings()
|
||||||
|
if err != nil {
|
||||||
|
a.logger.WithError(err).Error("failed to retrieve settings for text generation timeout")
|
||||||
|
return 5 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize Timeout
|
||||||
|
normalizeSettings(settings)
|
||||||
|
return time.Duration(settings.TextGenerationTimeoutMinutes) * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
func (a *API) GetSettings(w http.ResponseWriter, r *http.Request) {
|
func (a *API) GetSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
log := a.logger.WithField("handler", "GetSettingsHandler")
|
log := a.logger.WithField("handler", "GetSettingsHandler")
|
||||||
|
|
||||||
@@ -54,6 +82,9 @@ func (a *API) GetSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize Settings
|
||||||
|
normalizeSettings(settings)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(settings); err != nil {
|
if err := json.NewEncoder(w).Encode(settings); err != nil {
|
||||||
log.WithError(err).Error("failed to encode application settings response")
|
log.WithError(err).Error("failed to encode application settings response")
|
||||||
@@ -72,6 +103,9 @@ func (a *API) PostSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize Settings
|
||||||
|
normalizeSettings(&newSettings)
|
||||||
|
|
||||||
if err := a.store.SaveSettings(&newSettings); err != nil {
|
if err := a.store.SaveSettings(&newSettings); err != nil {
|
||||||
log.WithError(err).Error("failed to save settings")
|
log.WithError(err).Error("failed to save settings")
|
||||||
http.Error(w, "Failed to save application settings", http.StatusInternalServerError)
|
http.Error(w, "Failed to save application settings", http.StatusInternalServerError)
|
||||||
@@ -589,7 +623,7 @@ func (a *API) startMessageGeneration(chatID uuid.UUID, chatModel, userMessage st
|
|||||||
|
|
||||||
func (a *API) runMessageGeneration(apiClient *client.Client, chat *store.Chat, assistantMsg *store.Message, chatModel string, gen *generation) {
|
func (a *API) runMessageGeneration(apiClient *client.Client, chat *store.Chat, assistantMsg *store.Message, chatModel string, gen *generation) {
|
||||||
// Create Generation Context
|
// Create Generation Context
|
||||||
ctx, cancel := context.WithTimeout(gen.ctx, time.Minute*5)
|
ctx, cancel := context.WithTimeout(gen.ctx, a.textGenerationTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Send Message
|
// Send Message
|
||||||
|
|||||||
@@ -141,9 +141,10 @@ func TestInMemoryStore_SaveSettings(t *testing.T) {
|
|||||||
store := NewInMemoryStore()
|
store := NewInMemoryStore()
|
||||||
|
|
||||||
settings := &Settings{
|
settings := &Settings{
|
||||||
ImageEditSelector: ".image-edit",
|
ImageEditSelector: ".image-edit",
|
||||||
ImageGenerationSelector: ".image-gen",
|
ImageGenerationSelector: ".image-gen",
|
||||||
TextGenerationSelector: ".text-gen",
|
TextGenerationSelector: ".text-gen",
|
||||||
|
TextGenerationTimeoutMinutes: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.SaveSettings(settings)
|
err := store.SaveSettings(settings)
|
||||||
@@ -160,9 +161,10 @@ func TestInMemoryStore_GetSettings(t *testing.T) {
|
|||||||
|
|
||||||
// Set some settings
|
// Set some settings
|
||||||
settings = &Settings{
|
settings = &Settings{
|
||||||
ImageEditSelector: ".image-edit",
|
ImageEditSelector: ".image-edit",
|
||||||
ImageGenerationSelector: ".image-gen",
|
ImageGenerationSelector: ".image-gen",
|
||||||
TextGenerationSelector: ".text-gen",
|
TextGenerationSelector: ".text-gen",
|
||||||
|
TextGenerationTimeoutMinutes: 10,
|
||||||
}
|
}
|
||||||
err = store.SaveSettings(settings)
|
err = store.SaveSettings(settings)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -171,4 +173,5 @@ func TestInMemoryStore_GetSettings(t *testing.T) {
|
|||||||
settings, err = store.GetSettings()
|
settings, err = store.GetSettings()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, ".image-edit", settings.ImageEditSelector)
|
assert.Equal(t, ".image-edit", settings.ImageEditSelector)
|
||||||
|
assert.Equal(t, 10, settings.TextGenerationTimeoutMinutes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ var _ Store = (*FileStore)(nil)
|
|||||||
|
|
||||||
// Settings represents the application settings
|
// Settings represents the application settings
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
ImageEditSelector string `json:"image_edit_selector,omitempty"`
|
ImageEditSelector string `json:"image_edit_selector,omitempty"`
|
||||||
ImageGenerationSelector string `json:"image_generation_selector,omitempty"`
|
ImageGenerationSelector string `json:"image_generation_selector,omitempty"`
|
||||||
TextGenerationSelector string `json:"text_generation_selector,omitempty"`
|
TextGenerationSelector string `json:"text_generation_selector,omitempty"`
|
||||||
|
TextGenerationTimeoutMinutes int `json:"text_generation_timeout_minutes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileStore implements the Store interface using a file-based storage
|
// FileStore implements the Store interface using a file-based storage
|
||||||
|
|||||||
@@ -209,9 +209,10 @@ func TestFileStore_SaveSettings(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
settings := &Settings{
|
settings := &Settings{
|
||||||
ImageEditSelector: ".image-edit",
|
ImageEditSelector: ".image-edit",
|
||||||
ImageGenerationSelector: ".image-gen",
|
ImageGenerationSelector: ".image-gen",
|
||||||
TextGenerationSelector: ".text-gen",
|
TextGenerationSelector: ".text-gen",
|
||||||
|
TextGenerationTimeoutMinutes: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.SaveSettings(settings)
|
err = store.SaveSettings(settings)
|
||||||
@@ -236,9 +237,10 @@ func TestFileStore_GetSettings(t *testing.T) {
|
|||||||
|
|
||||||
// Set some settings
|
// Set some settings
|
||||||
settings = &Settings{
|
settings = &Settings{
|
||||||
ImageEditSelector: ".image-edit",
|
ImageEditSelector: ".image-edit",
|
||||||
ImageGenerationSelector: ".image-gen",
|
ImageGenerationSelector: ".image-gen",
|
||||||
TextGenerationSelector: ".text-gen",
|
TextGenerationSelector: ".text-gen",
|
||||||
|
TextGenerationTimeoutMinutes: 10,
|
||||||
}
|
}
|
||||||
err = store.SaveSettings(settings)
|
err = store.SaveSettings(settings)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -247,4 +249,5 @@ func TestFileStore_GetSettings(t *testing.T) {
|
|||||||
settings, err = store.GetSettings()
|
settings, err = store.GetSettings()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, ".image-edit", settings.ImageEditSelector)
|
assert.Equal(t, ".image-edit", settings.ImageEditSelector)
|
||||||
|
assert.Equal(t, 10, settings.TextGenerationTimeoutMinutes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="text-sm font-medium font-semibold text-primary-700"
|
||||||
|
>Generation</span
|
||||||
|
>
|
||||||
|
<div class="flex flex-col md:flex-row pl-1 gap-4 justify-between">
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<label
|
||||||
|
for="textGenerationTimeout"
|
||||||
|
class="text-sm font-medium text-primary-700"
|
||||||
|
>Chat Timeout</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="textGenerationTimeout"
|
||||||
|
name="textGenerationTimeout"
|
||||||
|
x-model.number="settings.text_generation_timeout_minutes"
|
||||||
|
class="mt-1 p-1 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||||
|
>
|
||||||
|
<template x-for="minutes in timeoutOptions" x-bind:key="minutes">
|
||||||
|
<option x-bind:value="minutes" x-text="`${minutes}m`"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
<p class="mt-2 text-xs text-primary-500">
|
||||||
|
Maximum time a chat response can stream before timing out
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
x-show="error"
|
x-show="error"
|
||||||
class="bg-tertiary-50 border border-tertiary-200 rounded-md p-4"
|
class="bg-tertiary-50 border border-tertiary-200 rounded-md p-4"
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { Settings } from '../types';
|
|||||||
|
|
||||||
Alpine.data('settingsManager', () => ({
|
Alpine.data('settingsManager', () => ({
|
||||||
settings: {} as Settings,
|
settings: {} as Settings,
|
||||||
|
timeoutOptions: [1, 5, 10, 15, 30],
|
||||||
loading: false,
|
loading: false,
|
||||||
saved: false,
|
saved: false,
|
||||||
error: '',
|
error: '',
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.settings = await getSettings();
|
this.settings = await getSettings();
|
||||||
|
this.settings.text_generation_timeout_minutes ||= 5;
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface Settings {
|
|||||||
image_edit_selector?: string;
|
image_edit_selector?: string;
|
||||||
image_generation_selector?: string;
|
image_generation_selector?: string;
|
||||||
text_generation_selector?: string;
|
text_generation_selector?: string;
|
||||||
|
text_generation_timeout_minutes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageRecord {
|
export interface ImageRecord {
|
||||||
|
|||||||
Reference in New Issue
Block a user