1import { PropsService } from '$lib/services/props';
  2import { ServerRole } from '$lib/enums';
  3
  4/**
  5 * serverStore - Server connection state, configuration, and role detection
  6 *
  7 * This store manages the server connection state and properties fetched from `/props`.
  8 * It provides reactive state for server configuration and role detection.
  9 *
 10 * **Architecture & Relationships:**
 11 * - **PropsService**: Stateless service for fetching `/props` data
 12 * - **serverStore** (this class): Reactive store for server state
 13 * - **modelsStore**: Independent store for model management (uses PropsService directly)
 14 *
 15 * **Key Features:**
 16 * - **Server State**: Connection status, loading, error handling
 17 * - **Role Detection**: MODEL (single model) vs ROUTER (multi-model)
 18 * - **Default Params**: Server-wide generation defaults
 19 */
 20class ServerStore {
 21	// ─────────────────────────────────────────────────────────────────────────────
 22	// State
 23	// ─────────────────────────────────────────────────────────────────────────────
 24
 25	props = $state<ApiLlamaCppServerProps | null>(null);
 26	loading = $state(false);
 27	error = $state<string | null>(null);
 28	role = $state<ServerRole | null>(null);
 29	private fetchPromise: Promise<void> | null = null;
 30
 31	// ─────────────────────────────────────────────────────────────────────────────
 32	// Getters
 33	// ─────────────────────────────────────────────────────────────────────────────
 34
 35	get defaultParams(): ApiLlamaCppServerProps['default_generation_settings']['params'] | null {
 36		return this.props?.default_generation_settings?.params || null;
 37	}
 38
 39	get contextSize(): number | null {
 40		return this.props?.default_generation_settings?.n_ctx ?? null;
 41	}
 42
 43	get webuiSettings(): Record<string, string | number | boolean> | undefined {
 44		return this.props?.webui_settings;
 45	}
 46
 47	get isRouterMode(): boolean {
 48		return this.role === ServerRole.ROUTER;
 49	}
 50
 51	get isModelMode(): boolean {
 52		return this.role === ServerRole.MODEL;
 53	}
 54
 55	// ─────────────────────────────────────────────────────────────────────────────
 56	// Data Handling
 57	// ─────────────────────────────────────────────────────────────────────────────
 58
 59	async fetch(): Promise<void> {
 60		if (this.fetchPromise) return this.fetchPromise;
 61
 62		this.loading = true;
 63		this.error = null;
 64
 65		const fetchPromise = (async () => {
 66			try {
 67				const props = await PropsService.fetch();
 68				this.props = props;
 69				this.error = null;
 70				this.detectRole(props);
 71			} catch (error) {
 72				this.error = this.getErrorMessage(error);
 73				console.error('Error fetching server properties:', error);
 74			} finally {
 75				this.loading = false;
 76				this.fetchPromise = null;
 77			}
 78		})();
 79
 80		this.fetchPromise = fetchPromise;
 81		await fetchPromise;
 82	}
 83
 84	private getErrorMessage(error: unknown): string {
 85		if (error instanceof Error) {
 86			const message = error.message || '';
 87
 88			if (error.name === 'TypeError' && message.includes('fetch')) {
 89				return 'Server is not running or unreachable';
 90			} else if (message.includes('ECONNREFUSED')) {
 91				return 'Connection refused - server may be offline';
 92			} else if (message.includes('ENOTFOUND')) {
 93				return 'Server not found - check server address';
 94			} else if (message.includes('ETIMEDOUT')) {
 95				return 'Request timed out';
 96			} else if (message.includes('503')) {
 97				return 'Server temporarily unavailable';
 98			} else if (message.includes('500')) {
 99				return 'Server error - check server logs';
100			} else if (message.includes('404')) {
101				return 'Server endpoint not found';
102			} else if (message.includes('403') || message.includes('401')) {
103				return 'Access denied';
104			}
105		}
106
107		return 'Failed to connect to server';
108	}
109
110	clear(): void {
111		this.props = null;
112		this.error = null;
113		this.loading = false;
114		this.role = null;
115		this.fetchPromise = null;
116	}
117
118	// ─────────────────────────────────────────────────────────────────────────────
119	// Utilities
120	// ─────────────────────────────────────────────────────────────────────────────
121
122	private detectRole(props: ApiLlamaCppServerProps): void {
123		const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
124		if (this.role !== newRole) {
125			this.role = newRole;
126			console.info(`Server running in ${newRole === ServerRole.ROUTER ? 'ROUTER' : 'MODEL'} mode`);
127		}
128	}
129}
130
131export const serverStore = new ServerStore();
132
133export const serverProps = () => serverStore.props;
134export const serverLoading = () => serverStore.loading;
135export const serverError = () => serverStore.error;
136export const serverRole = () => serverStore.role;
137export const defaultParams = () => serverStore.defaultParams;
138export const contextSize = () => serverStore.contextSize;
139export const isRouterMode = () => serverStore.isRouterMode;
140export const isModelMode = () => serverStore.isModelMode;