Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Thomas G. Lopes
commited on
Commit
·
af1f386
1
Parent(s):
52c6f5c
switch models api route to use remote functions
Browse files- src/{routes/api/models/+server.ts → lib/remote/models.remote.ts} +57 -53
- src/lib/state/models.svelte.ts +12 -6
- src/lib/utils/debug.ts +11 -0
- src/routes/+page.ts +0 -47
- svelte.config.js +3 -0
src/{routes/api/models/+server.ts → lib/remote/models.remote.ts}
RENAMED
@@ -1,6 +1,40 @@
|
|
1 |
-
import
|
2 |
-
import {
|
3 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
enum CacheStatus {
|
6 |
SUCCESS = "success",
|
@@ -12,8 +46,7 @@ type Cache = {
|
|
12 |
data: Model[] | undefined;
|
13 |
timestamp: number;
|
14 |
status: CacheStatus;
|
15 |
-
|
16 |
-
failedTokenizers: string[]; // Using array instead of Set for serialization compatibility
|
17 |
failedApiCalls: {
|
18 |
textGeneration: boolean;
|
19 |
imageTextToText: boolean;
|
@@ -31,9 +64,8 @@ const cache: Cache = {
|
|
31 |
},
|
32 |
};
|
33 |
|
34 |
-
// The time between cache refreshes
|
35 |
const FULL_CACHE_REFRESH = 1000 * 60 * 60; // 1 hour
|
36 |
-
const PARTIAL_CACHE_REFRESH = 1000 * 60 * 15; // 15 minutes
|
37 |
|
38 |
const headers: HeadersInit = {
|
39 |
"Upgrade-Insecure-Requests": "1",
|
@@ -73,14 +105,12 @@ const baseUrl = "https://huggingface.co/api/models";
|
|
73 |
function buildApiUrl(params: ApiQueryParams): string {
|
74 |
const url = new URL(baseUrl);
|
75 |
|
76 |
-
// Add simple params
|
77 |
Object.entries(params).forEach(([key, value]) => {
|
78 |
if (!Array.isArray(value) && value !== undefined) {
|
79 |
url.searchParams.append(key, String(value));
|
80 |
}
|
81 |
});
|
82 |
|
83 |
-
// Handle array params specially
|
84 |
params.expand.forEach(item => {
|
85 |
url.searchParams.append("expand[]", item);
|
86 |
});
|
@@ -88,10 +118,7 @@ function buildApiUrl(params: ApiQueryParams): string {
|
|
88 |
return url.toString();
|
89 |
}
|
90 |
|
91 |
-
async function fetchAllModelsWithPagination(
|
92 |
-
pipeline_tag: "text-generation" | "image-text-to-text",
|
93 |
-
fetch: typeof globalThis.fetch,
|
94 |
-
): Promise<Model[]> {
|
95 |
const allModels: Model[] = [];
|
96 |
let skip = 0;
|
97 |
const batchSize = 1000;
|
@@ -113,46 +140,33 @@ async function fetchAllModelsWithPagination(
|
|
113 |
const models: Model[] = await response.json();
|
114 |
|
115 |
if (models.length === 0) {
|
116 |
-
break;
|
117 |
}
|
118 |
|
119 |
allModels.push(...models);
|
120 |
skip += batchSize;
|
121 |
|
122 |
-
// Optional: Add a small delay to be respectful to the API
|
123 |
await new Promise(resolve => setTimeout(resolve, 100));
|
124 |
}
|
125 |
|
126 |
return allModels;
|
127 |
}
|
128 |
|
129 |
-
export
|
130 |
-
models: Model[];
|
131 |
-
};
|
132 |
-
|
133 |
-
function createResponse(data: ApiModelsResponse): Response {
|
134 |
-
return json(data);
|
135 |
-
}
|
136 |
-
|
137 |
-
export const GET: RequestHandler = async ({ fetch }) => {
|
138 |
const timestamp = Date.now();
|
139 |
|
140 |
-
// Determine if cache is valid
|
141 |
const elapsed = timestamp - cache.timestamp;
|
142 |
const cacheRefreshTime = cache.status === CacheStatus.SUCCESS ? FULL_CACHE_REFRESH : PARTIAL_CACHE_REFRESH;
|
143 |
|
144 |
-
// Use cache if it's still valid and has data
|
145 |
if (elapsed < cacheRefreshTime && cache.data?.length) {
|
146 |
-
|
147 |
-
return
|
148 |
}
|
149 |
|
150 |
try {
|
151 |
-
// Determine which API calls we need to make based on cache status
|
152 |
const needTextGenFetch = elapsed >= FULL_CACHE_REFRESH || cache.failedApiCalls.textGeneration;
|
153 |
const needImgTextFetch = elapsed >= FULL_CACHE_REFRESH || cache.failedApiCalls.imageTextToText;
|
154 |
|
155 |
-
// Track the existing models we'll keep
|
156 |
const existingModels = new Map<string, Model>();
|
157 |
if (cache.data) {
|
158 |
cache.data.forEach(model => {
|
@@ -160,27 +174,24 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
160 |
});
|
161 |
}
|
162 |
|
163 |
-
// Initialize new tracking for failed requests
|
164 |
const newFailedTokenizers: string[] = [];
|
165 |
const newFailedApiCalls = {
|
166 |
textGeneration: false,
|
167 |
imageTextToText: false,
|
168 |
};
|
169 |
|
170 |
-
// Fetch models as needed
|
171 |
let textGenModels: Model[] = [];
|
172 |
let imgText2TextModels: Model[] = [];
|
173 |
|
174 |
-
// Make the needed API calls in parallel
|
175 |
const apiPromises: Promise<void>[] = [];
|
176 |
if (needTextGenFetch) {
|
177 |
apiPromises.push(
|
178 |
-
fetchAllModelsWithPagination("text-generation"
|
179 |
.then(models => {
|
180 |
textGenModels = models;
|
181 |
})
|
182 |
.catch(error => {
|
183 |
-
|
184 |
newFailedApiCalls.textGeneration = true;
|
185 |
}),
|
186 |
);
|
@@ -188,12 +199,12 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
188 |
|
189 |
if (needImgTextFetch) {
|
190 |
apiPromises.push(
|
191 |
-
fetchAllModelsWithPagination("image-text-to-text"
|
192 |
.then(models => {
|
193 |
imgText2TextModels = models;
|
194 |
})
|
195 |
.catch(error => {
|
196 |
-
|
197 |
newFailedApiCalls.imageTextToText = true;
|
198 |
}),
|
199 |
);
|
@@ -201,7 +212,6 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
201 |
|
202 |
await Promise.all(apiPromises);
|
203 |
|
204 |
-
// If both needed API calls failed and we have cached data, use it
|
205 |
if (
|
206 |
needTextGenFetch &&
|
207 |
newFailedApiCalls.textGeneration &&
|
@@ -209,14 +219,13 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
209 |
newFailedApiCalls.imageTextToText &&
|
210 |
cache.data?.length
|
211 |
) {
|
212 |
-
|
213 |
cache.status = CacheStatus.ERROR;
|
214 |
-
cache.timestamp = timestamp;
|
215 |
cache.failedApiCalls = newFailedApiCalls;
|
216 |
-
return
|
217 |
}
|
218 |
|
219 |
-
// For API calls we didn't need to make, use cached models
|
220 |
if (!needTextGenFetch && cache.data) {
|
221 |
textGenModels = cache.data.filter(model => model.pipeline_tag === "text-generation").map(model => model as Model);
|
222 |
}
|
@@ -232,9 +241,7 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
232 |
);
|
233 |
models.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
|
234 |
|
235 |
-
// Determine cache status based on failures
|
236 |
const hasApiFailures = newFailedApiCalls.textGeneration || newFailedApiCalls.imageTextToText;
|
237 |
-
|
238 |
const cacheStatus = hasApiFailures ? CacheStatus.PARTIAL : CacheStatus.SUCCESS;
|
239 |
|
240 |
cache.data = models;
|
@@ -243,34 +250,31 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
|
243 |
cache.failedTokenizers = newFailedTokenizers;
|
244 |
cache.failedApiCalls = newFailedApiCalls;
|
245 |
|
246 |
-
|
247 |
`Cache updated: ${models.length} models, status: ${cacheStatus}, ` +
|
248 |
`failed tokenizers: ${newFailedTokenizers.length}, ` +
|
249 |
`API failures: text=${newFailedApiCalls.textGeneration}, img=${newFailedApiCalls.imageTextToText}`,
|
250 |
);
|
251 |
|
252 |
-
return
|
253 |
} catch (error) {
|
254 |
-
|
255 |
|
256 |
-
// If we have cached data, use it as fallback
|
257 |
if (cache.data?.length) {
|
258 |
cache.status = CacheStatus.ERROR;
|
259 |
-
// Mark all API calls as failed so we retry them next time
|
260 |
cache.failedApiCalls = {
|
261 |
textGeneration: true,
|
262 |
imageTextToText: true,
|
263 |
};
|
264 |
-
return
|
265 |
}
|
266 |
|
267 |
-
// No cache available, return empty array
|
268 |
cache.status = CacheStatus.ERROR;
|
269 |
cache.timestamp = timestamp;
|
270 |
cache.failedApiCalls = {
|
271 |
textGeneration: true,
|
272 |
imageTextToText: true,
|
273 |
};
|
274 |
-
return
|
275 |
}
|
276 |
-
};
|
|
|
1 |
+
import { query } from "$app/server";
|
2 |
+
import type { Provider, Model } from "$lib/types.js";
|
3 |
+
import { debugError, debugLog } from "$lib/utils/debug.js";
|
4 |
+
|
5 |
+
export type RouterData = {
|
6 |
+
object: string;
|
7 |
+
data: Datum[];
|
8 |
+
};
|
9 |
+
|
10 |
+
type Datum = {
|
11 |
+
id: string;
|
12 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
13 |
+
object: any;
|
14 |
+
created: number;
|
15 |
+
owned_by: string;
|
16 |
+
providers: ProviderElement[];
|
17 |
+
};
|
18 |
+
|
19 |
+
type ProviderElement = {
|
20 |
+
provider: Provider;
|
21 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
22 |
+
status: any;
|
23 |
+
context_length?: number;
|
24 |
+
pricing?: Pricing;
|
25 |
+
supports_tools?: boolean;
|
26 |
+
supports_structured_output?: boolean;
|
27 |
+
};
|
28 |
+
|
29 |
+
type Pricing = {
|
30 |
+
input: number;
|
31 |
+
output: number;
|
32 |
+
};
|
33 |
+
|
34 |
+
export const getRouterData = query(async (): Promise<RouterData> => {
|
35 |
+
const res = await fetch("https://router.huggingface.co/v1/models");
|
36 |
+
return res.json();
|
37 |
+
});
|
38 |
|
39 |
enum CacheStatus {
|
40 |
SUCCESS = "success",
|
|
|
46 |
data: Model[] | undefined;
|
47 |
timestamp: number;
|
48 |
status: CacheStatus;
|
49 |
+
failedTokenizers: string[];
|
|
|
50 |
failedApiCalls: {
|
51 |
textGeneration: boolean;
|
52 |
imageTextToText: boolean;
|
|
|
64 |
},
|
65 |
};
|
66 |
|
|
|
67 |
const FULL_CACHE_REFRESH = 1000 * 60 * 60; // 1 hour
|
68 |
+
const PARTIAL_CACHE_REFRESH = 1000 * 60 * 15; // 15 minutes
|
69 |
|
70 |
const headers: HeadersInit = {
|
71 |
"Upgrade-Insecure-Requests": "1",
|
|
|
105 |
function buildApiUrl(params: ApiQueryParams): string {
|
106 |
const url = new URL(baseUrl);
|
107 |
|
|
|
108 |
Object.entries(params).forEach(([key, value]) => {
|
109 |
if (!Array.isArray(value) && value !== undefined) {
|
110 |
url.searchParams.append(key, String(value));
|
111 |
}
|
112 |
});
|
113 |
|
|
|
114 |
params.expand.forEach(item => {
|
115 |
url.searchParams.append("expand[]", item);
|
116 |
});
|
|
|
118 |
return url.toString();
|
119 |
}
|
120 |
|
121 |
+
async function fetchAllModelsWithPagination(pipeline_tag: "text-generation" | "image-text-to-text"): Promise<Model[]> {
|
|
|
|
|
|
|
122 |
const allModels: Model[] = [];
|
123 |
let skip = 0;
|
124 |
const batchSize = 1000;
|
|
|
140 |
const models: Model[] = await response.json();
|
141 |
|
142 |
if (models.length === 0) {
|
143 |
+
break;
|
144 |
}
|
145 |
|
146 |
allModels.push(...models);
|
147 |
skip += batchSize;
|
148 |
|
|
|
149 |
await new Promise(resolve => setTimeout(resolve, 100));
|
150 |
}
|
151 |
|
152 |
return allModels;
|
153 |
}
|
154 |
|
155 |
+
export const getModels = query(async (): Promise<Model[]> => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
const timestamp = Date.now();
|
157 |
|
|
|
158 |
const elapsed = timestamp - cache.timestamp;
|
159 |
const cacheRefreshTime = cache.status === CacheStatus.SUCCESS ? FULL_CACHE_REFRESH : PARTIAL_CACHE_REFRESH;
|
160 |
|
|
|
161 |
if (elapsed < cacheRefreshTime && cache.data?.length) {
|
162 |
+
debugLog(`Using ${cache.status} cache (${Math.floor(elapsed / 1000 / 60)} min old)`);
|
163 |
+
return cache.data;
|
164 |
}
|
165 |
|
166 |
try {
|
|
|
167 |
const needTextGenFetch = elapsed >= FULL_CACHE_REFRESH || cache.failedApiCalls.textGeneration;
|
168 |
const needImgTextFetch = elapsed >= FULL_CACHE_REFRESH || cache.failedApiCalls.imageTextToText;
|
169 |
|
|
|
170 |
const existingModels = new Map<string, Model>();
|
171 |
if (cache.data) {
|
172 |
cache.data.forEach(model => {
|
|
|
174 |
});
|
175 |
}
|
176 |
|
|
|
177 |
const newFailedTokenizers: string[] = [];
|
178 |
const newFailedApiCalls = {
|
179 |
textGeneration: false,
|
180 |
imageTextToText: false,
|
181 |
};
|
182 |
|
|
|
183 |
let textGenModels: Model[] = [];
|
184 |
let imgText2TextModels: Model[] = [];
|
185 |
|
|
|
186 |
const apiPromises: Promise<void>[] = [];
|
187 |
if (needTextGenFetch) {
|
188 |
apiPromises.push(
|
189 |
+
fetchAllModelsWithPagination("text-generation")
|
190 |
.then(models => {
|
191 |
textGenModels = models;
|
192 |
})
|
193 |
.catch(error => {
|
194 |
+
debugError(`Error fetching text-generation models:`, error);
|
195 |
newFailedApiCalls.textGeneration = true;
|
196 |
}),
|
197 |
);
|
|
|
199 |
|
200 |
if (needImgTextFetch) {
|
201 |
apiPromises.push(
|
202 |
+
fetchAllModelsWithPagination("image-text-to-text")
|
203 |
.then(models => {
|
204 |
imgText2TextModels = models;
|
205 |
})
|
206 |
.catch(error => {
|
207 |
+
debugError(`Error fetching image-text-to-text models:`, error);
|
208 |
newFailedApiCalls.imageTextToText = true;
|
209 |
}),
|
210 |
);
|
|
|
212 |
|
213 |
await Promise.all(apiPromises);
|
214 |
|
|
|
215 |
if (
|
216 |
needTextGenFetch &&
|
217 |
newFailedApiCalls.textGeneration &&
|
|
|
219 |
newFailedApiCalls.imageTextToText &&
|
220 |
cache.data?.length
|
221 |
) {
|
222 |
+
debugLog("All API requests failed. Using existing cache as fallback.");
|
223 |
cache.status = CacheStatus.ERROR;
|
224 |
+
cache.timestamp = timestamp;
|
225 |
cache.failedApiCalls = newFailedApiCalls;
|
226 |
+
return cache.data;
|
227 |
}
|
228 |
|
|
|
229 |
if (!needTextGenFetch && cache.data) {
|
230 |
textGenModels = cache.data.filter(model => model.pipeline_tag === "text-generation").map(model => model as Model);
|
231 |
}
|
|
|
241 |
);
|
242 |
models.sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
|
243 |
|
|
|
244 |
const hasApiFailures = newFailedApiCalls.textGeneration || newFailedApiCalls.imageTextToText;
|
|
|
245 |
const cacheStatus = hasApiFailures ? CacheStatus.PARTIAL : CacheStatus.SUCCESS;
|
246 |
|
247 |
cache.data = models;
|
|
|
250 |
cache.failedTokenizers = newFailedTokenizers;
|
251 |
cache.failedApiCalls = newFailedApiCalls;
|
252 |
|
253 |
+
debugLog(
|
254 |
`Cache updated: ${models.length} models, status: ${cacheStatus}, ` +
|
255 |
`failed tokenizers: ${newFailedTokenizers.length}, ` +
|
256 |
`API failures: text=${newFailedApiCalls.textGeneration}, img=${newFailedApiCalls.imageTextToText}`,
|
257 |
);
|
258 |
|
259 |
+
return models;
|
260 |
} catch (error) {
|
261 |
+
debugError("Error fetching models:", error);
|
262 |
|
|
|
263 |
if (cache.data?.length) {
|
264 |
cache.status = CacheStatus.ERROR;
|
|
|
265 |
cache.failedApiCalls = {
|
266 |
textGeneration: true,
|
267 |
imageTextToText: true,
|
268 |
};
|
269 |
+
return cache.data;
|
270 |
}
|
271 |
|
|
|
272 |
cache.status = CacheStatus.ERROR;
|
273 |
cache.timestamp = timestamp;
|
274 |
cache.failedApiCalls = {
|
275 |
textGeneration: true,
|
276 |
imageTextToText: true,
|
277 |
};
|
278 |
+
return [];
|
279 |
}
|
280 |
+
});
|
src/lib/state/models.svelte.ts
CHANGED
@@ -1,22 +1,27 @@
|
|
1 |
-
import { page } from "$app/state";
|
2 |
import { type CustomModel, type Model } from "$lib/types.js";
|
3 |
import { edit, randomPick } from "$lib/utils/array.js";
|
4 |
import { safeParse } from "$lib/utils/json.js";
|
5 |
import typia from "typia";
|
6 |
-
import type { PageData } from "../../routes/$types.js";
|
7 |
import { conversations } from "./conversations.svelte";
|
|
|
8 |
|
9 |
const LOCAL_STORAGE_KEY = "hf_inference_playground_custom_models";
|
10 |
|
11 |
-
const pageData = $derived(page.data as PageData);
|
12 |
-
|
13 |
class Models {
|
14 |
-
|
|
|
15 |
trending = $derived(this.remote.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
|
16 |
nonTrending = $derived(this.remote.filter(m => !this.trending.includes(m)));
|
17 |
all = $derived([...this.remote, ...this.custom]);
|
18 |
|
19 |
constructor() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
const savedData = localStorage.getItem(LOCAL_STORAGE_KEY);
|
21 |
if (!savedData) return;
|
22 |
|
@@ -69,8 +74,9 @@ class Models {
|
|
69 |
}
|
70 |
|
71 |
supportsStructuredOutput(model: Model | CustomModel, provider?: string) {
|
|
|
72 |
if (typia.is<CustomModel>(model)) return true;
|
73 |
-
const routerDataEntry =
|
74 |
if (!routerDataEntry) return false;
|
75 |
return routerDataEntry.providers.find(p => p.provider === provider)?.supports_structured_output ?? false;
|
76 |
}
|
|
|
|
|
1 |
import { type CustomModel, type Model } from "$lib/types.js";
|
2 |
import { edit, randomPick } from "$lib/utils/array.js";
|
3 |
import { safeParse } from "$lib/utils/json.js";
|
4 |
import typia from "typia";
|
|
|
5 |
import { conversations } from "./conversations.svelte";
|
6 |
+
import { getModels, getRouterData, type RouterData } from "$lib/remote/models.remote";
|
7 |
|
8 |
const LOCAL_STORAGE_KEY = "hf_inference_playground_custom_models";
|
9 |
|
|
|
|
|
10 |
class Models {
|
11 |
+
routerData = $state<RouterData>();
|
12 |
+
remote: Model[] = $state([]);
|
13 |
trending = $derived(this.remote.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
|
14 |
nonTrending = $derived(this.remote.filter(m => !this.trending.includes(m)));
|
15 |
all = $derived([...this.remote, ...this.custom]);
|
16 |
|
17 |
constructor() {
|
18 |
+
getModels().then(models => {
|
19 |
+
this.remote = models;
|
20 |
+
});
|
21 |
+
getRouterData().then(data => {
|
22 |
+
this.routerData = data;
|
23 |
+
});
|
24 |
+
|
25 |
const savedData = localStorage.getItem(LOCAL_STORAGE_KEY);
|
26 |
if (!savedData) return;
|
27 |
|
|
|
74 |
}
|
75 |
|
76 |
supportsStructuredOutput(model: Model | CustomModel, provider?: string) {
|
77 |
+
if (!this.routerData) return false;
|
78 |
if (typia.is<CustomModel>(model)) return true;
|
79 |
+
const routerDataEntry = this.routerData?.data.find(d => d.id === model.id);
|
80 |
if (!routerDataEntry) return false;
|
81 |
return routerDataEntry.providers.find(p => p.provider === provider)?.supports_structured_output ?? false;
|
82 |
}
|
src/lib/utils/debug.ts
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const DEBUG_LOG = true;
|
2 |
+
|
3 |
+
export const debugLog = (...args: unknown[]) => {
|
4 |
+
if (!DEBUG_LOG) return;
|
5 |
+
console.log("[LOG DEBUG]", ...args);
|
6 |
+
};
|
7 |
+
|
8 |
+
export const debugError = (...args: unknown[]) => {
|
9 |
+
if (!DEBUG_LOG) return;
|
10 |
+
console.error("[LOG DEBUG]", ...args);
|
11 |
+
};
|
src/routes/+page.ts
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
import type { Provider } from "$lib/types.js";
|
2 |
-
import type { PageLoad } from "./$types.js";
|
3 |
-
import type { ApiModelsResponse } from "./api/models/+server.js";
|
4 |
-
|
5 |
-
export type RouterData = {
|
6 |
-
object: string;
|
7 |
-
data: Datum[];
|
8 |
-
};
|
9 |
-
|
10 |
-
type Datum = {
|
11 |
-
id: string;
|
12 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
13 |
-
object: any;
|
14 |
-
created: number;
|
15 |
-
owned_by: string;
|
16 |
-
providers: ProviderElement[];
|
17 |
-
};
|
18 |
-
|
19 |
-
type ProviderElement = {
|
20 |
-
provider: Provider;
|
21 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
22 |
-
status: any;
|
23 |
-
context_length?: number;
|
24 |
-
pricing?: Pricing;
|
25 |
-
supports_tools?: boolean;
|
26 |
-
supports_structured_output?: boolean;
|
27 |
-
};
|
28 |
-
|
29 |
-
type Pricing = {
|
30 |
-
input: number;
|
31 |
-
output: number;
|
32 |
-
};
|
33 |
-
|
34 |
-
export const load: PageLoad = async ({ fetch }) => {
|
35 |
-
const [modelsRes, routerRes] = await Promise.all([
|
36 |
-
fetch("/api/models"),
|
37 |
-
fetch("https://router.huggingface.co/v1/models"),
|
38 |
-
]);
|
39 |
-
|
40 |
-
const models: ApiModelsResponse = await modelsRes.json();
|
41 |
-
const routerData = (await routerRes.json()) as RouterData;
|
42 |
-
|
43 |
-
return {
|
44 |
-
...models,
|
45 |
-
routerData,
|
46 |
-
};
|
47 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
svelte.config.js
CHANGED
@@ -12,6 +12,9 @@ const config = {
|
|
12 |
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
13 |
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
14 |
adapter: adapter(),
|
|
|
|
|
|
|
15 |
},
|
16 |
|
17 |
compilerOptions: {
|
|
|
12 |
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
13 |
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
14 |
adapter: adapter(),
|
15 |
+
experimental: {
|
16 |
+
remoteFunctions: true,
|
17 |
+
},
|
18 |
},
|
19 |
|
20 |
compilerOptions: {
|