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: {
|