Skip to content

Commit 1c489c0

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 46adc26 commit 1c489c0

File tree

4 files changed

+215
-69
lines changed

4 files changed

+215
-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
@@ -684,9 +684,15 @@ export class ChatService {
684684
const currentConfig = config();
685685
const apiKey = currentConfig.apiKey?.toString().trim();
686686

687-
const response = await fetch(`./props`, {
687+
// Force a fresh network read so restarting the backend with a new model
688+
// immediately propagates updated props and modality information.
689+
const cacheBuster = Date.now().toString();
690+
const response = await fetch(`./props?cb=${cacheBuster}`, {
691+
cache: 'no-store',
688692
headers: {
689693
'Content-Type': 'application/json',
694+
'Cache-Control': 'no-cache',
695+
Pragma: 'no-cache',
690696
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
691697
}
692698
});

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

Lines changed: 76 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';
@@ -364,8 +365,75 @@ class ChatStore {
364365

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

368434
const recordModel = (modelName: string, persistImmediately = true): void => {
435+
ensureServerPropsRefresh();
436+
369437
const normalizedModel = normalizeModelName(modelName);
370438

371439
if (!normalizedModel || normalizedModel === resolvedModel) {
@@ -399,6 +467,8 @@ class ChatStore {
399467
...this.getApiOptions(),
400468

401469
onChunk: (chunk: string) => {
470+
ensureServerPropsRefresh();
471+
402472
streamedContent += chunk;
403473
this.setConversationStreaming(
404474
assistantMessage.convId,
@@ -413,6 +483,8 @@ class ChatStore {
413483
},
414484

415485
onReasoningChunk: (reasoningChunk: string) => {
486+
ensureServerPropsRefresh();
487+
416488
streamedReasoningContent += reasoningChunk;
417489

418490
const messageIndex = this.findMessageIndex(assistantMessage.id);
@@ -425,6 +497,8 @@ class ChatStore {
425497
},
426498

427499
onToolCallChunk: (toolCallChunk: string) => {
500+
ensureServerPropsRefresh();
501+
428502
const chunk = toolCallChunk.trim();
429503

430504
if (!chunk) {
@@ -444,6 +518,8 @@ class ChatStore {
444518
timings?: ChatMessageTimings,
445519
toolCallContent?: string
446520
) => {
521+
ensureServerPropsRefresh();
522+
447523
slotsService.stopStreaming();
448524

449525
const updateData: {

0 commit comments

Comments
 (0)