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;