From e7f6d372957e201f581fc38413c8a556189056cf Mon Sep 17 00:00:00 2001 From: Jacob Lamb <44789941+jacobdalamb@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:46:46 -0800 Subject: [PATCH 1/3] Add biome.json and CI workflow --- .github/workflows/code-quality.yml | 22 ++++++++++++++++++ biome.json | 37 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/code-quality.yml create mode 100644 biome.json diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 00000000..d756530b --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,22 @@ +name: Code quality + +on: + push: + pull_request: + +jobs: + quality: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup Biome + uses: biomejs/setup-biome@v2 + with: + version: latest + - name: Run Biome + run: biome ci . diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..d8fe6808 --- /dev/null +++ b/biome.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "html": { + "experimentalFullSupportEnabled": true + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} From 05f86ffdef90c7ba5c45686165d13891fe503f28 Mon Sep 17 00:00:00 2001 From: Jacob Lamb <44789941+jacobdalamb@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:51:45 -0800 Subject: [PATCH 2/3] Format files --- biome.json | 2 +- package.json | 44 +- packages/core/package.json | 26 +- packages/core/script/validate.ts | 20 +- packages/core/src/generate.ts | 82 +-- packages/core/src/schema.ts | 200 +++--- packages/core/sst-env.d.ts | 4 +- packages/core/tsconfig.json | 6 +- packages/function/package.json | 16 +- packages/function/src/worker.ts | 118 ++-- packages/function/sst-env.d.ts | 26 +- packages/function/tsconfig.json | 10 +- packages/web/package.json | 26 +- packages/web/script/build.ts | 28 +- packages/web/src/index.css | 978 ++++++++++++++-------------- packages/web/src/index.ts | 314 ++++----- packages/web/src/render.tsx | 1038 +++++++++++++++--------------- packages/web/src/server.ts | 115 ++-- packages/web/sst-env.d.ts | 4 +- packages/web/tsconfig.json | 28 +- sst-env.d.ts | 26 +- sst.config.ts | 68 +- tsconfig.json | 6 +- 23 files changed, 1604 insertions(+), 1581 deletions(-) diff --git a/biome.json b/biome.json index d8fe6808..e6b3dc04 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/package.json b/package.json index eee04663..4d1f8968 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ { - "type": "module", - "private": true, - "workspaces": { - "packages": [ - "packages/*" - ], - "catalog": { - "typescript": "5.8.2", - "@types/node": "22.13.9", - "@types/bun": "1.3.0", - "zod": "3.24.2", - "ai": "4.3.16", - "@tsconfig/bun": "^1.0.8" - } - }, - "scripts": { - "validate": "bun ./packages/core/script/validate.ts" - }, - "dependencies": { - "@cloudflare/workers-types": "^4.20250801.0", - "sst": "3.17.5" - } + "type": "module", + "private": true, + "workspaces": { + "packages": [ + "packages/*" + ], + "catalog": { + "typescript": "5.8.2", + "@types/node": "22.13.9", + "@types/bun": "1.3.0", + "zod": "3.24.2", + "ai": "4.3.16", + "@tsconfig/bun": "^1.0.8" + } + }, + "scripts": { + "validate": "bun ./packages/core/script/validate.ts" + }, + "dependencies": { + "@cloudflare/workers-types": "^4.20250801.0", + "sst": "3.17.5" + } } diff --git a/packages/core/package.json b/packages/core/package.json index bb74eae3..470cd315 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,15 +1,15 @@ { - "name": "models.dev", - "version": "0.0.0", - "$schema": "https://json.schemastore.org/package.json", - "type": "module", - "dependencies": { - "zod": "catalog:" - }, - "main": "./src/index.ts", - "devDependencies": { - "@tsconfig/bun": "catalog:", - "@types/bun": "catalog:", - "@types/node": "catalog:" - } + "name": "models.dev", + "version": "0.0.0", + "$schema": "https://json.schemastore.org/package.json", + "type": "module", + "dependencies": { + "zod": "catalog:" + }, + "main": "./src/index.ts", + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/node": "catalog:" + } } diff --git a/packages/core/script/validate.ts b/packages/core/script/validate.ts index 0667fb4d..1d9b9f31 100755 --- a/packages/core/script/validate.ts +++ b/packages/core/script/validate.ts @@ -5,15 +5,15 @@ import path from "path"; import { ZodError } from "zod"; try { - const result = await generate( - path.join(import.meta.dirname, "..", "..", "..", "providers"), - ); - console.log(JSON.stringify(result, null, 2)); + const result = await generate( + path.join(import.meta.dirname, "..", "..", "..", "providers"), + ); + console.log(JSON.stringify(result, null, 2)); } catch (e: any) { - if (e instanceof ZodError) { - console.error("Validation error:", e.errors); - console.error("When parsing:", e.cause); - process.exit(1); - } - throw e; + if (e instanceof ZodError) { + console.error("Validation error:", e.errors); + console.error("When parsing:", e.cause); + process.exit(1); + } + throw e; } diff --git a/packages/core/src/generate.ts b/packages/core/src/generate.ts index 0169c5c5..b8f7f93f 100755 --- a/packages/core/src/generate.ts +++ b/packages/core/src/generate.ts @@ -3,47 +3,47 @@ import path from "path"; import { Provider, Model } from "./schema.js"; export async function generate(directory: string) { - const result = {} as Record; - for await (const providerPath of new Bun.Glob("*/provider.toml").scan({ - cwd: directory, - absolute: true, - })) { - const providerID = path.basename(path.dirname(providerPath)); - const toml = await import(providerPath, { - with: { - type: "toml", - }, - }).then((mod) => mod.default); - toml.id = providerID; - toml.models = {}; - const provider = Provider.safeParse(toml); - if (!provider.success) { - provider.error.cause = { providerPath, toml }; - throw provider.error; - } + const result = {} as Record; + for await (const providerPath of new Bun.Glob("*/provider.toml").scan({ + cwd: directory, + absolute: true, + })) { + const providerID = path.basename(path.dirname(providerPath)); + const toml = await import(providerPath, { + with: { + type: "toml", + }, + }).then((mod) => mod.default); + toml.id = providerID; + toml.models = {}; + const provider = Provider.safeParse(toml); + if (!provider.success) { + provider.error.cause = { providerPath, toml }; + throw provider.error; + } - const modelsPath = path.join(directory, providerID, "models"); - for await (const modelPath of new Bun.Glob("**/*.toml").scan({ - cwd: modelsPath, - absolute: true, - followSymlinks: true, - })) { - const modelID = path.relative(modelsPath, modelPath).slice(0, -5); - const toml = await import(modelPath, { - with: { - type: "toml", - }, - }).then((mod) => mod.default); - toml.id = modelID; - const model = Model.safeParse(toml); - if (!model.success) { - model.error.cause = { modelPath, toml }; - throw model.error; - } - provider.data.models[modelID] = model.data; - } - result[providerID] = provider.data; - } + const modelsPath = path.join(directory, providerID, "models"); + for await (const modelPath of new Bun.Glob("**/*.toml").scan({ + cwd: modelsPath, + absolute: true, + followSymlinks: true, + })) { + const modelID = path.relative(modelsPath, modelPath).slice(0, -5); + const toml = await import(modelPath, { + with: { + type: "toml", + }, + }).then((mod) => mod.default); + toml.id = modelID; + const model = Model.safeParse(toml); + if (!model.success) { + model.error.cause = { modelPath, toml }; + throw model.error; + } + provider.data.models[modelID] = model.data; + } + result[providerID] = provider.data; + } - return result; + return result; } diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 49f885be..64a8b4b4 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -1,108 +1,108 @@ import { z } from "zod"; export const Model = z - .object({ - id: z.string(), - name: z.string().min(1, "Model name cannot be empty"), - attachment: z.boolean(), - reasoning: z.boolean(), - temperature: z.boolean(), - tool_call: z.boolean(), - knowledge: z - .string() - .regex(/^\d{4}-\d{2}(-\d{2})?$/, { - message: "Must be in YYYY-MM or YYYY-MM-DD format", - }) - .optional(), - release_date: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, { - message: "Must be in YYYY-MM or YYYY-MM-DD format", - }), - last_updated: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, { - message: "Must be in YYYY-MM or YYYY-MM-DD format", - }), - modalities: z.object({ - input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - }), - open_weights: z.boolean(), - cost: z - .object({ - input: z.number().min(0, "Input price cannot be negative"), - output: z.number().min(0, "Output price cannot be negative"), - reasoning: z - .number() - .min(0, "Input price cannot be negative") - .optional(), - cache_read: z - .number() - .min(0, "Cache read price cannot be negative") - .optional(), - cache_write: z - .number() - .min(0, "Cache write price cannot be negative") - .optional(), - input_audio: z - .number() - .min(0, "Audio input price cannot be negative") - .optional(), - output_audio: z - .number() - .min(0, "Audio output price cannot be negative") - .optional(), - }) - .optional(), - limit: z.object({ - context: z.number().min(0, "Context window must be positive"), - output: z.number().min(0, "Output tokens must be positive"), - }), - status: z.enum(["alpha", "beta", "deprecated"]).optional(), - provider: z - .object({ - npm: z.string().optional(), - api: z.string().optional(), - }) - .optional(), - }) - .strict() - .refine( - (data) => { - return !(data.reasoning === false && data.cost?.reasoning !== undefined); - }, - { - message: "Cannot set cost.reasoning when reasoning is false", - path: ["cost", "reasoning"], - } - ); + .object({ + id: z.string(), + name: z.string().min(1, "Model name cannot be empty"), + attachment: z.boolean(), + reasoning: z.boolean(), + temperature: z.boolean(), + tool_call: z.boolean(), + knowledge: z + .string() + .regex(/^\d{4}-\d{2}(-\d{2})?$/, { + message: "Must be in YYYY-MM or YYYY-MM-DD format", + }) + .optional(), + release_date: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, { + message: "Must be in YYYY-MM or YYYY-MM-DD format", + }), + last_updated: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, { + message: "Must be in YYYY-MM or YYYY-MM-DD format", + }), + modalities: z.object({ + input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), + output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), + }), + open_weights: z.boolean(), + cost: z + .object({ + input: z.number().min(0, "Input price cannot be negative"), + output: z.number().min(0, "Output price cannot be negative"), + reasoning: z + .number() + .min(0, "Input price cannot be negative") + .optional(), + cache_read: z + .number() + .min(0, "Cache read price cannot be negative") + .optional(), + cache_write: z + .number() + .min(0, "Cache write price cannot be negative") + .optional(), + input_audio: z + .number() + .min(0, "Audio input price cannot be negative") + .optional(), + output_audio: z + .number() + .min(0, "Audio output price cannot be negative") + .optional(), + }) + .optional(), + limit: z.object({ + context: z.number().min(0, "Context window must be positive"), + output: z.number().min(0, "Output tokens must be positive"), + }), + status: z.enum(["alpha", "beta", "deprecated"]).optional(), + provider: z + .object({ + npm: z.string().optional(), + api: z.string().optional(), + }) + .optional(), + }) + .strict() + .refine( + (data) => { + return !(data.reasoning === false && data.cost?.reasoning !== undefined); + }, + { + message: "Cannot set cost.reasoning when reasoning is false", + path: ["cost", "reasoning"], + }, + ); export type Model = z.infer; export const Provider = z - .object({ - id: z.string(), - env: z.array(z.string()).min(1, "Provider env cannot be empty"), - npm: z.string().min(1, "Provider npm module cannot be empty"), - api: z.string().optional(), - name: z.string().min(1, "Provider name cannot be empty"), - doc: z - .string() - .min( - 1, - "Please provide a link to the provider documentation where models are listed" - ), - models: z.record(Model), - }) - .strict() - .refine( - (data) => { - return ( - (data.npm === "@ai-sdk/openai-compatible" && data.api !== undefined) || - (data.npm !== "@ai-sdk/openai-compatible" && data.api === undefined) - ); - }, - { - message: - "'api' field is required if and only if npm is '@ai-sdk/openai-compatible'", - path: ["api"], - } - ); + .object({ + id: z.string(), + env: z.array(z.string()).min(1, "Provider env cannot be empty"), + npm: z.string().min(1, "Provider npm module cannot be empty"), + api: z.string().optional(), + name: z.string().min(1, "Provider name cannot be empty"), + doc: z + .string() + .min( + 1, + "Please provide a link to the provider documentation where models are listed", + ), + models: z.record(Model), + }) + .strict() + .refine( + (data) => { + return ( + (data.npm === "@ai-sdk/openai-compatible" && data.api !== undefined) || + (data.npm !== "@ai-sdk/openai-compatible" && data.api === undefined) + ); + }, + { + message: + "'api' field is required if and only if npm is '@ai-sdk/openai-compatible'", + path: ["api"], + }, + ); export type Provider = z.infer; diff --git a/packages/core/sst-env.d.ts b/packages/core/sst-env.d.ts index b6a7e906..cf5e5fbe 100644 --- a/packages/core/sst-env.d.ts +++ b/packages/core/sst-env.d.ts @@ -5,5 +5,5 @@ /// -import "sst" -export {} \ No newline at end of file +import "sst"; +export {}; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 65fa6c7f..af546f00 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@tsconfig/bun/tsconfig.json", - "compilerOptions": {} + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": {} } diff --git a/packages/function/package.json b/packages/function/package.json index 1d1c7c9f..69e8a757 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,10 +1,10 @@ { - "$schema": "https://json.schemastore.org/package.json", - "name": "@models.dev/function", - "private": true, - "type": "module", - "devDependencies": { - "@cloudflare/workers-types": "4.20250522.0", - "@tsconfig/bun": "catalog:" - } + "$schema": "https://json.schemastore.org/package.json", + "name": "@models.dev/function", + "private": true, + "type": "module", + "devDependencies": { + "@cloudflare/workers-types": "4.20250522.0", + "@tsconfig/bun": "catalog:" + } } diff --git a/packages/function/src/worker.ts b/packages/function/src/worker.ts index 5fd32633..6f7b89b5 100644 --- a/packages/function/src/worker.ts +++ b/packages/function/src/worker.ts @@ -1,66 +1,70 @@ export interface Env { - ASSETS: any; - PosthogToken: string; + ASSETS: any; + PosthogToken: string; } export default { - async fetch( - request: Request, - env: Env, - ctx: ExecutionContext, - ): Promise { - const url = new URL(request.url); - const ip = request.headers.get("cf-connecting-ip") || "unknown"; - const agent = request.headers.get("user-agent") || "unknown"; - if (agent.includes("opencode") || agent.includes("bun")) { - ctx.waitUntil( - fetch("https://us.i.posthog.com/i/v0/e/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - api_key: JSON.parse(env.PosthogToken).value, - event: "hit", - distinct_id: ip, - properties: { - $process_person_profile: false, - user_agent: agent, - path: url.pathname, - }, - }), - }), - ); - } + async fetch( + request: Request, + env: Env, + ctx: ExecutionContext, + ): Promise { + const url = new URL(request.url); + const ip = request.headers.get("cf-connecting-ip") || "unknown"; + const agent = request.headers.get("user-agent") || "unknown"; + if (agent.includes("opencode") || agent.includes("bun")) { + ctx.waitUntil( + fetch("https://us.i.posthog.com/i/v0/e/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + api_key: JSON.parse(env.PosthogToken).value, + event: "hit", + distinct_id: ip, + properties: { + $process_person_profile: false, + user_agent: agent, + path: url.pathname, + }, + }), + }), + ); + } - if (url.pathname === "/api.json") { - url.pathname = "/_api.json"; - } else if ( - url.pathname === "/" || - url.pathname === "/index.html" || - url.pathname === "/index" - ) { - url.pathname = "/_index"; - } else if (url.pathname.startsWith("/logos/")) { - // Check if the specific provider logo exists in static assets - const logoResponse = await env.ASSETS.fetch(new Request(url.toString(), request)); + if (url.pathname === "/api.json") { + url.pathname = "/_api.json"; + } else if ( + url.pathname === "/" || + url.pathname === "/index.html" || + url.pathname === "/index" + ) { + url.pathname = "/_index"; + } else if (url.pathname.startsWith("/logos/")) { + // Check if the specific provider logo exists in static assets + const logoResponse = await env.ASSETS.fetch( + new Request(url.toString(), request), + ); - if (logoResponse.status === 404) { - // Fallback to default logo - const defaultUrl = new URL(url); - defaultUrl.pathname = "/logos/default.svg"; - return await env.ASSETS.fetch(new Request(defaultUrl.toString(), request)); - } + if (logoResponse.status === 404) { + // Fallback to default logo + const defaultUrl = new URL(url); + defaultUrl.pathname = "/logos/default.svg"; + return await env.ASSETS.fetch( + new Request(defaultUrl.toString(), request), + ); + } - return logoResponse; - } else { - // redirect to "/" - return new Response(null, { - status: 302, - headers: { Location: "/" }, - }); - } + return logoResponse; + } else { + // redirect to "/" + return new Response(null, { + status: 302, + headers: { Location: "/" }, + }); + } - return await env.ASSETS.fetch(new Request(url.toString(), request)); - }, + return await env.ASSETS.fetch(new Request(url.toString(), request)); + }, }; diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 3fed309c..550effe6 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -3,22 +3,22 @@ /* eslint-disable */ /* deno-fmt-ignore-file */ -import "sst" +import "sst"; declare module "sst" { - export interface Resource { - "PosthogToken": { - "type": "sst.sst.Secret" - "value": string - } - } + export interface Resource { + PosthogToken: { + type: "sst.sst.Secret"; + value: string; + }; + } } -// cloudflare +// cloudflare import * as cloudflare from "@cloudflare/workers-types"; declare module "sst" { - export interface Resource { - "Server": cloudflare.Service - } + export interface Resource { + Server: cloudflare.Service; + } } -import "sst" -export {} \ No newline at end of file +import "sst"; +export {}; diff --git a/packages/function/tsconfig.json b/packages/function/tsconfig.json index 8600603d..7142e348 100644 --- a/packages/function/tsconfig.json +++ b/packages/function/tsconfig.json @@ -1,7 +1,7 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@tsconfig/bun/tsconfig.json", - "compilerOptions": { - "types": ["@cloudflare/workers-types"] - } + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + } } diff --git a/packages/web/package.json b/packages/web/package.json index 852f51ab..a38863d4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,15 +1,15 @@ { - "$schema": "https://json.schemastore.org/package.json", - "name": "@models.dev/web", - "scripts": { - "dev": "bun run --hot ./src/server.ts", - "build": "./script/build.ts" - }, - "dependencies": { - "hono": "^4.8.0", - "models.dev": "workspace:*" - }, - "devDependencies": { - "@types/bun": "^1.2.16" - } + "$schema": "https://json.schemastore.org/package.json", + "name": "@models.dev/web", + "scripts": { + "dev": "bun run --hot ./src/server.ts", + "build": "./script/build.ts" + }, + "dependencies": { + "hono": "^4.8.0", + "models.dev": "workspace:*" + }, + "devDependencies": { + "@types/bun": "^1.2.16" + } } diff --git a/packages/web/script/build.ts b/packages/web/script/build.ts index daad95cc..9bd5bee3 100755 --- a/packages/web/script/build.ts +++ b/packages/web/script/build.ts @@ -7,13 +7,13 @@ import { $ } from "bun"; await fs.rm("./dist", { recursive: true, force: true }); await Bun.build({ - entrypoints: ["./index.html"], - outdir: "dist", - target: "bun", + entrypoints: ["./index.html"], + outdir: "dist", + target: "bun", }); for await (const file of new Bun.Glob("./public/*").scan()) { - await Bun.write(file.replace("./public/", "./dist/"), Bun.file(file)); + await Bun.write(file.replace("./public/", "./dist/"), Bun.file(file)); } // Copy provider logos to dist/logos/ @@ -23,22 +23,22 @@ await fs.mkdir("./dist/logos", { recursive: true }); const defaultLogoPath = "../../providers/logo.svg"; const defaultLogo = Bun.file(defaultLogoPath); if (await defaultLogo.exists()) { - await Bun.write("./dist/logos/default.svg", defaultLogo); + await Bun.write("./dist/logos/default.svg", defaultLogo); } // Then copy provider-specific logos const providersDir = "../../providers"; const entries = await fs.readdir(providersDir, { withFileTypes: true }); for (const entry of entries) { - if (entry.isDirectory()) { - const provider = entry.name; - const logoPath = path.join(providersDir, provider, "logo.svg"); - const logoFile = Bun.file(logoPath); - - if (await logoFile.exists()) { - await Bun.write(`./dist/logos/${provider}.svg`, logoFile); - } - } + if (entry.isDirectory()) { + const provider = entry.name; + const logoPath = path.join(providersDir, provider, "logo.svg"); + const logoFile = Bun.file(logoPath); + + if (await logoFile.exists()) { + await Bun.write(`./dist/logos/${provider}.svg`, logoFile); + } + } } let html = await Bun.file("./dist/index.html").text(); diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 58a4a5c4..9a383c03 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -1,549 +1,551 @@ /* CSS Reset/Normalize */ * { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; } :root { - --icon-opacity: 0.85; - --header-height: 56px; - --font-mono: 'IBM Plex Mono', monospace; + --icon-opacity: 0.85; + --header-height: 56px; + --font-mono: "IBM Plex Mono", monospace; } :root { - --color-brand: #FD9527; - --color-background: #FFF; - --color-border: #DDD; - --color-surface: #EEE; - --color-alpha-background: rgba(255, 255, 255, 0.75); - - --color-text: #333; - --color-text-invert: #FFF; - --color-text-secondary: #666; - --color-text-tertiary: #999; + --color-brand: #fd9527; + --color-background: #fff; + --color-border: #ddd; + --color-surface: #eee; + --color-alpha-background: rgba(255, 255, 255, 0.75); + + --color-text: #333; + --color-text-invert: #fff; + --color-text-secondary: #666; + --color-text-tertiary: #999; } @media (prefers-color-scheme: dark) { - :root { - --color-brand: #FD9527; - --color-background: #1E1E1E; - --color-border: #333; - --color-surface: #111; - --color-alpha-background: rgba(30, 30, 30, 0.75); - - --color-text: #FFF; - --color-text-invert: #333; - --color-text-secondary: #AAA; - --color-text-tertiary: #666; - } + :root { + --color-brand: #fd9527; + --color-background: #1e1e1e; + --color-border: #333; + --color-surface: #111; + --color-alpha-background: rgba(30, 30, 30, 0.75); + + --color-text: #fff; + --color-text-invert: #333; + --color-text-secondary: #aaa; + --color-text-tertiary: #666; + } } html, body { - font-family: 'Rubik', sans-serif; - line-height: 1.6; - color: var(--color-text); - background-color: var(--color-background); + font-family: "Rubik", sans-serif; + line-height: 1.6; + color: var(--color-text); + background-color: var(--color-background); } body:has(dialog[open]) { - overscroll-behavior: none; + overscroll-behavior: none; } input, button { - font-family: inherit; + font-family: inherit; } a { - color: var(--color-text); - text-decoration: underline; - text-decoration-style: dotted; - text-decoration-color: var(--color-text-tertiary); - text-underline-offset: 0.1875rem; - - &:hover { - color: var(--color-text); - } + color: var(--color-text); + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-color: var(--color-text-tertiary); + text-underline-offset: 0.1875rem; + + &:hover { + color: var(--color-text); + } } header { - top: 0; - display: flex; - gap: 0.5rem; - justify-content: space-between; - align-items: center; - height: var(--header-height); - padding: 0 0.75rem; - background-color: var(--color-background); - position: fixed; - width: 100%; - z-index: 10; - - &>div { - display: flex; - align-items: center; - - &.left { - flex: 1 1 auto; - min-width: 0; - position: relative; - align-items: baseline; - } - - &.right { - flex: 0 0 auto; - gap: 0.75rem; - } - } - - h1 { - font-size: 1rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: -0.5px; - } - - p { - font-size: 0.875rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: var(--color-text-tertiary); - } - - .slash { - margin-left: 0.625rem; - margin-right: 0.25rem; - display: block; - position: relative; - top: 1px; - width: 0; - line-height: 1; - height: 0.75rem; - border-right: 2px solid var(--color-border); - transform: translateX(-50%) rotate(20deg); - transform-origin: top center; - } - - a.github { - flex: 0 0 auto; - height: 24px; - color: var(--color-text-secondary); - - svg { - opacity: var(--icon-opacity); - } - } - - .search-container { - position: relative; - flex: 1 1 auto; - min-width: 12.5rem; - } - - input { - width: 100%; - font-size: 0.8125rem; - line-height: 1.1; - padding: 0.5rem 2.5rem 0.5rem 0.625rem; - border-radius: 0.25rem; - border: 1px solid var(--color-border); - height: 2rem; - background: none; - color: var(--color-text); - - &:focus { - border-color: var(--color-brand); - outline: none; - } - } - - .search-shortcut { - position: absolute; - right: 0.5rem; - top: 50%; - transform: translateY(-50%); - font-size: 0.75rem; - color: var(--color-text-tertiary); - pointer-events: none; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; - } - - button { - flex: 0 0 auto; - cursor: pointer; - border: none; - background-color: var(--color-brand); - color: var(--color-text-invert); - font-size: 0.8125rem; - line-height: 1.1; - height: 2rem; - padding: 0.5rem 0.75rem; - border-radius: 0.25rem; - } - - @media (max-width: 32rem) { - div.left { - - p, - span.slash { - display: none; - } - } - } - - @media (max-width: 45rem) { - div.right { - - .github, - .search-container { - display: none; - } - } - } + top: 0; + display: flex; + gap: 0.5rem; + justify-content: space-between; + align-items: center; + height: var(--header-height); + padding: 0 0.75rem; + background-color: var(--color-background); + position: fixed; + width: 100%; + z-index: 10; + + & > div { + display: flex; + align-items: center; + + &.left { + flex: 1 1 auto; + min-width: 0; + position: relative; + align-items: baseline; + } + + &.right { + flex: 0 0 auto; + gap: 0.75rem; + } + } + + h1 { + font-size: 1rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: -0.5px; + } + + p { + font-size: 0.875rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--color-text-tertiary); + } + + .slash { + margin-left: 0.625rem; + margin-right: 0.25rem; + display: block; + position: relative; + top: 1px; + width: 0; + line-height: 1; + height: 0.75rem; + border-right: 2px solid var(--color-border); + transform: translateX(-50%) rotate(20deg); + transform-origin: top center; + } + + a.github { + flex: 0 0 auto; + height: 24px; + color: var(--color-text-secondary); + + svg { + opacity: var(--icon-opacity); + } + } + + .search-container { + position: relative; + flex: 1 1 auto; + min-width: 12.5rem; + } + + input { + width: 100%; + font-size: 0.8125rem; + line-height: 1.1; + padding: 0.5rem 2.5rem 0.5rem 0.625rem; + border-radius: 0.25rem; + border: 1px solid var(--color-border); + height: 2rem; + background: none; + color: var(--color-text); + + &:focus { + border-color: var(--color-brand); + outline: none; + } + } + + .search-shortcut { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + font-size: 0.75rem; + color: var(--color-text-tertiary); + pointer-events: none; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; + } + + button { + flex: 0 0 auto; + cursor: pointer; + border: none; + background-color: var(--color-brand); + color: var(--color-text-invert); + font-size: 0.8125rem; + line-height: 1.1; + height: 2rem; + padding: 0.5rem 0.75rem; + border-radius: 0.25rem; + } + + @media (max-width: 32rem) { + div.left { + p, + span.slash { + display: none; + } + } + } + + @media (max-width: 45rem) { + div.right { + .github, + .search-container { + display: none; + } + } + } } table { - border-collapse: separate; - border-spacing: 0; - font-size: 0.875rem; - width: 100%; - margin-top: var(--header-height); + border-collapse: separate; + border-spacing: 0; + font-size: 0.875rem; + width: 100%; + margin-top: var(--header-height); } thead, -tbody {} +tbody { +} table thead th { - position: sticky; - top: var(--header-height); - border-top: 1px solid var(--color-border); - border-bottom: 1px solid var(--color-border); - font-size: 0.75rem; - padding: 0.75rem 0.75rem calc(0.75rem - 2px); - line-height: 1; - font-weight: 400; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--color-text-secondary); - backdrop-filter: blur(6px); - background-color: var(--color-alpha-background); - z-index: 10; + position: sticky; + top: var(--header-height); + border-top: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + font-size: 0.75rem; + padding: 0.75rem 0.75rem calc(0.75rem - 2px); + line-height: 1; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--color-text-secondary); + backdrop-filter: blur(6px); + background-color: var(--color-alpha-background); + z-index: 10; } table thead th .header-container { - display: flex; - align-items: center; - gap: 0.125rem; + display: flex; + align-items: center; + gap: 0.125rem; } th.sortable { - cursor: pointer; - user-select: none; + cursor: pointer; + user-select: none; } .sort-indicator { - display: inline-block; - width: 1rem; - text-align: center; + display: inline-block; + width: 1rem; + text-align: center; } table thead th .desc { - color: var(--color-text-tertiary); - margin-top: 0.5em; - display: block; - font-size: 0.625rem; - font-weight: normal; + color: var(--color-text-tertiary); + margin-top: 0.5em; + display: block; + font-size: 0.625rem; + font-weight: normal; } th, td { - padding: 0.75rem; - text-align: left; - border-bottom: 1px solid var(--color-border); - white-space: nowrap; + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid var(--color-border); + white-space: nowrap; } tbody { - td { - color: var(--color-text-tertiary); - } - - td:nth-child(1) { - font-weight: 500; - } - - td:nth-child(1), - td:nth-child(2), - td:nth-child(5), - td:nth-child(6), - td:nth-child(9), - td:nth-child(10), - td:nth-child(11), - td:nth-child(12), - td:nth-child(13), - td:nth-child(14), - td:nth-child(15), - td:nth-child(16) { - color: var(--color-text); - } - - td:nth-child(5), - td:nth-child(6), - td:nth-child(18) { - font-size: 0.8125rem; - font-family: var(--font-mono); - text-transform: uppercase; - } - - td:nth-child(3), - td:nth-child(4), - td:nth-child(9), - td:nth-child(10), - td:nth-child(11), - td:nth-child(12), - td:nth-child(13), - td:nth-child(14), - td:nth-child(15), - td:nth-child(16), - td:nth-child(17) { - font-size: 0.8125rem; - font-family: var(--font-mono); - } - - .provider-cell { - display: flex; - align-items: center; - gap: 0.375rem; - } - - .provider-cell span:first-child { - flex: 0 0 auto; - } - - .provider-cell svg { - display: block; - width: 1rem; - height: 1rem; - color: var(--color-text-secondary); - } - - .model-id-cell { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.375rem; - } - - .model-id-text {} - - .copy-button { - flex: 0 0 auto; - background: none; - border: none; - cursor: pointer; - padding: 0.25rem; - border-radius: 0.25rem; - color: var(--color-text-tertiary); - opacity: 0; - transition: opacity 0.2s ease, color 0.2s ease; - } - - .model-id-cell:hover .copy-button { - opacity: 1; - } - - .model-id-cell .copy-button svg { - display: block; - } - - .copy-button:hover { - color: var(--color-text); - background-color: var(--color-surface); - } - - .copy-button:active { - transform: scale(0.95); - } - - .copy-button.copied { - color: var(--color-brand) !important; - } - - .modalities { - display: flex; - gap: 0.25rem; - align-items: center; - } - - .modality-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border: 1px solid var(--color-border); - border-radius: 2px; - background-color: var(--color-background); - color: var(--color-text-secondary); - position: relative; - } - - .modality-icon::after { - content: attr(data-tooltip); - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - margin-bottom: 4px; - text-transform: uppercase; - letter-spacing: 0.5px; - line-height: 1; - padding: 0.375rem 0.375rem; - background-color: var(--color-text); - color: var(--color-background); - font-size: 0.625rem; - border-radius: 3px; - white-space: nowrap; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s ease; - z-index: 100; - } - - .modality-icon:hover::after { - opacity: 1; - } + td { + color: var(--color-text-tertiary); + } + + td:nth-child(1) { + font-weight: 500; + } + + td:nth-child(1), + td:nth-child(2), + td:nth-child(5), + td:nth-child(6), + td:nth-child(9), + td:nth-child(10), + td:nth-child(11), + td:nth-child(12), + td:nth-child(13), + td:nth-child(14), + td:nth-child(15), + td:nth-child(16) { + color: var(--color-text); + } + + td:nth-child(5), + td:nth-child(6), + td:nth-child(18) { + font-size: 0.8125rem; + font-family: var(--font-mono); + text-transform: uppercase; + } + + td:nth-child(3), + td:nth-child(4), + td:nth-child(9), + td:nth-child(10), + td:nth-child(11), + td:nth-child(12), + td:nth-child(13), + td:nth-child(14), + td:nth-child(15), + td:nth-child(16), + td:nth-child(17) { + font-size: 0.8125rem; + font-family: var(--font-mono); + } + + .provider-cell { + display: flex; + align-items: center; + gap: 0.375rem; + } + + .provider-cell span:first-child { + flex: 0 0 auto; + } + + .provider-cell svg { + display: block; + width: 1rem; + height: 1rem; + color: var(--color-text-secondary); + } + + .model-id-cell { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.375rem; + } + + .model-id-text { + } + + .copy-button { + flex: 0 0 auto; + background: none; + border: none; + cursor: pointer; + padding: 0.25rem; + border-radius: 0.25rem; + color: var(--color-text-tertiary); + opacity: 0; + transition: + opacity 0.2s ease, + color 0.2s ease; + } + + .model-id-cell:hover .copy-button { + opacity: 1; + } + + .model-id-cell .copy-button svg { + display: block; + } + + .copy-button:hover { + color: var(--color-text); + background-color: var(--color-surface); + } + + .copy-button:active { + transform: scale(0.95); + } + + .copy-button.copied { + color: var(--color-brand) !important; + } + + .modalities { + display: flex; + gap: 0.25rem; + align-items: center; + } + + .modality-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: 1px solid var(--color-border); + border-radius: 2px; + background-color: var(--color-background); + color: var(--color-text-secondary); + position: relative; + } + + .modality-icon::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + line-height: 1; + padding: 0.375rem 0.375rem; + background-color: var(--color-text); + color: var(--color-background); + font-size: 0.625rem; + border-radius: 3px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; + z-index: 100; + } + + .modality-icon:hover::after { + opacity: 1; + } } dialog::backdrop { - backdrop-filter: blur(8px); - background-color: rgba(0, 0, 0, 0.03); + backdrop-filter: blur(8px); + background-color: rgba(0, 0, 0, 0.03); } dialog { - margin: auto; - background-color: var(--color-background); - color: var(--color-text); - border: none; - border-radius: 0.5rem; - width: calc(100vw - 2rem); - max-width: 40rem; - max-height: calc(100svh - 2rem); - box-shadow: - 0 2px 4px rgba(0, 0, 0, .05), - 0 4px 8px rgba(0, 0, 0, .05), - 0 8px 16px rgba(0, 0, 0, .07), - 0 16px 32px rgba(0, 0, 0, .07), - 0 32px 64px rgba(0, 0, 0, .07), - 0 48px 96px rgba(0, 0, 0, .07); - - flex-direction: column; - overflow: hidden; - - &[open] { - display: flex; - } - - .header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.875rem calc(1rem - 0.5rem) calc(0.875rem - 4px) 1rem; - border-bottom: 1px solid var(--color-border); - flex: 0 0 auto; - - h2 { - font-size: 1rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: -0.5px; - line-height: 1; - } - - button { - background: transparent; - color: var(--color-text); - opacity: var(--icon-opacity); - border: none; - font-size: 1.5rem; - line-height: 1; - cursor: pointer; - outline: none; - - svg { - display: block; - width: 1.5rem; - height: 1.5rem; - } - } - } - - .body { - padding: 1rem; - overflow-y: auto; - flex: 1 1 auto; - overscroll-behavior: contain; - font-size: 0.875rem; - - h2, - p, - .code-block { - margin-bottom: 0.625rem; - - &:has(+ h2) { - margin-bottom: 1.5rem; - } - - &:last-child { - margin-bottom: 0; - } - } - - h2 { - font-size: 1rem; - font-weight: 500; - } - - p { - b { - font-weight: 500; - } - } - - .code-block { - padding: 0.875rem 1rem; - border-radius: 0.25rem; - background-color: var(--color-surface); - } - - code { - font-size: 0.8125rem; - font-family: var(--font-mono); - } - } - - .footer { - flex: 0 0 auto; - text-align: center; - border-top: 1px solid var(--color-border); - padding: 0.875rem 1rem 0.875rem; - display: flex; - justify-content: space-between; - align-items: center; - - a { - font-size: 0.75rem; - color: var(--color-text-tertiary); - text-decoration: none; - - &:hover, - &:visited { - color: var(--color-text-tertiary); - } - } - } - -} \ No newline at end of file + margin: auto; + background-color: var(--color-background); + color: var(--color-text); + border: none; + border-radius: 0.5rem; + width: calc(100vw - 2rem); + max-width: 40rem; + max-height: calc(100svh - 2rem); + box-shadow: + 0 2px 4px rgba(0, 0, 0, 0.05), + 0 4px 8px rgba(0, 0, 0, 0.05), + 0 8px 16px rgba(0, 0, 0, 0.07), + 0 16px 32px rgba(0, 0, 0, 0.07), + 0 32px 64px rgba(0, 0, 0, 0.07), + 0 48px 96px rgba(0, 0, 0, 0.07); + + flex-direction: column; + overflow: hidden; + + &[open] { + display: flex; + } + + .header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.875rem calc(1rem - 0.5rem) calc(0.875rem - 4px) 1rem; + border-bottom: 1px solid var(--color-border); + flex: 0 0 auto; + + h2 { + font-size: 1rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: -0.5px; + line-height: 1; + } + + button { + background: transparent; + color: var(--color-text); + opacity: var(--icon-opacity); + border: none; + font-size: 1.5rem; + line-height: 1; + cursor: pointer; + outline: none; + + svg { + display: block; + width: 1.5rem; + height: 1.5rem; + } + } + } + + .body { + padding: 1rem; + overflow-y: auto; + flex: 1 1 auto; + overscroll-behavior: contain; + font-size: 0.875rem; + + h2, + p, + .code-block { + margin-bottom: 0.625rem; + + &:has(+ h2) { + margin-bottom: 1.5rem; + } + + &:last-child { + margin-bottom: 0; + } + } + + h2 { + font-size: 1rem; + font-weight: 500; + } + + p { + b { + font-weight: 500; + } + } + + .code-block { + padding: 0.875rem 1rem; + border-radius: 0.25rem; + background-color: var(--color-surface); + } + + code { + font-size: 0.8125rem; + font-family: var(--font-mono); + } + } + + .footer { + flex: 0 0 auto; + text-align: center; + border-top: 1px solid var(--color-border); + padding: 0.875rem 1rem 0.875rem; + display: flex; + justify-content: space-between; + align-items: center; + + a { + font-size: 0.75rem; + color: var(--color-text-tertiary); + text-decoration: none; + + &:hover, + &:visited { + color: var(--color-text-tertiary); + } + } + } +} diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 81afb434..f46715a9 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -7,34 +7,34 @@ const search = document.getElementById("search")! as HTMLInputElement; // URL State Management ///////////////////////// function getQueryParams() { - return new URLSearchParams(window.location.search); + return new URLSearchParams(window.location.search); } function updateQueryParams(updates: Record) { - const params = getQueryParams(); - for (const [key, value] of Object.entries(updates)) { - if (value) { - params.set(key, value); - } else { - params.delete(key); - } - } - const newPath = params.toString() - ? `${window.location.pathname}?${params.toString()}` - : window.location.pathname; - window.history.pushState({}, "", newPath); + const params = getQueryParams(); + for (const [key, value] of Object.entries(updates)) { + if (value) { + params.set(key, value); + } else { + params.delete(key); + } + } + const newPath = params.toString() + ? `${window.location.pathname}?${params.toString()}` + : window.location.pathname; + window.history.pushState({}, "", newPath); } function getColumnNameForURL(headerEl: Element): string { - const text = headerEl.textContent?.trim().toLowerCase() || ""; - return text.replace(/↑|↓/g, "").trim().split(/\s+/).slice(0, 2).join("-"); + const text = headerEl.textContent?.trim().toLowerCase() || ""; + return text.replace(/↑|↓/g, "").trim().split(/\s+/).slice(0, 2).join("-"); } function getColumnIndexByUrlName(name: string): number { - const headers = document.querySelectorAll("th.sortable"); - return Array.from(headers).findIndex( - (header) => getColumnNameForURL(header) === name - ); + const headers = document.querySelectorAll("th.sortable"); + return Array.from(headers).findIndex( + (header) => getColumnNameForURL(header) === name, + ); } ///////////////////////// @@ -43,23 +43,23 @@ function getColumnIndexByUrlName(name: string): number { let y = 0; help.addEventListener("click", () => { - y = window.scrollY; - document.body.style.position = "fixed"; - document.body.style.top = `-${y}px`; - modal.showModal(); + y = window.scrollY; + document.body.style.position = "fixed"; + document.body.style.top = `-${y}px`; + modal.showModal(); }); function closeDialog() { - modal.close(); - document.body.style.position = ""; - document.body.style.top = ""; - window.scrollTo(0, y); + modal.close(); + document.body.style.position = ""; + document.body.style.top = ""; + window.scrollTo(0, y); } modalClose.addEventListener("click", closeDialog); modal.addEventListener("cancel", closeDialog); modal.addEventListener("click", (e) => { - if (e.target === modal) closeDialog(); + if (e.target === modal) closeDialog(); }); //////////////////// @@ -68,172 +68,178 @@ modal.addEventListener("click", (e) => { let currentSort = { column: -1, direction: "asc" }; function sortTable(column: number, direction: "asc" | "desc") { - const header = document.querySelectorAll("th.sortable")[column]; - const columnType = header.getAttribute("data-type"); - if (!columnType) return; - - // update state - currentSort = { column, direction }; - updateQueryParams({ - sort: getColumnNameForURL(header), - order: direction, - }); - - // sort rows - const tbody = document.querySelector("table tbody")!; - const rows = Array.from( - tbody.querySelectorAll("tr") - ) as HTMLTableRowElement[]; - rows.sort((a, b) => { - const aValue = getCellValue(a.cells[column], columnType); - const bValue = getCellValue(b.cells[column], columnType); - - // Handle undefined values - always sort to bottom - if (aValue === undefined && bValue === undefined) return 0; - if (aValue === undefined) return 1; - if (bValue === undefined) return -1; - - let comparison = 0; - if (columnType === "number" || columnType === "modalities") { - comparison = (aValue as number) - (bValue as number); - } else if (columnType === "boolean") { - comparison = (aValue as string).localeCompare(bValue as string); - } else { - comparison = (aValue as string).localeCompare(bValue as string); - } - - return direction === "asc" ? comparison : -comparison; - }); - rows.forEach((row) => tbody.appendChild(row)); - - // update sort indicators - const headers = document.querySelectorAll("th.sortable"); - headers.forEach((header, i) => { - const indicator = header.querySelector(".sort-indicator")!; - - if (i === column) { - indicator.textContent = direction === "asc" ? "↑" : "↓"; - } else { - indicator.textContent = ""; - } - }); + const header = document.querySelectorAll("th.sortable")[column]; + const columnType = header.getAttribute("data-type"); + if (!columnType) return; + + // update state + currentSort = { column, direction }; + updateQueryParams({ + sort: getColumnNameForURL(header), + order: direction, + }); + + // sort rows + const tbody = document.querySelector("table tbody")!; + const rows = Array.from( + tbody.querySelectorAll("tr"), + ) as HTMLTableRowElement[]; + rows.sort((a, b) => { + const aValue = getCellValue(a.cells[column], columnType); + const bValue = getCellValue(b.cells[column], columnType); + + // Handle undefined values - always sort to bottom + if (aValue === undefined && bValue === undefined) return 0; + if (aValue === undefined) return 1; + if (bValue === undefined) return -1; + + let comparison = 0; + if (columnType === "number" || columnType === "modalities") { + comparison = (aValue as number) - (bValue as number); + } else if (columnType === "boolean") { + comparison = (aValue as string).localeCompare(bValue as string); + } else { + comparison = (aValue as string).localeCompare(bValue as string); + } + + return direction === "asc" ? comparison : -comparison; + }); + rows.forEach((row) => tbody.appendChild(row)); + + // update sort indicators + const headers = document.querySelectorAll("th.sortable"); + headers.forEach((header, i) => { + const indicator = header.querySelector(".sort-indicator")!; + + if (i === column) { + indicator.textContent = direction === "asc" ? "↑" : "↓"; + } else { + indicator.textContent = ""; + } + }); } function getCellValue( - cell: HTMLTableCellElement, - type: string + cell: HTMLTableCellElement, + type: string, ): string | number | undefined { - if (type === "modalities") - return cell.querySelectorAll(".modality-icon").length; + if (type === "modalities") + return cell.querySelectorAll(".modality-icon").length; - const text = cell.textContent?.trim() || ""; - if (text === "-") return; - if (type === "number") return parseFloat(text.replace(/[$,]/g, "")) || 0; - return text; + const text = cell.textContent?.trim() || ""; + if (text === "-") return; + if (type === "number") return parseFloat(text.replace(/[$,]/g, "")) || 0; + return text; } document.querySelectorAll("th.sortable").forEach((header) => { - header.addEventListener("click", () => { - const column = Array.from(header.parentElement!.children).indexOf(header); - const direction = - currentSort.column === column && currentSort.direction === "asc" - ? "desc" - : "asc"; - sortTable(column, direction); - }); + header.addEventListener("click", () => { + const column = Array.from(header.parentElement!.children).indexOf(header); + const direction = + currentSort.column === column && currentSort.direction === "asc" + ? "desc" + : "asc"; + sortTable(column, direction); + }); }); /////////////////// // Handle Search /////////////////// function filterTable(value: string) { - const lowerCaseValues = value.toLowerCase().split(",").filter(str => str.trim() !== ""); - const rows = document.querySelectorAll( - "table tbody tr" - ) as NodeListOf; - - rows.forEach((row) => { - const cellTexts = Array.from(row.cells).map((cell) => - cell.textContent!.toLowerCase() - ); - const isVisible = lowerCaseValues.length === 0 || - lowerCaseValues.some((lowerCaseValue) => cellTexts.some((text) => text.includes(lowerCaseValue))); - row.style.display = isVisible ? "" : "none"; - }); - - updateQueryParams({ search: value || null }); + const lowerCaseValues = value + .toLowerCase() + .split(",") + .filter((str) => str.trim() !== ""); + const rows = document.querySelectorAll( + "table tbody tr", + ) as NodeListOf; + + rows.forEach((row) => { + const cellTexts = Array.from(row.cells).map((cell) => + cell.textContent!.toLowerCase(), + ); + const isVisible = + lowerCaseValues.length === 0 || + lowerCaseValues.some((lowerCaseValue) => + cellTexts.some((text) => text.includes(lowerCaseValue)), + ); + row.style.display = isVisible ? "" : "none"; + }); + + updateQueryParams({ search: value || null }); } search.addEventListener("input", () => { - filterTable(search.value); + filterTable(search.value); }); document.addEventListener("keydown", (e) => { - if ((e.metaKey || e.ctrlKey) && e.key === "k") { - e.preventDefault(); - search.focus(); - } + if ((e.metaKey || e.ctrlKey) && e.key === "k") { + e.preventDefault(); + search.focus(); + } }); search.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - search.value = ""; - search.dispatchEvent(new Event("input")); - } + if (e.key === "Escape") { + search.value = ""; + search.dispatchEvent(new Event("input")); + } }); /////////////////////////////////// // Handle Copy model ID function /////////////////////////////////// (window as any).copyModelId = async ( - button: HTMLButtonElement, - modelId: string + button: HTMLButtonElement, + modelId: string, ) => { - try { - if (navigator.clipboard) { - await navigator.clipboard.writeText(modelId); - - // Switch to check icon - const copyIcon = button.querySelector(".copy-icon") as HTMLElement; - const checkIcon = button.querySelector(".check-icon") as HTMLElement; - - copyIcon.style.display = "none"; - checkIcon.style.display = "block"; - - // Switch back after 1 second - setTimeout(() => { - copyIcon.style.display = "block"; - checkIcon.style.display = "none"; - }, 1000); - } - } catch (err) { - console.error("Failed to copy text: ", err); - } + try { + if (navigator.clipboard) { + await navigator.clipboard.writeText(modelId); + + // Switch to check icon + const copyIcon = button.querySelector(".copy-icon") as HTMLElement; + const checkIcon = button.querySelector(".check-icon") as HTMLElement; + + copyIcon.style.display = "none"; + checkIcon.style.display = "block"; + + // Switch back after 1 second + setTimeout(() => { + copyIcon.style.display = "block"; + checkIcon.style.display = "none"; + }, 1000); + } + } catch (err) { + console.error("Failed to copy text: ", err); + } }; /////////////////////////////////// // Initialize State from URL /////////////////////////////////// function initializeFromURL() { - const params = getQueryParams(); + const params = getQueryParams(); - (() => { - const searchQuery = params.get("search"); - if (!searchQuery) return; - search.value = searchQuery; - filterTable(searchQuery); - })(); + (() => { + const searchQuery = params.get("search"); + if (!searchQuery) return; + search.value = searchQuery; + filterTable(searchQuery); + })(); - (() => { - const columnName = params.get("sort"); - if (!columnName) return; + (() => { + const columnName = params.get("sort"); + if (!columnName) return; - const columnIndex = getColumnIndexByUrlName(columnName); - if (columnIndex === -1) return; + const columnIndex = getColumnIndexByUrlName(columnName); + if (columnIndex === -1) return; - const direction = (params.get("order") as "asc" | "desc") || "asc"; - sortTable(columnIndex, direction); - })(); + const direction = (params.get("order") as "asc" | "desc") || "asc"; + sortTable(columnIndex, direction); + })(); } document.addEventListener("DOMContentLoaded", initializeFromURL); diff --git a/packages/web/src/render.tsx b/packages/web/src/render.tsx index 41e1d461..bf59dda2 100644 --- a/packages/web/src/render.tsx +++ b/packages/web/src/render.tsx @@ -8,47 +8,47 @@ import { existsSync } from "fs"; import path from "path"; export const Providers = await generate( - path.join(import.meta.dir, "..", "..", "..", "providers") + path.join(import.meta.dir, "..", "..", "..", "providers"), ); // Function to load SVG content const loadProviderSvg = async (providerId: string): Promise => { - const providerLogoPath = path.join( - import.meta.dir, - "..", - "..", - "..", - "providers", - providerId, - "logo.svg" - ); + const providerLogoPath = path.join( + import.meta.dir, + "..", + "..", + "..", + "providers", + providerId, + "logo.svg", + ); - const defaultLogoPath = path.join( - import.meta.dir, - "..", - "..", - "..", - "providers", - "logo.svg" - ); + const defaultLogoPath = path.join( + import.meta.dir, + "..", + "..", + "..", + "providers", + "logo.svg", + ); - try { - // Try provider-specific logo first - if (existsSync(providerLogoPath)) { - const file = Bun.file(providerLogoPath); - return await file.text(); - } - // - // Fall back to default logo - if (existsSync(defaultLogoPath)) { - const file = Bun.file(defaultLogoPath); - return await file.text(); - } - return null; - } catch (error) { - console.warn(`Failed to load logo for provider ${providerId}:`, error); - return null; - } + try { + // Try provider-specific logo first + if (existsSync(providerLogoPath)) { + const file = Bun.file(providerLogoPath); + return await file.text(); + } + // + // Fall back to default logo + if (existsSync(defaultLogoPath)) { + const file = Bun.file(defaultLogoPath); + return await file.text(); + } + return null; + } catch (error) { + console.warn(`Failed to load logo for provider ${providerId}:`, error); + return null; + } }; // Create a cache of loaded SVGs at build time @@ -56,501 +56,501 @@ const providerLogos = new Map(); // Pre-load all provider logos for (const [providerId] of Object.entries(Providers)) { - const svgContent = await loadProviderSvg(providerId); - if (svgContent) { - providerLogos.set(providerId, svgContent); - } + const svgContent = await loadProviderSvg(providerId); + if (svgContent) { + providerLogos.set(providerId, svgContent); + } } function renderProviderLogo(providerId: string) { - const svgContent = providerLogos.get(providerId) || ""; + const svgContent = providerLogos.get(providerId) || ""; - return ; + return ; } const getModalityIcon = (modality: string) => { - switch (modality) { - case "text": - return ( - - - - - - - - ); - case "image": - return ( - - - - - - - - ); - case "audio": - return ( - - - - - - - ); - case "video": - return ( - - - - - - - ); - case "pdf": - return ( - - - - - - - - - - ); - default: - return null; - } + switch (modality) { + case "text": + return ( + + + + + + + + ); + case "image": + return ( + + + + + + + + ); + case "audio": + return ( + + + + + + + ); + case "video": + return ( + + + + + + + ); + case "pdf": + return ( + + + + + + + + + + ); + default: + return null; + } }; const renderCost = (cost?: number) => { - return cost === undefined ? "-" : `$${cost.toFixed(2)}`; + return cost === undefined ? "-" : `$${cost.toFixed(2)}`; }; export const Rendered = renderToString( - -
-
-

Models.dev

- -

An open-source database of AI models

-
-
- - - - - -
- - ⌘K -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - {Object.entries(Providers) - .sort(([, providerA], [, providerB]) => - providerA.name.localeCompare(providerB.name) - ) - .flatMap(([providerId, provider]) => - Object.entries(provider.models) - .filter(([, model]) => model.status !== "alpha") - .sort(([, modelA], [, modelB]) => - modelA.name.localeCompare(modelB.name) - ) - .map(([modelId, model]) => ( - - - - - - - - - - - - - - - - - - - - - - - - - )) - )} - -
- Provider - - Model - - Provider ID - - Model ID - - Tool Call - - Reasoning - - Input - - Output - -
- - Input Cost -
- per 1M tokens -
- -
-
-
- - Output Cost -
- per 1M tokens -
- -
-
-
- - Reasoning Cost -
- per 1M tokens -
- -
-
-
- - Cache Read Cost -
- per 1M tokens -
- -
-
-
- - Cache Write Cost -
- per 1M tokens -
- -
-
-
- - Audio Input Cost -
- per 1M tokens -
- -
-
-
- - Audio Output Cost -
- per 1M tokens -
- -
-
- Context Limit - - Output Limit - - Temperature - - Weights - - Knowledge - - Release Date - - Last Updated -
-
- {renderProviderLogo(providerId)} - {provider.name} -
-
{model.name}{providerId} -
- {modelId} - -
-
{model.tool_call ? "Yes" : "No"}{model.reasoning ? "Yes" : "No"} -
- {model.modalities.input.map((modality) => - getModalityIcon(modality) - )} -
-
-
- {model.modalities.output.map((modality) => - getModalityIcon(modality) - )} -
-
{renderCost(model.cost?.input)}{renderCost(model.cost?.output)}{renderCost(model.cost?.reasoning)}{renderCost(model.cost?.cache_read)}{renderCost(model.cost?.cache_write)}{renderCost(model.cost?.input_audio)}{renderCost(model.cost?.output_audio)}{model.limit.context.toLocaleString()}{model.limit.output.toLocaleString()}{model.temperature ? "Yes" : "No"}{model.open_weights ? "Open" : "Closed"} - {model.knowledge ? model.knowledge.substring(0, 7) : "-"} - {model.release_date}{model.last_updated}
- -
-

How to use

- -
-
-

- Models.dev is a comprehensive open-source database of - AI model specifications, pricing, and features. -

-

- There's no single database with information about all the - available AI models. We started Models.dev as a community-contributed - project to address this. We also use it internally in{" "} - - opencode - - . -

-

API

-

You can access this data through an API.

- -

- Use the Model ID field to do a lookup on any model; it's - the identifier used by{" "} - - AI SDK - - . -

-

Logos

-

- Provider logos are available at /logos/{`{provider}`}.svg{" "} - where {`{provider}`} is the Provider ID. -

- -

- If we don't have a provider's logo, a default logo is served instead. -

-

Contribute

-

- The data is stored in the{" "} - - GitHub repo - {" "} - as TOML files; organized by provider and model. The logo is stored as - an SVG. This is used to generate this page and power the API. -

-

- We need your help keeping this up to date. Feel free to edit the data - and submit a pull request. Refer to the{" "} - - README - {" "} - for more information. -

-
- -
-
+ +
+
+

Models.dev

+ +

An open-source database of AI models

+
+
+ + + + + +
+ + ⌘K +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + {Object.entries(Providers) + .sort(([, providerA], [, providerB]) => + providerA.name.localeCompare(providerB.name), + ) + .flatMap(([providerId, provider]) => + Object.entries(provider.models) + .filter(([, model]) => model.status !== "alpha") + .sort(([, modelA], [, modelB]) => + modelA.name.localeCompare(modelB.name), + ) + .map(([modelId, model]) => ( + + + + + + + + + + + + + + + + + + + + + + + + + )), + )} + +
+ Provider + + Model + + Provider ID + + Model ID + + Tool Call + + Reasoning + + Input + + Output + +
+ + Input Cost +
+ per 1M tokens +
+ +
+
+
+ + Output Cost +
+ per 1M tokens +
+ +
+
+
+ + Reasoning Cost +
+ per 1M tokens +
+ +
+
+
+ + Cache Read Cost +
+ per 1M tokens +
+ +
+
+
+ + Cache Write Cost +
+ per 1M tokens +
+ +
+
+
+ + Audio Input Cost +
+ per 1M tokens +
+ +
+
+
+ + Audio Output Cost +
+ per 1M tokens +
+ +
+
+ Context Limit + + Output Limit + + Temperature + + Weights + + Knowledge + + Release Date + + Last Updated +
+
+ {renderProviderLogo(providerId)} + {provider.name} +
+
{model.name}{providerId} +
+ {modelId} + +
+
{model.tool_call ? "Yes" : "No"}{model.reasoning ? "Yes" : "No"} +
+ {model.modalities.input.map((modality) => + getModalityIcon(modality), + )} +
+
+
+ {model.modalities.output.map((modality) => + getModalityIcon(modality), + )} +
+
{renderCost(model.cost?.input)}{renderCost(model.cost?.output)}{renderCost(model.cost?.reasoning)}{renderCost(model.cost?.cache_read)}{renderCost(model.cost?.cache_write)}{renderCost(model.cost?.input_audio)}{renderCost(model.cost?.output_audio)}{model.limit.context.toLocaleString()}{model.limit.output.toLocaleString()}{model.temperature ? "Yes" : "No"}{model.open_weights ? "Open" : "Closed"} + {model.knowledge ? model.knowledge.substring(0, 7) : "-"} + {model.release_date}{model.last_updated}
+ +
+

How to use

+ +
+
+

+ Models.dev is a comprehensive open-source database of + AI model specifications, pricing, and features. +

+

+ There's no single database with information about all the + available AI models. We started Models.dev as a community-contributed + project to address this. We also use it internally in{" "} + + opencode + + . +

+

API

+

You can access this data through an API.

+ +

+ Use the Model ID field to do a lookup on any model; it's + the identifier used by{" "} + + AI SDK + + . +

+

Logos

+

+ Provider logos are available at /logos/{`{provider}`}.svg{" "} + where {`{provider}`} is the Provider ID. +

+ +

+ If we don't have a provider's logo, a default logo is served instead. +

+

Contribute

+

+ The data is stored in the{" "} + + GitHub repo + {" "} + as TOML files; organized by provider and model. The logo is stored as + an SVG. This is used to generate this page and power the API. +

+

+ We need your help keeping this up to date. Feel free to edit the data + and submit a pull request. Refer to the{" "} + + README + {" "} + for more information. +

+
+ +
+
, ); diff --git a/packages/web/src/server.ts b/packages/web/src/server.ts index b04ea39a..fb7040d8 100644 --- a/packages/web/src/server.ts +++ b/packages/web/src/server.ts @@ -3,64 +3,79 @@ import { Rendered } from "./render"; import path from "path"; Bun.serve({ - port: 16_000, - routes: { - "/": Index, - "/assets/*": (req) => { - const file = Bun.file( - path.join(import.meta.dir, new URL(req.url).pathname), - ); - return new Response(file); - }, - "/logos/*": async (req) => { - const url = new URL(req.url); - const provider = url.pathname.split('/')[2].replace('.svg', ''); - const logoPath = path.join(import.meta.dir, "..", "..", "..", "providers", provider, "logo.svg"); - const defaultLogoPath = path.join(import.meta.dir, "..", "..", "..", "providers", "logo.svg"); + port: 16_000, + routes: { + "/": Index, + "/assets/*": (req) => { + const file = Bun.file( + path.join(import.meta.dir, new URL(req.url).pathname), + ); + return new Response(file); + }, + "/logos/*": async (req) => { + const url = new URL(req.url); + const provider = url.pathname.split("/")[2].replace(".svg", ""); + const logoPath = path.join( + import.meta.dir, + "..", + "..", + "..", + "providers", + provider, + "logo.svg", + ); + const defaultLogoPath = path.join( + import.meta.dir, + "..", + "..", + "..", + "providers", + "logo.svg", + ); - let file = Bun.file(logoPath); - if (!await file.exists()) { - file = Bun.file(defaultLogoPath); - } + let file = Bun.file(logoPath); + if (!(await file.exists())) { + file = Bun.file(defaultLogoPath); + } - return new Response(file, { - headers: { - "Content-Type": "image/svg+xml", - "Cache-Control": "public, max-age=3600" - } - }); - }, - }, + return new Response(file, { + headers: { + "Content-Type": "image/svg+xml", + "Cache-Control": "public, max-age=3600", + }, + }); + }, + }, }); const server = Bun.serve({ - development: true, - hostname: "0.0.0.0", - async fetch(req) { - // Reject WebSocket upgrade requests - if (req.headers.get("upgrade") === "websocket") { - return new Response("WebSocket upgrades not supported", { - status: 426, - headers: { - Upgrade: "Required", - }, - }); - } + development: true, + hostname: "0.0.0.0", + async fetch(req) { + // Reject WebSocket upgrade requests + if (req.headers.get("upgrade") === "websocket") { + return new Response("WebSocket upgrades not supported", { + status: 426, + headers: { + Upgrade: "Required", + }, + }); + } - const url = new URL(req.url); - url.host = "localhost:16000"; - const result = fetch(url.toString(), req); + const url = new URL(req.url); + url.host = "localhost:16000"; + const result = fetch(url.toString(), req); - if (url.pathname !== "/") return result; + if (url.pathname !== "/") return result; - let html = await result.then((r) => r.text()); - html = html.replace("", Rendered); - return new Response(html, { - headers: { - "Content-Type": "text/html", - }, - }); - }, + let html = await result.then((r) => r.text()); + html = html.replace("", Rendered); + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + }, }); console.log(`Server running at ${server.hostname}:${server.port}`); diff --git a/packages/web/sst-env.d.ts b/packages/web/sst-env.d.ts index b6a7e906..cf5e5fbe 100644 --- a/packages/web/sst-env.d.ts +++ b/packages/web/sst-env.d.ts @@ -5,5 +5,5 @@ /// -import "sst" -export {} \ No newline at end of file +import "sst"; +export {}; diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 606701d6..6b740458 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -1,18 +1,14 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "compilerOptions": { - "jsx": "react-jsx", - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "bundler", - "lib": [ - "ES2020", - "DOM", - "DOM.Iterable" - ], - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - } + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "jsx": "react-jsx", + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } } diff --git a/sst-env.d.ts b/sst-env.d.ts index 3fed309c..550effe6 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -3,22 +3,22 @@ /* eslint-disable */ /* deno-fmt-ignore-file */ -import "sst" +import "sst"; declare module "sst" { - export interface Resource { - "PosthogToken": { - "type": "sst.sst.Secret" - "value": string - } - } + export interface Resource { + PosthogToken: { + type: "sst.sst.Secret"; + value: string; + }; + } } -// cloudflare +// cloudflare import * as cloudflare from "@cloudflare/workers-types"; declare module "sst" { - export interface Resource { - "Server": cloudflare.Service - } + export interface Resource { + Server: cloudflare.Service; + } } -import "sst" -export {} \ No newline at end of file +import "sst"; +export {}; diff --git a/sst.config.ts b/sst.config.ts index d56f3309..0f373cbd 100644 --- a/sst.config.ts +++ b/sst.config.ts @@ -1,42 +1,42 @@ /// export default $config({ - app() { - return { - name: "models-dev", - home: "cloudflare", - }; - }, - async run() { - const { spawnSync } = await import("child_process"); + app() { + return { + name: "models-dev", + home: "cloudflare", + }; + }, + async run() { + const { spawnSync } = await import("child_process"); - const ret = spawnSync("./script/build.ts", [], { - cwd: "./packages/web", - stdio: "inherit", - }); - if (ret.status !== 0) throw new Error("Build failed"); + const ret = spawnSync("./script/build.ts", [], { + cwd: "./packages/web", + stdio: "inherit", + }); + if (ret.status !== 0) throw new Error("Build failed"); - const secrets = { - PosthogToken: new sst.Secret("PosthogToken"), - }; + const secrets = { + PosthogToken: new sst.Secret("PosthogToken"), + }; - const worker = new sst.cloudflare.Worker("Server", { - url: true, - domain: $app.stage === "dev" ? "models.dev" : undefined, - link: [secrets.PosthogToken], - handler: "./packages/function/src/worker.ts", - assets: { - directory: "./packages/web/dist", - }, - transform: { - worker: { - observability: { enabled: true }, - }, - }, - }); + const worker = new sst.cloudflare.Worker("Server", { + url: true, + domain: $app.stage === "dev" ? "models.dev" : undefined, + link: [secrets.PosthogToken], + handler: "./packages/function/src/worker.ts", + assets: { + directory: "./packages/web/dist", + }, + transform: { + worker: { + observability: { enabled: true }, + }, + }, + }); - return { - url: worker.url, - }; - }, + return { + url: worker.url, + }; + }, }); diff --git a/tsconfig.json b/tsconfig.json index 65fa6c7f..af546f00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@tsconfig/bun/tsconfig.json", - "compilerOptions": {} + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": {} } From 0fd6d5e17247f649d157c0a3b845f6686d88fd84 Mon Sep 17 00:00:00 2001 From: Jacob Lamb <44789941+jacobdalamb@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:53:59 -0800 Subject: [PATCH 3/3] Lint && check files --- packages/core/script/validate.ts | 2 +- packages/core/src/generate.ts | 2 +- packages/core/src/index.ts | 2 +- packages/core/sst-env.d.ts | 1 - packages/function/sst-env.d.ts | 1 - packages/web/script/build.ts | 4 ++-- packages/web/src/render.tsx | 4 ++-- packages/web/src/server.ts | 2 +- packages/web/sst-env.d.ts | 1 - sst-env.d.ts | 1 - 10 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/core/script/validate.ts b/packages/core/script/validate.ts index 1d9b9f31..48e341c9 100755 --- a/packages/core/script/validate.ts +++ b/packages/core/script/validate.ts @@ -1,8 +1,8 @@ #!/usr/bin/env bun -import { generate } from "../src/generate"; import path from "path"; import { ZodError } from "zod"; +import { generate } from "../src/generate"; try { const result = await generate( diff --git a/packages/core/src/generate.ts b/packages/core/src/generate.ts index b8f7f93f..49f8f776 100755 --- a/packages/core/src/generate.ts +++ b/packages/core/src/generate.ts @@ -1,6 +1,6 @@ import path from "path"; -import { Provider, Model } from "./schema.js"; +import { Model, Provider } from "./schema.js"; export async function generate(directory: string) { const result = {} as Record; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1e37222f..04190ae7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,2 +1,2 @@ -export * from "./schema.js"; export * from "./generate.js"; +export * from "./schema.js"; diff --git a/packages/core/sst-env.d.ts b/packages/core/sst-env.d.ts index cf5e5fbe..0c6eeb07 100644 --- a/packages/core/sst-env.d.ts +++ b/packages/core/sst-env.d.ts @@ -6,4 +6,3 @@ /// import "sst"; -export {}; diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 550effe6..4125e1f6 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -21,4 +21,3 @@ declare module "sst" { } import "sst"; -export {}; diff --git a/packages/web/script/build.ts b/packages/web/script/build.ts index 9bd5bee3..d34b33aa 100755 --- a/packages/web/script/build.ts +++ b/packages/web/script/build.ts @@ -1,9 +1,9 @@ #!/usr/bin/env bun -import { Rendered, Providers } from "../src/render"; +import { $ } from "bun"; import fs from "fs/promises"; import path from "path"; -import { $ } from "bun"; +import { Providers, Rendered } from "../src/render"; await fs.rm("./dist", { recursive: true, force: true }); await Bun.build({ diff --git a/packages/web/src/render.tsx b/packages/web/src/render.tsx index bf59dda2..28eb2fe1 100644 --- a/packages/web/src/render.tsx +++ b/packages/web/src/render.tsx @@ -1,10 +1,10 @@ /** @jsx jsx */ /** @jsxImportSource hono/jsx */ -import { generate } from "models.dev"; +import { existsSync } from "fs"; import { Fragment } from "hono/jsx"; import { renderToString } from "hono/jsx/dom/server"; -import { existsSync } from "fs"; +import { generate } from "models.dev"; import path from "path"; export const Providers = await generate( diff --git a/packages/web/src/server.ts b/packages/web/src/server.ts index fb7040d8..2ec58f18 100644 --- a/packages/web/src/server.ts +++ b/packages/web/src/server.ts @@ -1,6 +1,6 @@ +import path from "path"; import Index from "../index.html"; import { Rendered } from "./render"; -import path from "path"; Bun.serve({ port: 16_000, diff --git a/packages/web/sst-env.d.ts b/packages/web/sst-env.d.ts index cf5e5fbe..0c6eeb07 100644 --- a/packages/web/sst-env.d.ts +++ b/packages/web/sst-env.d.ts @@ -6,4 +6,3 @@ /// import "sst"; -export {}; diff --git a/sst-env.d.ts b/sst-env.d.ts index 550effe6..4125e1f6 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -21,4 +21,3 @@ declare module "sst" { } import "sst"; -export {};