Skip to content

Commit e1a9321

Browse files
webui: auto-refresh /props on inference start to resync model metadata
- Add no-cache headers to /props and /slots - Throttle slot checks to 30s - Prevent concurrent fetches with promise guard - Trigger refresh from chat streaming for legacy and ModelSelector - Show dynamic serverWarning when using cached data
1 parent bea0452 commit e1a9321

File tree

4 files changed

+213
-69
lines changed

4 files changed

+213
-69
lines changed

tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenWarning.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { AlertTriangle, RefreshCw } from '@lucide/svelte';
3-
import { serverLoading, serverStore } from '$lib/stores/server.svelte';
3+
import { serverLoading, serverStore, serverWarning } from '$lib/stores/server.svelte';
44
import { fly } from 'svelte/transition';
55
66
interface Props {
@@ -22,7 +22,7 @@
2222
<div class="flex items-center">
2323
<AlertTriangle class="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
2424
<p class="ml-2 text-sm text-yellow-800 dark:text-yellow-200">
25-
Server `/props` endpoint not available - using cached data
25+
{serverWarning() ?? 'Server `/props` endpoint not available - using cached data'}
2626
</p>
2727
</div>
2828
<button

tools/server/webui/src/lib/services/chat.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,9 +551,15 @@ export class ChatService {
551551
const currentConfig = config();
552552
const apiKey = currentConfig.apiKey?.toString().trim();
553553

554-
const response = await fetch(`./props`, {
554+
// Force a fresh network read so restarting the backend with a new model
555+
// immediately propagates updated props and modality information.
556+
const cacheBuster = Date.now().toString();
557+
const response = await fetch(`./props?cb=${cacheBuster}`, {
558+
cache: 'no-store',
555559
headers: {
556560
'Content-Type': 'application/json',
561+
'Cache-Control': 'no-cache',
562+
Pragma: 'no-cache',
557563
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
558564
}
559565
});

tools/server/webui/src/lib/stores/chat.svelte.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DatabaseStore } from '$lib/stores/database';
22
import { chatService, slotsService } from '$lib/services';
33
import { config } from '$lib/stores/settings.svelte';
4+
import { serverStore } from '$lib/stores/server.svelte';
45
import { normalizeModelName } from '$lib/utils/model-names';
56
import { filterByLeafNodeId, findLeafNode, findDescendantMessages } from '$lib/utils/branching';
67
import { browser } from '$app/environment';
@@ -362,8 +363,75 @@ class ChatStore {
362363

363364
let resolvedModel: string | null = null;
364365
let modelPersisted = false;
366+
const PROPS_REFRESH_RETRY_DELAY_MS = 1_000;
367+
let serverPropsRefreshRequested = false;
368+
let lastPropsRefreshAttempt = 0;
369+
370+
const resetPropsRefreshGate = (options?: { immediate?: boolean }) => {
371+
serverPropsRefreshRequested = false;
372+
if (options?.immediate) {
373+
lastPropsRefreshAttempt = Date.now() - PROPS_REFRESH_RETRY_DELAY_MS;
374+
} else {
375+
lastPropsRefreshAttempt = Date.now();
376+
}
377+
};
378+
379+
const ensureServerPropsRefresh = () => {
380+
const now = Date.now();
381+
382+
if (serverPropsRefreshRequested) {
383+
if (resolvedModel) {
384+
const currentModel = serverStore.modelName;
385+
const normalizedStoreModel = currentModel ? normalizeModelName(currentModel) : null;
386+
387+
if (!normalizedStoreModel || normalizedStoreModel !== resolvedModel) {
388+
resetPropsRefreshGate({ immediate: true });
389+
} else {
390+
return;
391+
}
392+
} else {
393+
return;
394+
}
395+
}
396+
397+
if (now - lastPropsRefreshAttempt < PROPS_REFRESH_RETRY_DELAY_MS) {
398+
return;
399+
}
400+
401+
serverPropsRefreshRequested = true;
402+
lastPropsRefreshAttempt = now;
403+
404+
const hasExistingProps = serverStore.serverProps !== null;
405+
406+
serverStore
407+
.fetchServerProps({ silent: hasExistingProps })
408+
.then(() => {
409+
if (!resolvedModel) {
410+
return;
411+
}
412+
413+
const currentModel = serverStore.modelName;
414+
415+
if (!currentModel) {
416+
resetPropsRefreshGate({ immediate: true });
417+
return;
418+
}
419+
420+
const normalizedStoreModel = normalizeModelName(currentModel);
421+
422+
if (!normalizedStoreModel || normalizedStoreModel !== resolvedModel) {
423+
resetPropsRefreshGate({ immediate: true });
424+
}
425+
})
426+
.catch((error) => {
427+
console.error('Failed to refresh server props during streaming:', error);
428+
resetPropsRefreshGate();
429+
});
430+
};
365431

366432
const recordModel = (modelName: string, persistImmediately = true): void => {
433+
ensureServerPropsRefresh();
434+
367435
const normalizedModel = normalizeModelName(modelName);
368436

369437
if (!normalizedModel || normalizedModel === resolvedModel) {
@@ -397,6 +465,8 @@ class ChatStore {
397465
...this.getApiOptions(),
398466

399467
onChunk: (chunk: string) => {
468+
ensureServerPropsRefresh();
469+
400470
streamedContent += chunk;
401471
this.setConversationStreaming(
402472
assistantMessage.convId,
@@ -411,6 +481,8 @@ class ChatStore {
411481
},
412482

413483
onReasoningChunk: (reasoningChunk: string) => {
484+
ensureServerPropsRefresh();
485+
414486
streamedReasoningContent += reasoningChunk;
415487

416488
const messageIndex = this.findMessageIndex(assistantMessage.id);
@@ -427,6 +499,8 @@ class ChatStore {
427499
reasoningContent?: string,
428500
timings?: ChatMessageTimings
429501
) => {
502+
ensureServerPropsRefresh();
503+
430504
slotsService.stopStreaming();
431505

432506
const updateData: {

0 commit comments

Comments
 (0)