feat: Complete migration to kepler-ai-sdk
This commit is contained in:
parent
071e1016b1
commit
31d72543b3
20 changed files with 1162 additions and 550 deletions
8
.mcp.json
Normal file
8
.mcp.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"svelte-llm": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://svelte-llm.stanislav.garden/mcp/mcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
bun.lock
98
bun.lock
|
|
@ -13,7 +13,7 @@
|
||||||
"@fontsource-variable/nunito-sans": "^5.2.6",
|
"@fontsource-variable/nunito-sans": "^5.2.6",
|
||||||
"@fontsource-variable/open-sans": "^5.2.6",
|
"@fontsource-variable/open-sans": "^5.2.6",
|
||||||
"@fontsource/instrument-serif": "^5.2.6",
|
"@fontsource/instrument-serif": "^5.2.6",
|
||||||
"@keplersystems/kepler-ai-sdk": "^1.0.5",
|
"@keplersystems/kepler-ai-sdk": "file:./tmp/kepler-ai-sdk",
|
||||||
"better-auth": "^1.2.9",
|
"better-auth": "^1.2.9",
|
||||||
"convex-helpers": "^0.1.94",
|
"convex-helpers": "^0.1.94",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
|
|
@ -21,10 +21,13 @@
|
||||||
"zod": "^3.25.64",
|
"zod": "^3.25.64",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.29.0",
|
||||||
"@better-auth-kit/convex": "^1.2.2",
|
"@better-auth-kit/convex": "^1.2.2",
|
||||||
"@eslint/compat": "^1.2.5",
|
"@eslint/compat": "^1.2.5",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@google/generative-ai": "^0.21.0",
|
||||||
"@iconify/json": "^2.2.349",
|
"@iconify/json": "^2.2.349",
|
||||||
|
"@mistralai/mistralai": "^1.1.0",
|
||||||
"@playwright/test": "^1.49.1",
|
"@playwright/test": "^1.49.1",
|
||||||
"@shikijs/langs": "^3.6.0",
|
"@shikijs/langs": "^3.6.0",
|
||||||
"@shikijs/markdown-it": "^3.6.0",
|
"@shikijs/markdown-it": "^3.6.0",
|
||||||
|
|
@ -38,6 +41,7 @@
|
||||||
"@vercel/functions": "^2.2.0",
|
"@vercel/functions": "^2.2.0",
|
||||||
"bits-ui": "^2.8.5",
|
"bits-ui": "^2.8.5",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cohere-ai": "^7.14.0",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"convex": "^1.24.8",
|
"convex": "^1.24.8",
|
||||||
"convex-svelte": "^0.0.11",
|
"convex-svelte": "^0.0.11",
|
||||||
|
|
@ -79,7 +83,7 @@
|
||||||
|
|
||||||
"@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="],
|
"@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="],
|
||||||
|
|
||||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.55.1", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-gjOMS4chmm8BxClKmCjNHmvf1FrO1Cn++CSX6K3YCZjz5JG4I9ZttQ/xEH4FBsz6HQyZvnUpiKlOAkmxaGmEaQ=="],
|
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.29.2", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-5dwiOPO/AZvhY4bJIG9vjFKU9Kza3hA6VEsbIQg6L9vny2RQIpCFhV50nB9IrG2edZaHZb4HuQ9Wmsn5zgWyZg=="],
|
||||||
|
|
||||||
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
|
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
|
||||||
|
|
||||||
|
|
@ -259,6 +263,8 @@
|
||||||
|
|
||||||
"@google/genai": ["@google/genai@1.16.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.4" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-hdTYu39QgDFxv+FB6BK2zi4UIJGWhx2iPc0pHQ0C5Q/RCi+m+4gsryIzTGO+riqWcUA8/WGYp6hpqckdOBNysw=="],
|
"@google/genai": ["@google/genai@1.16.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.4" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-hdTYu39QgDFxv+FB6BK2zi4UIJGWhx2iPc0pHQ0C5Q/RCi+m+4gsryIzTGO+riqWcUA8/WGYp6hpqckdOBNysw=="],
|
||||||
|
|
||||||
|
"@google/generative-ai": ["@google/generative-ai@0.21.0", "", {}, "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg=="],
|
||||||
|
|
||||||
"@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="],
|
"@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="],
|
||||||
|
|
||||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||||
|
|
@ -277,6 +283,8 @@
|
||||||
|
|
||||||
"@internationalized/date": ["@internationalized/date@3.9.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg=="],
|
"@internationalized/date": ["@internationalized/date@3.9.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||||
|
|
||||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||||
|
|
||||||
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
|
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
|
||||||
|
|
@ -291,7 +299,7 @@
|
||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
|
||||||
|
|
||||||
"@keplersystems/kepler-ai-sdk": ["@keplersystems/kepler-ai-sdk@1.0.5", "", { "dependencies": { "@anthropic-ai/sdk": "^0.55.0", "@google/genai": "^1.7.0", "@mistralai/mistralai": "^1.7.2", "cohere-ai": "^7.17.1", "exa-js": "^1.8.20", "openai": "^5.7.0" }, "peerDependencies": { "typescript": "^5" } }, "sha512-aynAClqQtS1Xkt53lZ4TdOS1YbtkfaYIrWxSmNXEV5DahLzHRi8YJ3zooAq2cmHUwjaEXmpaB2AMFdwAxWWTkQ=="],
|
"@keplersystems/kepler-ai-sdk": ["@keplersystems/kepler-ai-sdk@file:tmp/kepler-ai-sdk", { "dependencies": { "@anthropic-ai/sdk": "^0.55.0", "@google/genai": "^1.7.0", "@mistralai/mistralai": "^1.7.2", "cohere-ai": "^7.17.1", "exa-js": "^1.8.20", "openai": "^5.7.0" }, "devDependencies": { "@types/bun": "latest", "rimraf": "^5.0.5" }, "peerDependencies": { "typescript": "^5" } }],
|
||||||
|
|
||||||
"@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="],
|
"@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="],
|
||||||
|
|
||||||
|
|
@ -317,6 +325,8 @@
|
||||||
|
|
||||||
"@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.4.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.4.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-F7mIZY2Eao2TaoVqigGMLv+NDdpwuBKU1fucHPONfzaBS4JXXCNCmfO0Z3dsy7JzKGqtDcYC1mr9JjaZQZNiuw=="],
|
"@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.4.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.4.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-F7mIZY2Eao2TaoVqigGMLv+NDdpwuBKU1fucHPONfzaBS4JXXCNCmfO0Z3dsy7JzKGqtDcYC1mr9JjaZQZNiuw=="],
|
||||||
|
|
||||||
|
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||||
|
|
||||||
"@playwright/test": ["@playwright/test@1.55.0", "", { "dependencies": { "playwright": "1.55.0" }, "bin": { "playwright": "cli.js" } }, "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ=="],
|
"@playwright/test": ["@playwright/test@1.55.0", "", { "dependencies": { "playwright": "1.55.0" }, "bin": { "playwright": "cli.js" } }, "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ=="],
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
@ -515,6 +525,8 @@
|
||||||
|
|
||||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
|
|
||||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
@ -537,6 +549,12 @@
|
||||||
|
|
||||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@18.19.123", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg=="],
|
||||||
|
|
||||||
|
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||||
|
|
||||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||||
|
|
||||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||||
|
|
@ -591,6 +609,8 @@
|
||||||
|
|
||||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||||
|
|
||||||
|
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||||
|
|
||||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
@ -633,6 +653,8 @@
|
||||||
|
|
||||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
@ -697,6 +719,8 @@
|
||||||
|
|
||||||
"cssstyle": ["cssstyle@4.6.0", "", { "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" } }, "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="],
|
"cssstyle": ["cssstyle@4.6.0", "", { "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" } }, "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
"data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
|
"data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
@ -731,6 +755,8 @@
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
|
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
|
|
||||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||||
|
|
||||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
@ -819,11 +845,13 @@
|
||||||
|
|
||||||
"focus-trap": ["focus-trap@7.6.5", "", { "dependencies": { "tabbable": "^6.2.0" } }, "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg=="],
|
"focus-trap": ["focus-trap@7.6.5", "", { "dependencies": { "tabbable": "^6.2.0" } }, "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg=="],
|
||||||
|
|
||||||
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
|
|
||||||
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||||
|
|
||||||
"form-data-encoder": ["form-data-encoder@4.1.0", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="],
|
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
|
||||||
|
|
||||||
"formdata-node": ["formdata-node@6.0.3", "", {}, "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="],
|
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
|
@ -839,6 +867,8 @@
|
||||||
|
|
||||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
|
|
||||||
|
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
"globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="],
|
"globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="],
|
||||||
|
|
@ -879,6 +909,8 @@
|
||||||
|
|
||||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||||
|
|
||||||
|
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
|
||||||
|
|
||||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||||
|
|
||||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
@ -911,6 +943,8 @@
|
||||||
|
|
||||||
"isomorphic-dompurify": ["isomorphic-dompurify@2.26.0", "", { "dependencies": { "dompurify": "^3.2.6", "jsdom": "^26.1.0" } }, "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw=="],
|
"isomorphic-dompurify": ["isomorphic-dompurify@2.26.0", "", { "dependencies": { "dompurify": "^3.2.6", "jsdom": "^26.1.0" } }, "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw=="],
|
||||||
|
|
||||||
|
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||||
|
|
||||||
"jest-axe": ["jest-axe@9.0.0", "", { "dependencies": { "axe-core": "4.9.1", "chalk": "4.1.2", "jest-matcher-utils": "29.2.2", "lodash.merge": "4.6.2" } }, "sha512-Xt7O0+wIpW31lv0SO1wQZUTyJE7DEmnDEZeTt9/S9L5WUywxrv8BrgvTuQEqujtfaQOcJ70p4wg7UUgK1E2F5g=="],
|
"jest-axe": ["jest-axe@9.0.0", "", { "dependencies": { "axe-core": "4.9.1", "chalk": "4.1.2", "jest-matcher-utils": "29.2.2", "lodash.merge": "4.6.2" } }, "sha512-Xt7O0+wIpW31lv0SO1wQZUTyJE7DEmnDEZeTt9/S9L5WUywxrv8BrgvTuQEqujtfaQOcJ70p4wg7UUgK1E2F5g=="],
|
||||||
|
|
||||||
"jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
|
"jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
|
||||||
|
|
@ -1059,6 +1093,8 @@
|
||||||
|
|
||||||
"neverthrow": ["neverthrow@8.2.0", "", { "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.24.0" } }, "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ=="],
|
"neverthrow": ["neverthrow@8.2.0", "", { "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.24.0" } }, "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ=="],
|
||||||
|
|
||||||
|
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||||
|
|
||||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||||
|
|
||||||
"nwsapi": ["nwsapi@2.2.21", "", {}, "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA=="],
|
"nwsapi": ["nwsapi@2.2.21", "", {}, "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA=="],
|
||||||
|
|
@ -1077,6 +1113,8 @@
|
||||||
|
|
||||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||||
|
|
||||||
|
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||||
|
|
||||||
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
||||||
|
|
||||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||||
|
|
@ -1087,6 +1125,8 @@
|
||||||
|
|
||||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
|
|
||||||
|
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
|
|
||||||
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||||
|
|
||||||
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||||
|
|
@ -1159,6 +1199,8 @@
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
|
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.49.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.49.0", "@rollup/rollup-android-arm64": "4.49.0", "@rollup/rollup-darwin-arm64": "4.49.0", "@rollup/rollup-darwin-x64": "4.49.0", "@rollup/rollup-freebsd-arm64": "4.49.0", "@rollup/rollup-freebsd-x64": "4.49.0", "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", "@rollup/rollup-linux-arm-musleabihf": "4.49.0", "@rollup/rollup-linux-arm64-gnu": "4.49.0", "@rollup/rollup-linux-arm64-musl": "4.49.0", "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", "@rollup/rollup-linux-ppc64-gnu": "4.49.0", "@rollup/rollup-linux-riscv64-gnu": "4.49.0", "@rollup/rollup-linux-riscv64-musl": "4.49.0", "@rollup/rollup-linux-s390x-gnu": "4.49.0", "@rollup/rollup-linux-x64-gnu": "4.49.0", "@rollup/rollup-linux-x64-musl": "4.49.0", "@rollup/rollup-win32-arm64-msvc": "4.49.0", "@rollup/rollup-win32-ia32-msvc": "4.49.0", "@rollup/rollup-win32-x64-msvc": "4.49.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA=="],
|
"rollup": ["rollup@4.49.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.49.0", "@rollup/rollup-android-arm64": "4.49.0", "@rollup/rollup-darwin-arm64": "4.49.0", "@rollup/rollup-darwin-x64": "4.49.0", "@rollup/rollup-freebsd-arm64": "4.49.0", "@rollup/rollup-freebsd-x64": "4.49.0", "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", "@rollup/rollup-linux-arm-musleabihf": "4.49.0", "@rollup/rollup-linux-arm64-gnu": "4.49.0", "@rollup/rollup-linux-arm64-musl": "4.49.0", "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", "@rollup/rollup-linux-ppc64-gnu": "4.49.0", "@rollup/rollup-linux-riscv64-gnu": "4.49.0", "@rollup/rollup-linux-riscv64-musl": "4.49.0", "@rollup/rollup-linux-s390x-gnu": "4.49.0", "@rollup/rollup-linux-x64-gnu": "4.49.0", "@rollup/rollup-linux-x64-musl": "4.49.0", "@rollup/rollup-win32-arm64-msvc": "4.49.0", "@rollup/rollup-win32-ia32-msvc": "4.49.0", "@rollup/rollup-win32-x64-msvc": "4.49.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA=="],
|
||||||
|
|
||||||
"rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
|
"rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
|
||||||
|
|
@ -1203,6 +1245,8 @@
|
||||||
|
|
||||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||||
|
|
||||||
|
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
|
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
@ -1215,12 +1259,16 @@
|
||||||
|
|
||||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||||
|
|
||||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
||||||
|
|
||||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
|
|
@ -1303,6 +1351,8 @@
|
||||||
|
|
||||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||||
|
|
||||||
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
|
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
|
||||||
|
|
||||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||||
|
|
@ -1339,6 +1389,8 @@
|
||||||
|
|
||||||
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
|
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
|
||||||
|
|
||||||
|
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||||
|
|
||||||
"webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
|
"webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
|
||||||
|
|
||||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||||
|
|
@ -1357,6 +1409,8 @@
|
||||||
|
|
||||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||||
|
|
||||||
"xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
|
"xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
|
||||||
|
|
@ -1371,7 +1425,7 @@
|
||||||
|
|
||||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||||
|
|
||||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
"yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
|
|
@ -1397,6 +1451,14 @@
|
||||||
|
|
||||||
"@iconify/utils/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
"@iconify/utils/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||||
|
|
||||||
|
"@keplersystems/kepler-ai-sdk/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.55.1", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-gjOMS4chmm8BxClKmCjNHmvf1FrO1Cn++CSX6K3YCZjz5JG4I9ZttQ/xEH4FBsz6HQyZvnUpiKlOAkmxaGmEaQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||||
|
|
@ -1413,6 +1475,8 @@
|
||||||
|
|
||||||
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||||
|
|
||||||
|
"@types/node-fetch/@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
@ -1423,14 +1487,20 @@
|
||||||
|
|
||||||
"bits-ui/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="],
|
"bits-ui/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
|
||||||
|
|
||||||
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
"convict/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
"cohere-ai/form-data-encoder": ["form-data-encoder@4.1.0", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="],
|
||||||
|
|
||||||
|
"cohere-ai/formdata-node": ["formdata-node@6.0.3", "", {}, "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="],
|
||||||
|
|
||||||
"exa-js/dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
|
"exa-js/dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
|
||||||
|
|
||||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"jest-diff/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
"jest-diff/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||||
|
|
||||||
"jest-matcher-utils/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
"jest-matcher-utils/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||||
|
|
@ -1467,12 +1537,26 @@
|
||||||
|
|
||||||
"vitest/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"vitest/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||||
|
|
||||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||||
|
|
||||||
|
"@types/node-fetch/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||||
|
|
||||||
|
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
"jest-diff/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
"jest-diff/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||||
|
|
||||||
"jest-diff/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
"jest-diff/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
"@fontsource-variable/nunito-sans": "^5.2.6",
|
"@fontsource-variable/nunito-sans": "^5.2.6",
|
||||||
"@fontsource-variable/open-sans": "^5.2.6",
|
"@fontsource-variable/open-sans": "^5.2.6",
|
||||||
"@fontsource/instrument-serif": "^5.2.6",
|
"@fontsource/instrument-serif": "^5.2.6",
|
||||||
"@keplersystems/kepler-ai-sdk": "^1.0.5",
|
"@keplersystems/kepler-ai-sdk": "file:./tmp/kepler-ai-sdk",
|
||||||
"better-auth": "^1.2.9",
|
"better-auth": "^1.2.9",
|
||||||
"convex-helpers": "^0.1.94",
|
"convex-helpers": "^0.1.94",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
|
|
|
||||||
|
|
@ -101,17 +101,21 @@ export const set = mutation({
|
||||||
)
|
)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (args.enabled && existing) return; // nothing to do here
|
if (args.enabled) {
|
||||||
|
// Enable model: insert if not exists
|
||||||
if (existing) {
|
if (!existing) {
|
||||||
await ctx.db.delete(existing._id);
|
|
||||||
} else {
|
|
||||||
await ctx.db.insert('user_enabled_models', {
|
await ctx.db.insert('user_enabled_models', {
|
||||||
...object.pick(args, ['provider', 'model_id']),
|
...object.pick(args, ['provider', 'model_id']),
|
||||||
user_id: session.userId,
|
user_id: session.userId,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Disable model: delete if exists
|
||||||
|
if (existing) {
|
||||||
|
await ctx.db.delete(existing._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
import { models as modelsState } from '$lib/state/models.svelte';
|
import { models as modelsState } from '$lib/state/models.svelte';
|
||||||
import { session } from '$lib/state/session.svelte';
|
import { session } from '$lib/state/session.svelte';
|
||||||
import { settings } from '$lib/state/settings.svelte';
|
import { settings } from '$lib/state/settings.svelte';
|
||||||
import { Provider } from '$lib/types';
|
import { Provider, PROVIDER_META } from '$lib/types';
|
||||||
import { fuzzysearch } from '$lib/utils/fuzzy-search';
|
import { fuzzysearch } from '$lib/utils/fuzzy-search';
|
||||||
import { supportsImages, supportsReasoning } from '$lib/utils/model-capabilities';
|
import { supportsImages, supportsReasoning, supportsStreaming, supportsToolCalls } from '$lib/utils/model-capabilities';
|
||||||
import { capitalize } from '$lib/utils/strings';
|
import { capitalize } from '$lib/utils/strings';
|
||||||
import { cn } from '$lib/utils/utils';
|
import { cn } from '$lib/utils/utils';
|
||||||
import { type Component } from 'svelte';
|
import { type Component } from 'svelte';
|
||||||
|
|
@ -57,7 +57,14 @@
|
||||||
session_token: session.current?.session.token ?? '',
|
session_token: session.current?.session.token ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const enabledArr = $derived(Object.values(enabledModelsQuery.data ?? {}));
|
// Get enabled models from our models state with ModelInfo data
|
||||||
|
const enabledArr = $derived.by(() => {
|
||||||
|
const enabledModelIds = Object.keys(enabledModelsQuery.data ?? {});
|
||||||
|
const enabledModels = modelsState.all().filter(model =>
|
||||||
|
enabledModelIds.some(id => id.includes(model.id))
|
||||||
|
);
|
||||||
|
return enabledModels;
|
||||||
|
});
|
||||||
|
|
||||||
modelsState.init();
|
modelsState.init();
|
||||||
|
|
||||||
|
|
@ -133,37 +140,38 @@
|
||||||
fuzzysearch({
|
fuzzysearch({
|
||||||
haystack: enabledArr,
|
haystack: enabledArr,
|
||||||
needle: search,
|
needle: search,
|
||||||
property: 'model_id',
|
property: 'id',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Group models by company
|
// Group models by provider
|
||||||
const groupedModels = $derived.by(() => {
|
const groupedModels = $derived.by(() => {
|
||||||
const groups: Record<string, typeof filteredModels> = {};
|
const groups: Record<Provider, typeof filteredModels> = {} as Record<Provider, typeof filteredModels>;
|
||||||
|
|
||||||
filteredModels.forEach((model) => {
|
filteredModels.forEach((model) => {
|
||||||
const company = getCompanyFromModelId(model.model_id);
|
const provider = model.provider as Provider;
|
||||||
if (!groups[company]) {
|
if (!groups[provider]) {
|
||||||
groups[company] = [];
|
groups[provider] = [];
|
||||||
}
|
}
|
||||||
groups[company].push(model);
|
groups[provider].push(model);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort companies with known icons first
|
// Sort by provider order and name
|
||||||
const result = Object.entries(groups).sort(([a], [b]) => {
|
const result = Object.entries(groups)
|
||||||
const aHasIcon = companyIcons[a] ? 0 : 1;
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
const bHasIcon = companyIcons[b] ? 0 : 1;
|
.map(([provider, models]) => [
|
||||||
return aHasIcon - bHasIcon || a.localeCompare(b);
|
provider,
|
||||||
});
|
models.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
] as [Provider, typeof models]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentModel = $derived(enabledArr.find((m) => m.model_id === settings.modelId));
|
const currentModel = $derived(enabledArr.find((m) => m.id === settings.modelId));
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!enabledArr.find((m) => m.model_id === settings.modelId) && enabledArr.length > 0) {
|
if (!enabledArr.find((m) => m.id === settings.modelId) && enabledArr.length > 0) {
|
||||||
settings.modelId = enabledArr[0]!.model_id;
|
settings.modelId = enabledArr[0]!.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -179,8 +187,10 @@
|
||||||
{ from: 'o3', to: 'o3' },
|
{ from: 'o3', to: 'o3' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function formatModelName(modelId: string) {
|
function formatModelName(model: { id: string; name: string }) {
|
||||||
const cleanId = modelId.replace(/^[^/]+\//, '');
|
// Use the name field if available, fallback to processing ID
|
||||||
|
const displayName = model.name || model.id;
|
||||||
|
const cleanId = displayName.replace(/^[^/]+\//, '');
|
||||||
const parts = cleanId.split(/[-_,:]/);
|
const parts = cleanId.split(/[-_,:]/);
|
||||||
|
|
||||||
const formattedParts = parts.map((part) => {
|
const formattedParts = parts.map((part) => {
|
||||||
|
|
@ -228,17 +238,20 @@
|
||||||
const activeModelInfo = $derived.by(() => {
|
const activeModelInfo = $derived.by(() => {
|
||||||
if (activeModel === '') return null;
|
if (activeModel === '') return null;
|
||||||
|
|
||||||
const model = enabledArr.find((m) => m.model_id === activeModel);
|
const model = enabledArr.find((m) => m.id === activeModel);
|
||||||
|
|
||||||
if (!model) return null;
|
if (!model) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...model,
|
...model,
|
||||||
formatted: formatModelName(activeModel),
|
formatted: formatModelName(model),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const pinnedModels = $derived(enabledArr.filter((m) => isPinned(m)));
|
// For now, we'll need to maintain pinned models using the old enabled models structure
|
||||||
|
// until we migrate the pinning system to work with the new ModelInfo structure
|
||||||
|
const enabledModelsData = $derived(Object.values(enabledModelsQuery.data ?? {}));
|
||||||
|
const pinnedModels = $derived(enabledModelsData.filter((m) => isPinned(m)));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
|
|
@ -259,12 +272,12 @@
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 pr-2">
|
<div class="flex items-center gap-2 pr-2">
|
||||||
{#if currentModel && getModelIcon(currentModel.model_id)}
|
{#if currentModel && getModelIcon(currentModel.id)}
|
||||||
{@const IconComponent = getModelIcon(currentModel.model_id)}
|
{@const IconComponent = getModelIcon(currentModel.id)}
|
||||||
<IconComponent class="size-3" />
|
<IconComponent class="size-3" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
{currentModel ? formatModelName(currentModel.model_id).full : 'Select model'}
|
{currentModel ? formatModelName(currentModel).full : 'Select model'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDownIcon class="size-4 opacity-50" />
|
<ChevronDownIcon class="size-4 opacity-50" />
|
||||||
|
|
@ -324,12 +337,9 @@
|
||||||
>
|
>
|
||||||
{#if view === 'favorites' && pinnedModels.length > 0}
|
{#if view === 'favorites' && pinnedModels.length > 0}
|
||||||
{#each pinnedModels as model (model._id)}
|
{#each pinnedModels as model (model._id)}
|
||||||
{@const formatted = formatModelName(model.model_id)}
|
{@const modelInfo = enabledArr.find((m) => m.id === model.model_id)}
|
||||||
{@const openRouterModel = modelsState
|
{@const formatted = modelInfo ? formatModelName(modelInfo) : { full: model.model_id, primary: model.model_id, secondary: '' }}
|
||||||
.from(Provider.OpenRouter)
|
{@const disabled = onlyImageModels && modelInfo && !supportsImages(modelInfo)}
|
||||||
.find((m) => m.id === model.model_id)}
|
|
||||||
{@const disabled =
|
|
||||||
onlyImageModels && openRouterModel && !supportsImages(openRouterModel)}
|
|
||||||
|
|
||||||
<Command.Item
|
<Command.Item
|
||||||
value={model.model_id}
|
value={model.model_id}
|
||||||
|
|
@ -354,7 +364,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex place-items-center gap-1">
|
<div class="flex place-items-center gap-1">
|
||||||
{#if openRouterModel && supportsImages(openRouterModel)}
|
{#if modelInfo && supportsImages(modelInfo)}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -364,11 +374,11 @@
|
||||||
<EyeIcon class="size-3" />
|
<EyeIcon class="size-3" />
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
Supports image analysis
|
Supports vision/image analysis
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if openRouterModel && supportsReasoning(openRouterModel)}
|
{#if modelInfo && supportsReasoning(modelInfo)}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -381,6 +391,34 @@
|
||||||
Supports reasoning
|
Supports reasoning
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if modelInfo && supportsStreaming(modelInfo)}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-blue-500 bg-blue-500/50 p-1 text-blue-400"
|
||||||
|
>
|
||||||
|
<ZapIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports streaming responses
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if modelInfo && supportsToolCalls(modelInfo)}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-orange-500 bg-orange-500/50 p-1 text-orange-400"
|
||||||
|
>
|
||||||
|
<CpuIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports tool/function calling
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -394,22 +432,25 @@
|
||||||
</Command.GroupHeading>
|
</Command.GroupHeading>
|
||||||
<Command.GroupItems class="grid grid-cols-2 gap-3 px-3 pb-3 md:grid-cols-4">
|
<Command.GroupItems class="grid grid-cols-2 gap-3 px-3 pb-3 md:grid-cols-4">
|
||||||
{#each pinnedModels as model (model._id)}
|
{#each pinnedModels as model (model._id)}
|
||||||
{@render modelCard(model)}
|
{@const modelInfo = enabledArr.find((m) => m.id === model.model_id)}
|
||||||
|
{#if modelInfo}
|
||||||
|
{@render modelCard(modelInfo)}
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</Command.GroupItems>
|
</Command.GroupItems>
|
||||||
</Command.Group>
|
</Command.Group>
|
||||||
{/if}
|
{/if}
|
||||||
{#each groupedModels as [company, models] (company)}
|
{#each groupedModels as [provider, models] (provider)}
|
||||||
{@const filteredModels = models.filter((m) => !isPinned(m))}
|
{@const providerMeta = PROVIDER_META[provider]}
|
||||||
{#if filteredModels.length > 0}
|
{#if models.length > 0}
|
||||||
<Command.Group class="space-y-2">
|
<Command.Group class="space-y-2">
|
||||||
<Command.GroupHeading
|
<Command.GroupHeading
|
||||||
class="text-heading/75 flex scroll-m-40 items-center gap-2 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide capitalize"
|
class="text-heading/75 flex scroll-m-40 items-center gap-2 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide"
|
||||||
>
|
>
|
||||||
{company}
|
{providerMeta.title}
|
||||||
</Command.GroupHeading>
|
</Command.GroupHeading>
|
||||||
<Command.GroupItems class="grid grid-cols-2 gap-3 px-3 pb-3 md:grid-cols-4">
|
<Command.GroupItems class="grid grid-cols-2 gap-3 px-3 pb-3 md:grid-cols-4">
|
||||||
{#each filteredModels as model (model._id)}
|
{#each models as model (model.id)}
|
||||||
{@render modelCard(model)}
|
{@render modelCard(model)}
|
||||||
{/each}
|
{/each}
|
||||||
</Command.GroupItems>
|
</Command.GroupItems>
|
||||||
|
|
@ -457,14 +498,12 @@
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
|
|
||||||
{#snippet modelCard(model: (typeof enabledArr)[number])}
|
{#snippet modelCard(model: (typeof enabledArr)[number])}
|
||||||
{@const formatted = formatModelName(model.model_id)}
|
{@const formatted = formatModelName(model)}
|
||||||
{@const openRouterModel = modelsState
|
{@const disabled = onlyImageModels && !supportsImages(model)}
|
||||||
.from(Provider.OpenRouter)
|
{@const enabledModelData = enabledModelsData.find(m => m.model_id === model.id)}
|
||||||
.find((m) => m.id === model.model_id)}
|
|
||||||
{@const disabled = onlyImageModels && openRouterModel && !supportsImages(openRouterModel)}
|
|
||||||
|
|
||||||
<Command.Item
|
<Command.Item
|
||||||
value={model.model_id}
|
value={model.id}
|
||||||
class={cn(
|
class={cn(
|
||||||
'border-border bg-popover group/item flex gap-2 rounded-lg border p-2',
|
'border-border bg-popover group/item flex gap-2 rounded-lg border p-2',
|
||||||
'relative scroll-m-36 select-none',
|
'relative scroll-m-36 select-none',
|
||||||
|
|
@ -472,11 +511,11 @@
|
||||||
'h-36 w-32 flex-col items-center justify-center',
|
'h-36 w-32 flex-col items-center justify-center',
|
||||||
disabled && 'opacity-50'
|
disabled && 'opacity-50'
|
||||||
)}
|
)}
|
||||||
onSelect={() => modelSelected(model.model_id)}
|
onSelect={() => modelSelected(model.id)}
|
||||||
>
|
>
|
||||||
<div class={cn('flex flex-col items-center')}>
|
<div class={cn('flex flex-col items-center')}>
|
||||||
{#if getModelIcon(model.model_id)}
|
{#if getModelIcon(model.id)}
|
||||||
{@const ModelIcon = getModelIcon(model.model_id)}
|
{@const ModelIcon = getModelIcon(model.id)}
|
||||||
<ModelIcon class="size-6 shrink-0" />
|
<ModelIcon class="size-6 shrink-0" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
@ -494,7 +533,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex place-items-center gap-1">
|
<div class="flex place-items-center gap-1">
|
||||||
{#if openRouterModel && supportsImages(openRouterModel)}
|
{#if supportsImages(model)}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -504,11 +543,11 @@
|
||||||
<EyeIcon class="size-3" />
|
<EyeIcon class="size-3" />
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
Supports image analysis
|
Supports vision/image analysis
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if openRouterModel && supportsReasoning(openRouterModel)}
|
{#if supportsReasoning(model)}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -521,8 +560,37 @@
|
||||||
Supports reasoning
|
Supports reasoning
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if supportsStreaming(model)}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-blue-500 bg-blue-500/50 p-1 text-blue-400"
|
||||||
|
>
|
||||||
|
<ZapIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports streaming responses
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if supportsToolCalls(model)}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-orange-500 bg-orange-500/50 p-1 text-orange-400"
|
||||||
|
>
|
||||||
|
<CpuIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports tool/function calling
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if enabledModelData}
|
||||||
<div
|
<div
|
||||||
class="bg-popover absolute top-1 right-1 scale-75 rounded-md p-1 transition-all group-hover/item:scale-100 group-hover/item:opacity-100 md:opacity-0"
|
class="bg-popover absolute top-1 right-1 scale-75 rounded-md p-1 transition-all group-hover/item:scale-100 group-hover/item:opacity-100 md:opacity-0"
|
||||||
>
|
>
|
||||||
|
|
@ -532,15 +600,16 @@
|
||||||
class="size-7"
|
class="size-7"
|
||||||
onclick={(e: MouseEvent) => {
|
onclick={(e: MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
togglePin(model._id);
|
togglePin(enabledModelData._id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if isPinned(model)}
|
{#if isPinned(enabledModelData)}
|
||||||
<PinOffIcon class="size-4" />
|
<PinOffIcon class="size-4" />
|
||||||
{:else}
|
{:else}
|
||||||
<PinIcon class="size-4" />
|
<PinIcon class="size-4" />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
160
src/lib/services/model-loader.server.ts
Normal file
160
src/lib/services/model-loader.server.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { ConvexHttpClient } from 'convex/browser';
|
||||||
|
import { PUBLIC_CONVEX_URL } from '$env/static/public';
|
||||||
|
import { api } from '$lib/backend/convex/_generated/api';
|
||||||
|
import { Provider } from '$lib/types';
|
||||||
|
import { createModelManager } from './model-manager';
|
||||||
|
import type { UserApiKeys } from './model-manager';
|
||||||
|
import type { ModelInfo } from '@keplersystems/kepler-ai-sdk';
|
||||||
|
import { ResultAsync } from 'neverthrow';
|
||||||
|
|
||||||
|
export interface MultiProviderModels {
|
||||||
|
[Provider.OpenAI]?: ModelInfo[];
|
||||||
|
[Provider.Anthropic]?: ModelInfo[];
|
||||||
|
[Provider.Gemini]?: ModelInfo[];
|
||||||
|
[Provider.Mistral]?: ModelInfo[];
|
||||||
|
[Provider.Cohere]?: ModelInfo[];
|
||||||
|
[Provider.OpenRouter]?: ModelInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new ConvexHttpClient(PUBLIC_CONVEX_URL);
|
||||||
|
|
||||||
|
// Cache models for 10 minutes to avoid excessive API calls
|
||||||
|
const MODEL_CACHE_TTL = 10 * 60 * 1000;
|
||||||
|
const modelCache = new Map<string, { models: MultiProviderModels; timestamp: number }>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load models for a specific user based on their API keys
|
||||||
|
*/
|
||||||
|
export async function loadUserModels(sessionToken: string): Promise<MultiProviderModels> {
|
||||||
|
const cacheKey = sessionToken;
|
||||||
|
const cached = modelCache.get(cacheKey);
|
||||||
|
|
||||||
|
// Return cached models if still valid
|
||||||
|
if (cached && Date.now() - cached.timestamp < MODEL_CACHE_TTL) {
|
||||||
|
console.log('Returning cached models:', Object.keys(cached.models));
|
||||||
|
return cached.models;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user's API keys
|
||||||
|
const userApiKeys = await getUserApiKeys(sessionToken);
|
||||||
|
console.log('getUserApiKeys result:', userApiKeys);
|
||||||
|
if (!userApiKeys) {
|
||||||
|
console.log('No user API keys found');
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ModelManager with user's API keys
|
||||||
|
const modelManager = createModelManager();
|
||||||
|
modelManager.initializeProviders(userApiKeys);
|
||||||
|
console.log('Enabled providers:', modelManager.getEnabledProviders());
|
||||||
|
|
||||||
|
// Load models from all enabled providers
|
||||||
|
const models: MultiProviderModels = {};
|
||||||
|
const enabledProviders = modelManager.getEnabledProviders();
|
||||||
|
console.log('Loading models from providers:', enabledProviders);
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
enabledProviders.map(async (provider) => {
|
||||||
|
try {
|
||||||
|
console.log(`Loading models from ${provider}...`);
|
||||||
|
const providerModels = await modelManager.getModelsByProvider(provider);
|
||||||
|
console.log(`${provider} returned ${providerModels.length} models`);
|
||||||
|
if (providerModels.length > 0) {
|
||||||
|
models[provider] = providerModels;
|
||||||
|
}
|
||||||
|
return { provider, count: providerModels.length };
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load models from ${provider}:`, error);
|
||||||
|
return { provider, error: error.message };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Model loading results:', results);
|
||||||
|
console.log('Final models object:', Object.keys(models));
|
||||||
|
|
||||||
|
// Cache the results
|
||||||
|
modelCache.set(cacheKey, {
|
||||||
|
models,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return models;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load user models:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load models without authentication (fallback to empty state)
|
||||||
|
*/
|
||||||
|
export function loadGuestModels(): MultiProviderModels {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear model cache for a specific user or all users
|
||||||
|
*/
|
||||||
|
export function clearModelCache(sessionToken?: string): void {
|
||||||
|
if (sessionToken) {
|
||||||
|
modelCache.delete(sessionToken);
|
||||||
|
} else {
|
||||||
|
modelCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's API keys from Convex
|
||||||
|
*/
|
||||||
|
async function getUserApiKeys(sessionToken: string): Promise<UserApiKeys | null> {
|
||||||
|
const keysResult = await ResultAsync.fromPromise(
|
||||||
|
client.query(api.user_keys.all, {
|
||||||
|
session_token: sessionToken,
|
||||||
|
}),
|
||||||
|
(e) => `Failed to get user API keys: ${e}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keysResult.isErr()) {
|
||||||
|
console.error('Failed to get user API keys:', keysResult.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = keysResult.value;
|
||||||
|
return {
|
||||||
|
openai: keys.openai,
|
||||||
|
anthropic: keys.anthropic,
|
||||||
|
google: keys.gemini,
|
||||||
|
mistral: keys.mistral,
|
||||||
|
cohere: keys.cohere,
|
||||||
|
openrouter: keys.openrouter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get models for a specific provider (useful for partial loading)
|
||||||
|
*/
|
||||||
|
export async function loadProviderModels(
|
||||||
|
sessionToken: string,
|
||||||
|
provider: Provider
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
try {
|
||||||
|
const userApiKeys = await getUserApiKeys(sessionToken);
|
||||||
|
if (!userApiKeys) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelManager = createModelManager();
|
||||||
|
modelManager.initializeProviders(userApiKeys);
|
||||||
|
|
||||||
|
if (!modelManager.hasProviderEnabled(provider)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return await modelManager.getModelsByProvider(provider);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load models from ${provider}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ export interface ProviderConfig {
|
||||||
export interface UserApiKeys {
|
export interface UserApiKeys {
|
||||||
openai?: string;
|
openai?: string;
|
||||||
anthropic?: string;
|
anthropic?: string;
|
||||||
gemini?: string;
|
google?: string;
|
||||||
mistral?: string;
|
mistral?: string;
|
||||||
cohere?: string;
|
cohere?: string;
|
||||||
openrouter?: string;
|
openrouter?: string;
|
||||||
|
|
@ -52,9 +52,9 @@ export class ChatModelManager {
|
||||||
this.enabledProviders.set('anthropic', provider);
|
this.enabledProviders.set('anthropic', provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userApiKeys.gemini) {
|
if (userApiKeys.google) {
|
||||||
const provider = new GeminiProvider({
|
const provider = new GeminiProvider({
|
||||||
apiKey: userApiKeys.gemini,
|
apiKey: userApiKeys.google,
|
||||||
});
|
});
|
||||||
this.modelManager.addProvider(provider);
|
this.modelManager.addProvider(provider);
|
||||||
this.enabledProviders.set('gemini', provider);
|
this.enabledProviders.set('gemini', provider);
|
||||||
|
|
@ -97,9 +97,19 @@ export class ChatModelManager {
|
||||||
return await this.modelManager.listModels();
|
return await this.modelManager.listModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getModelsByProvider(provider: Provider): Promise<ModelInfo[]> {
|
||||||
|
if (!this.hasProviderEnabled(provider)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const allModels = await this.listAvailableModels();
|
||||||
|
return allModels.filter((model) => model.provider === provider);
|
||||||
|
}
|
||||||
|
|
||||||
async getModelsByCapability(capability: string): Promise<ModelInfo[]> {
|
async getModelsByCapability(capability: string): Promise<ModelInfo[]> {
|
||||||
const allModels = await this.listAvailableModels();
|
const allModels = await this.listAvailableModels();
|
||||||
return allModels.filter(model => model.capabilities[capability as keyof typeof model.capabilities]);
|
return allModels.filter(
|
||||||
|
(model) => model.capabilities[capability as keyof typeof model.capabilities]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasProviderEnabled(provider: Provider): boolean {
|
hasProviderEnabled(provider: Provider): boolean {
|
||||||
|
|
@ -111,7 +121,7 @@ export class ChatModelManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
isModelAvailable(modelId: string): Promise<boolean> {
|
isModelAvailable(modelId: string): Promise<boolean> {
|
||||||
return this.getModel(modelId).then(model => model !== null);
|
return this.getModel(modelId).then((model) => model !== null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { api } from '$lib/backend/convex/_generated/api';
|
import { api } from '$lib/backend/convex/_generated/api';
|
||||||
import { getModelKey } from '$lib/backend/convex/user_enabled_models';
|
import { getModelKey } from '$lib/backend/convex/user_enabled_models';
|
||||||
import type { ProviderModelMap } from '$lib/backend/models/all';
|
|
||||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
|
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
|
||||||
import { createInit } from '$lib/spells/create-init.svelte';
|
import { createInit } from '$lib/spells/create-init.svelte';
|
||||||
import { Provider } from '$lib/types';
|
import { Provider } from '$lib/types';
|
||||||
|
import type { ModelInfo } from '@keplersystems/kepler-ai-sdk';
|
||||||
import { watch } from 'runed';
|
import { watch } from 'runed';
|
||||||
import { session } from './session.svelte';
|
import { session } from './session.svelte';
|
||||||
|
|
||||||
|
export interface ModelWithEnabledStatus extends ModelInfo {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class Models {
|
export class Models {
|
||||||
enabled = $state({} as Record<string, unknown>);
|
enabled = $state({} as Record<string, unknown>);
|
||||||
|
|
||||||
|
|
@ -23,13 +27,103 @@ export class Models {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
from<P extends Provider>(provider: Provider) {
|
/**
|
||||||
return page.data.models[provider].map((m: { id: string }) => {
|
* Get models from a specific provider with enabled status
|
||||||
return {
|
*/
|
||||||
...m,
|
from(provider: Provider): ModelWithEnabledStatus[] {
|
||||||
enabled: this.enabled[getModelKey({ provider, model_id: m.id })] !== undefined,
|
const providerModels = page.data.models[provider] || [];
|
||||||
};
|
return providerModels.map((model: ModelInfo) => ({
|
||||||
}) as Array<ProviderModelMap[P] & { enabled: boolean }>;
|
...model,
|
||||||
|
enabled: this.enabled[getModelKey({ provider, model_id: model.id })] !== undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all models from all providers with enabled status
|
||||||
|
*/
|
||||||
|
all(): ModelWithEnabledStatus[] {
|
||||||
|
const allModels: ModelWithEnabledStatus[] = [];
|
||||||
|
const availableProviders = Object.keys(page.data.models || {}) as Provider[];
|
||||||
|
|
||||||
|
for (const provider of availableProviders) {
|
||||||
|
const providerModels = this.from(provider);
|
||||||
|
allModels.push(...providerModels);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get models that support specific capabilities
|
||||||
|
*/
|
||||||
|
withCapability(capability: keyof ModelInfo['capabilities']): ModelWithEnabledStatus[] {
|
||||||
|
return this.all().filter(model => model.capabilities[capability]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enabled models from all providers
|
||||||
|
*/
|
||||||
|
enabledModels(): ModelWithEnabledStatus[] {
|
||||||
|
return this.all().filter(model => model.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enabled models from a specific provider
|
||||||
|
*/
|
||||||
|
enabledFrom(provider: Provider): ModelWithEnabledStatus[] {
|
||||||
|
return this.from(provider).filter(model => model.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available providers (providers that have models loaded)
|
||||||
|
*/
|
||||||
|
availableProviders(): Provider[] {
|
||||||
|
const models = page.data.models || {};
|
||||||
|
return Object.keys(models).filter(provider =>
|
||||||
|
models[provider as Provider] && models[provider as Provider].length > 0
|
||||||
|
) as Provider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a provider has models loaded
|
||||||
|
*/
|
||||||
|
hasProvider(provider: Provider): boolean {
|
||||||
|
const models = page.data.models[provider];
|
||||||
|
return Array.isArray(models) && models.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search models across all providers
|
||||||
|
*/
|
||||||
|
search(query: string): ModelWithEnabledStatus[] {
|
||||||
|
const lowerQuery = query.toLowerCase();
|
||||||
|
return this.all().filter(model =>
|
||||||
|
model.name.toLowerCase().includes(lowerQuery) ||
|
||||||
|
model.id.toLowerCase().includes(lowerQuery) ||
|
||||||
|
model.description?.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get models sorted by preference (enabled first, then by provider order)
|
||||||
|
*/
|
||||||
|
sorted(): ModelWithEnabledStatus[] {
|
||||||
|
return this.all().sort((a, b) => {
|
||||||
|
// Enabled models first
|
||||||
|
if (a.enabled && !b.enabled) return -1;
|
||||||
|
if (!a.enabled && b.enabled) return 1;
|
||||||
|
|
||||||
|
// Then by provider order
|
||||||
|
const providerOrder = Object.values(Provider);
|
||||||
|
const aProviderIndex = providerOrder.indexOf(a.provider as Provider);
|
||||||
|
const bProviderIndex = providerOrder.indexOf(b.provider as Provider);
|
||||||
|
if (aProviderIndex !== bProviderIndex) {
|
||||||
|
return aProviderIndex - bProviderIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally by name
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export const PROVIDER_META: Record<Provider, ProviderMeta> = {
|
||||||
},
|
},
|
||||||
[Provider.Gemini]: {
|
[Provider.Gemini]: {
|
||||||
title: 'Google Gemini',
|
title: 'Google Gemini',
|
||||||
link: 'https://cloud.google.com/vertex-ai',
|
link: 'https://ai.google.dev/docs',
|
||||||
description: 'Gemini models from Google',
|
description: 'Gemini models from Google',
|
||||||
apiKeyName: 'Google AI API Key',
|
apiKeyName: 'Google AI API Key',
|
||||||
placeholder: 'AIza...',
|
placeholder: 'AIza...',
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,29 @@
|
||||||
import type { OpenRouterModel } from '$lib/backend/models/open-router';
|
import type { ModelInfo } from '@keplersystems/kepler-ai-sdk';
|
||||||
|
|
||||||
export function supportsImages(model: OpenRouterModel): boolean {
|
export function supportsImages(model: ModelInfo): boolean {
|
||||||
return model.architecture.input_modalities.includes('image');
|
return model.capabilities.vision;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function supportsReasoning(model: OpenRouterModel): boolean {
|
export function supportsReasoning(model: ModelInfo): boolean {
|
||||||
return model.supported_parameters.includes('reasoning');
|
return model.capabilities.reasoning;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getImageSupportedModels(models: OpenRouterModel[]): OpenRouterModel[] {
|
export function supportsStreaming(model: ModelInfo): boolean {
|
||||||
|
return model.capabilities.streaming;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function supportsToolCalls(model: ModelInfo): boolean {
|
||||||
|
return model.capabilities.toolCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImageSupportedModels(models: ModelInfo[]): ModelInfo[] {
|
||||||
return models.filter(supportsImages);
|
return models.filter(supportsImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getReasoningModels(models: ModelInfo[]): ModelInfo[] {
|
||||||
|
return models.filter(supportsReasoning);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStreamingModels(models: ModelInfo[]): ModelInfo[] {
|
||||||
|
return models.filter(supportsStreaming);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,29 @@ export type ProviderApiKeyData = {
|
||||||
|
|
||||||
export const ProviderUtils = {
|
export const ProviderUtils = {
|
||||||
/**
|
/**
|
||||||
* Validate an API key for a specific provider
|
* Validate an API key for a specific provider via server endpoint
|
||||||
*/
|
*/
|
||||||
validateApiKey: async (provider: Provider, key: string): Promise<Result<ProviderApiKeyData, string>> => {
|
validateApiKey: async (provider: Provider, key: string): Promise<Result<ProviderApiKeyData, string>> => {
|
||||||
switch (provider) {
|
return await ResultAsync.fromPromise(
|
||||||
case Provider.OpenRouter:
|
(async () => {
|
||||||
return await validateOpenRouterKey(key);
|
const response = await fetch('/api/validate-key', {
|
||||||
case Provider.OpenAI:
|
method: 'POST',
|
||||||
return await validateOpenAIKey(key);
|
headers: {
|
||||||
case Provider.Anthropic:
|
'Content-Type': 'application/json',
|
||||||
return await validateAnthropicKey(key);
|
},
|
||||||
case Provider.Gemini:
|
body: JSON.stringify({ provider, key }),
|
||||||
return await validateGeminiKey(key);
|
});
|
||||||
case Provider.Mistral:
|
|
||||||
return await validateMistralKey(key);
|
if (!response.ok) {
|
||||||
case Provider.Cohere:
|
const error = await response.json();
|
||||||
return await validateCohereKey(key);
|
throw new Error(error.error || `HTTP ${response.status}`);
|
||||||
default:
|
|
||||||
return Result.err(`Validation not implemented for provider: ${provider}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
return result.data;
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate API key: ${e}`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,158 +59,3 @@ export const ProviderUtils = {
|
||||||
return Object.values(Provider);
|
return Object.values(Provider);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provider-specific validation functions
|
|
||||||
async function validateOpenRouterKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch('https://openrouter.ai/api/v1/auth/key', {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await res.json();
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
throw new Error('No key information returned');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: data.label || 'OpenRouter API Key',
|
|
||||||
usage: data.usage,
|
|
||||||
is_free_tier: data.is_free_tier,
|
|
||||||
is_provisioning_key: data.is_provisioning_key,
|
|
||||||
limit: data.limit,
|
|
||||||
limit_remaining: data.limit_remaining,
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate OpenRouter API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateOpenAIKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch('https://api.openai.com/v1/models', {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: 'OpenAI API Key',
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate OpenAI API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateAnthropicKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
// Anthropic doesn't have a simple key validation endpoint
|
|
||||||
// We'll try a minimal request to test the key
|
|
||||||
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'x-api-key': key,
|
|
||||||
'anthropic-version': '2023-06-01',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'claude-3-haiku-20240307',
|
|
||||||
max_tokens: 1,
|
|
||||||
messages: [{ role: 'user', content: 'test' }],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Even a 400 error means the key is valid (just bad request format)
|
|
||||||
if (res.status === 401 || res.status === 403) {
|
|
||||||
throw new Error('Invalid API key');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: 'Anthropic API Key',
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate Anthropic API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateGeminiKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch(`https://generativelanguage.googleapis.com/v1/models?key=${key}`);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: 'Google Gemini API Key',
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate Gemini API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateMistralKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch('https://api.mistral.ai/v1/models', {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: 'Mistral API Key',
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate Mistral API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateCohereKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
|
||||||
return await ResultAsync.fromPromise(
|
|
||||||
(async () => {
|
|
||||||
const res = await fetch('https://api.cohere.ai/v1/models', {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: 'Cohere API Key',
|
|
||||||
valid: true,
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
(e) => `Failed to validate Cohere API key: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
import { getOpenRouterModels, type OpenRouterModel } from '$lib/backend/models/open-router';
|
import { loadUserModels, loadGuestModels } from '$lib/services/model-loader.server';
|
||||||
import { Provider } from '$lib/types';
|
|
||||||
import type { LayoutServerLoad } from './$types';
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
const [session, openRouterModels] = await Promise.all([locals.auth(), getOpenRouterModels()]);
|
const session = await locals.auth();
|
||||||
|
|
||||||
|
// Load models based on user's API keys
|
||||||
|
const models = session?.session?.token
|
||||||
|
? await loadUserModels(session.session.token)
|
||||||
|
: loadGuestModels();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
session,
|
||||||
models: {
|
models,
|
||||||
[Provider.OpenRouter]: openRouterModels.unwrapOr([] as OpenRouterModel[]),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Makes caching easier, and tbf, we don't need SSR anyways here
|
// Enable SSR for better performance
|
||||||
export const ssr = true;
|
export const ssr = true;
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
{#if apiKeyInfoResource.loading}
|
{#if apiKeyInfoResource.loading}
|
||||||
<div class="bg-input h-6 w-[200px] animate-pulse rounded-md"></div>
|
<div class="bg-input h-6 w-[200px] animate-pulse rounded-md"></div>
|
||||||
{:else if apiKeyInfoResource.current}
|
{:else if apiKeyInfoResource.current}
|
||||||
{#if apiKeyInfoResource.current.usage !== undefined && apiKeyInfoResource.current.limit_remaining !== undefined}
|
{#if apiKeyInfoResource.current.usage !== undefined && apiKeyInfoResource.current.limit_remaining !== undefined && apiKeyInfoResource.current.usage !== null && apiKeyInfoResource.current.limit_remaining !== null}
|
||||||
<span class="text-muted-foreground flex h-6 place-items-center text-xs">
|
<span class="text-muted-foreground flex h-6 place-items-center text-xs">
|
||||||
${apiKeyInfoResource.current.usage.toFixed(3)} used ・ ${apiKeyInfoResource.current.limit_remaining.toFixed(
|
${apiKeyInfoResource.current.usage.toFixed(3)} used ・ ${apiKeyInfoResource.current.limit_remaining.toFixed(
|
||||||
3
|
3
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,37 @@
|
||||||
import { Search } from '$lib/components/ui/search';
|
import { Search } from '$lib/components/ui/search';
|
||||||
import { models } from '$lib/state/models.svelte';
|
import { models } from '$lib/state/models.svelte';
|
||||||
import { session } from '$lib/state/session.svelte';
|
import { session } from '$lib/state/session.svelte';
|
||||||
import { Provider } from '$lib/types.js';
|
import { page } from '$app/state';
|
||||||
|
import { Provider, PROVIDER_META } from '$lib/types.js';
|
||||||
import { fuzzysearch } from '$lib/utils/fuzzy-search';
|
import { fuzzysearch } from '$lib/utils/fuzzy-search';
|
||||||
import { cn } from '$lib/utils/utils';
|
import { cn } from '$lib/utils/utils';
|
||||||
import { Toggle } from 'melt/builders';
|
import { Toggle } from 'melt/builders';
|
||||||
import PlusIcon from '~icons/lucide/plus';
|
import PlusIcon from '~icons/lucide/plus';
|
||||||
import XIcon from '~icons/lucide/x';
|
import XIcon from '~icons/lucide/x';
|
||||||
import ModelCard from './model-card.svelte';
|
import ModelCard from './model-card.svelte';
|
||||||
import { supportsImages, supportsReasoning } from '$lib/utils/model-capabilities';
|
import type { ModelInfo } from '@keplersystems/kepler-ai-sdk';
|
||||||
|
|
||||||
const openRouterKeyQuery = useCachedQuery(api.user_keys.get, {
|
// Get all user's API keys to determine which providers are available
|
||||||
provider: Provider.OpenRouter,
|
const userKeysQuery = useCachedQuery(api.user_keys.all, {
|
||||||
session_token: session.current?.session.token ?? '',
|
session_token: session.current?.session.token ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasOpenRouterKey = $derived(
|
|
||||||
openRouterKeyQuery.data !== undefined && openRouterKeyQuery.data !== ''
|
// Show providers that have API keys, regardless of whether models are loaded yet
|
||||||
);
|
const availableProviders = $derived.by(() => {
|
||||||
|
if (!userKeysQuery.data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(userKeysQuery.data)
|
||||||
|
.filter(([_, key]) => key) // Only providers with API keys
|
||||||
|
.map(([provider, _]) => provider as Provider);
|
||||||
|
});
|
||||||
|
|
||||||
let search = $state('');
|
let search = $state('');
|
||||||
|
let selectedProvider = $state<Provider | 'all'>('all');
|
||||||
|
|
||||||
const openRouterToggle = new Toggle({
|
// Filter toggles
|
||||||
value: true,
|
|
||||||
// TODO: enable this if and when when we use multiple providers
|
|
||||||
disabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const freeModelsToggle = new Toggle({
|
|
||||||
value: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reasoningModelsToggle = new Toggle({
|
const reasoningModelsToggle = new Toggle({
|
||||||
value: false,
|
value: false,
|
||||||
});
|
});
|
||||||
|
|
@ -43,43 +44,47 @@
|
||||||
value: false,
|
value: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let initiallyEnabled = $state<string[]>([]);
|
const streamingModelsToggle = new Toggle({
|
||||||
$effect(() => {
|
value: false,
|
||||||
if (Object.keys(models.enabled).length && initiallyEnabled.length === 0) {
|
|
||||||
initiallyEnabled = models
|
|
||||||
.from(Provider.OpenRouter)
|
|
||||||
.filter((m) => m.enabled)
|
|
||||||
.map((m) => m.id);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const openRouterModels = $derived(
|
// Get models based on current filters
|
||||||
fuzzysearch({
|
const filteredModels = $derived.by(() => {
|
||||||
haystack: models.from(Provider.OpenRouter).filter((m) => {
|
let modelList = selectedProvider === 'all'
|
||||||
if (freeModelsToggle.value) {
|
? models.all()
|
||||||
if (m.pricing.prompt !== '0') return false;
|
: models.from(selectedProvider);
|
||||||
}
|
|
||||||
|
|
||||||
|
// Apply capability filters
|
||||||
if (reasoningModelsToggle.value) {
|
if (reasoningModelsToggle.value) {
|
||||||
if (!supportsReasoning(m)) return false;
|
modelList = modelList.filter(m => m.capabilities.reasoning);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageModelsToggle.value) {
|
if (imageModelsToggle.value) {
|
||||||
if (!supportsImages(m)) return false;
|
modelList = modelList.filter(m => m.capabilities.vision);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (streamingModelsToggle.value) {
|
||||||
}),
|
modelList = modelList.filter(m => m.capabilities.streaming);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply text search
|
||||||
|
if (search) {
|
||||||
|
modelList = fuzzysearch({
|
||||||
|
haystack: modelList,
|
||||||
needle: search,
|
needle: search,
|
||||||
property: 'name',
|
property: 'name',
|
||||||
}).sort((a, b) => {
|
});
|
||||||
const aEnabled = initiallyEnabled.includes(a.id);
|
}
|
||||||
const bEnabled = initiallyEnabled.includes(b.id);
|
|
||||||
if (aEnabled && !bEnabled) return -1;
|
// Sort: enabled first, then by name
|
||||||
if (!aEnabled && bEnabled) return 1;
|
return modelList.sort((a, b) => {
|
||||||
return 0;
|
if (a.enabled && !b.enabled) return -1;
|
||||||
})
|
if (!a.enabled && b.enabled) return 1;
|
||||||
);
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasAnyApiKeys = $derived(availableProviders.length > 0);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -91,76 +96,125 @@
|
||||||
Choose which models appear in your model selector. This won't affect existing conversations.
|
Choose which models appear in your model selector. This won't affect existing conversations.
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-col gap-2">
|
{#if !hasAnyApiKeys}
|
||||||
<Search bind:value={search} placeholder="Search models" />
|
<div class="mt-8 rounded-lg border border-yellow-200 bg-yellow-50 p-4">
|
||||||
<div class="flex place-items-center gap-2">
|
<h3 class="font-semibold text-yellow-800">No API Keys Configured</h3>
|
||||||
<button
|
<p class="text-sm text-yellow-700 mt-1">
|
||||||
{...openRouterToggle.trigger}
|
You need to add API keys for at least one provider to see and manage models.
|
||||||
aria-label="OpenRouter"
|
<a href="/account/api-keys" class="underline hover:text-yellow-900">Go to API Keys Settings</a>
|
||||||
class="group text-primary-foreground bg-primary aria-[pressed=false]:border-border border-primary aria-[pressed=false]:bg-background flex place-items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50"
|
</p>
|
||||||
>
|
|
||||||
OpenRouter
|
|
||||||
<XIcon class="inline size-3 group-aria-[pressed=false]:hidden" />
|
|
||||||
<PlusIcon class="inline size-3 group-aria-[pressed=true]:hidden" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
{...freeModelsToggle.trigger}
|
|
||||||
aria-label="Free Models"
|
|
||||||
class="group text-primary-foreground bg-primary aria-[pressed=false]:border-border border-primary aria-[pressed=false]:bg-background flex place-items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
Free
|
|
||||||
<XIcon class="inline size-3 group-aria-[pressed=false]:hidden" />
|
|
||||||
<PlusIcon class="inline size-3 group-aria-[pressed=true]:hidden" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
{...reasoningModelsToggle.trigger}
|
|
||||||
aria-label="Reasoning Models"
|
|
||||||
class="group text-primary-foreground bg-primary aria-[pressed=false]:border-border border-primary aria-[pressed=false]:bg-background flex place-items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
Reasoning
|
|
||||||
<XIcon class="inline size-3 group-aria-[pressed=false]:hidden" />
|
|
||||||
<PlusIcon class="inline size-3 group-aria-[pressed=true]:hidden" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
{...imageModelsToggle.trigger}
|
|
||||||
aria-label="Image Models"
|
|
||||||
class="group text-primary-foreground bg-primary aria-[pressed=false]:border-border border-primary aria-[pressed=false]:bg-background flex place-items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
Images
|
|
||||||
<XIcon class="inline size-3 group-aria-[pressed=false]:hidden" />
|
|
||||||
<PlusIcon class="inline size-3 group-aria-[pressed=true]:hidden" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="mt-6 space-y-4">
|
||||||
|
<!-- Search -->
|
||||||
|
<Search bind:value={search} placeholder="Search models" />
|
||||||
|
|
||||||
|
<!-- Provider and filter tabs -->
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<!-- Provider selector -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
onclick={() => selectedProvider = 'all'}
|
||||||
|
class={cn(
|
||||||
|
"px-3 py-1 rounded-full text-sm transition-all",
|
||||||
|
selectedProvider === 'all'
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
All Providers ({models.all().length})
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#each availableProviders as provider}
|
||||||
|
{@const providerMeta = PROVIDER_META[provider]}
|
||||||
|
{@const providerModels = models.from(provider)}
|
||||||
|
{@const hasModels = models.hasProvider(provider)}
|
||||||
|
{#if providerMeta}
|
||||||
|
<button
|
||||||
|
onclick={() => selectedProvider = provider}
|
||||||
|
class={cn(
|
||||||
|
"px-3 py-1 rounded-full text-sm transition-all",
|
||||||
|
selectedProvider === provider
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{providerMeta.title} ({hasModels ? providerModels.length : 'loading...'})
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if openRouterModels.length > 0}
|
<!-- Capability filters -->
|
||||||
<div class="mt-4 flex flex-col gap-4">
|
<div class="h-4 w-px bg-border"></div>
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-bold">OpenRouter</h3>
|
<button
|
||||||
<p class="text-muted-foreground text-sm">Easy access to over 400 models.</p>
|
{...reasoningModelsToggle.trigger}
|
||||||
</div>
|
class={cn(
|
||||||
<div class="relative">
|
"px-3 py-1 rounded-full text-sm transition-all",
|
||||||
<div
|
reasoningModelsToggle.value
|
||||||
class={cn('flex flex-col gap-4 overflow-hidden', {
|
? "bg-blue-500 text-white"
|
||||||
'pointer-events-none max-h-96 mask-b-from-0% mask-b-to-80%': !hasOpenRouterKey,
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
})}
|
)}
|
||||||
>
|
>
|
||||||
{#each openRouterModels as model (model.id)}
|
Reasoning
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{...imageModelsToggle.trigger}
|
||||||
|
class={cn(
|
||||||
|
"px-3 py-1 rounded-full text-sm transition-all",
|
||||||
|
imageModelsToggle.value
|
||||||
|
? "bg-green-500 text-white"
|
||||||
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Vision
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{...streamingModelsToggle.trigger}
|
||||||
|
class={cn(
|
||||||
|
"px-3 py-1 rounded-full text-sm transition-all",
|
||||||
|
streamingModelsToggle.value
|
||||||
|
? "bg-purple-500 text-white"
|
||||||
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Streaming
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Models grid -->
|
||||||
|
{#if filteredModels.length === 0}
|
||||||
|
<div class="text-center py-12">
|
||||||
|
{#if selectedProvider !== 'all' && !models.hasProvider(selectedProvider)}
|
||||||
|
<h3 class="text-lg font-semibold text-muted-foreground">Loading models...</h3>
|
||||||
|
<p class="text-sm text-muted-foreground mt-2">
|
||||||
|
Models are being loaded from {PROVIDER_META[selectedProvider]?.title || selectedProvider}. Please refresh the page in a moment.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<h3 class="text-lg font-semibold text-muted-foreground">No models found</h3>
|
||||||
|
<p class="text-sm text-muted-foreground mt-2">
|
||||||
|
{#if search}
|
||||||
|
Try adjusting your search or filters.
|
||||||
|
{:else}
|
||||||
|
No models match your current filters.
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="grid gap-4">
|
||||||
|
{#each filteredModels as model (model.id)}
|
||||||
<ModelCard
|
<ModelCard
|
||||||
provider={Provider.OpenRouter}
|
provider={model.provider as Provider}
|
||||||
{model}
|
{model}
|
||||||
enabled={model.enabled}
|
enabled={model.enabled}
|
||||||
disabled={!hasOpenRouterKey}
|
disabled={false}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if !hasOpenRouterKey}
|
|
||||||
<div
|
|
||||||
class="absolute bottom-10 left-0 z-10 flex w-full place-items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<Button href="/account/api-keys#openrouter" class="w-fit">Add API Key</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Provider } from '$lib/types';
|
import type { Provider } from '$lib/types';
|
||||||
|
import { PROVIDER_META } from '$lib/types';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { Switch } from '$lib/components/ui/switch';
|
import { Switch } from '$lib/components/ui/switch';
|
||||||
import { useConvexClient } from 'convex-svelte';
|
import { useConvexClient } from 'convex-svelte';
|
||||||
|
|
@ -7,38 +8,40 @@
|
||||||
import { session } from '$lib/state/session.svelte.js';
|
import { session } from '$lib/state/session.svelte.js';
|
||||||
import { ResultAsync } from 'neverthrow';
|
import { ResultAsync } from 'neverthrow';
|
||||||
import { getFirstSentence } from '$lib/utils/strings';
|
import { getFirstSentence } from '$lib/utils/strings';
|
||||||
import { supportsImages, supportsReasoning } from '$lib/utils/model-capabilities';
|
import type { ModelInfo } from '@keplersystems/kepler-ai-sdk';
|
||||||
import type { OpenRouterModel } from '$lib/backend/models/open-router';
|
|
||||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||||
import EyeIcon from '~icons/lucide/eye';
|
import EyeIcon from '~icons/lucide/eye';
|
||||||
import BrainIcon from '~icons/lucide/brain';
|
import BrainIcon from '~icons/lucide/brain';
|
||||||
|
import ZapIcon from '~icons/lucide/zap';
|
||||||
type Model = {
|
import CpuIcon from '~icons/lucide/cpu';
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
provider: Provider;
|
||||||
|
model: ModelInfo;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
} & {
|
|
||||||
provider: typeof Provider.OpenRouter;
|
|
||||||
model: OpenRouterModel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let { provider, model, enabled = false, disabled = false }: Props = $props();
|
let { provider, model, enabled = false, disabled = false }: Props = $props();
|
||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
|
const providerMeta = $derived(PROVIDER_META[provider]);
|
||||||
|
|
||||||
const [shortDescription, fullDescription] = $derived(getFirstSentence(model.description));
|
const [shortDescription, fullDescription] = $derived(
|
||||||
|
model.description ? getFirstSentence(model.description) : [null, model.name]
|
||||||
|
);
|
||||||
|
|
||||||
let showMore = $state(false);
|
let showMore = $state(false);
|
||||||
|
|
||||||
async function toggleEnabled(v: boolean) {
|
async function toggleEnabled(v: boolean) {
|
||||||
|
console.log('toggleEnabled called:', { provider, model_id: model.id, enabled: v });
|
||||||
enabled = v; // Optimistic!
|
enabled = v; // Optimistic!
|
||||||
if (!session.current?.user.id) return;
|
if (!session.current?.user.id) {
|
||||||
|
console.log('No user session, returning early');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Calling Convex mutation...');
|
||||||
const res = await ResultAsync.fromPromise(
|
const res = await ResultAsync.fromPromise(
|
||||||
client.mutation(api.user_enabled_models.set, {
|
client.mutation(api.user_enabled_models.set, {
|
||||||
provider,
|
provider,
|
||||||
|
|
@ -49,36 +52,75 @@
|
||||||
(e) => e
|
(e) => e
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.isErr()) enabled = !v; // Should have been a realist :(
|
if (res.isErr()) {
|
||||||
|
console.error('Mutation failed:', res.error);
|
||||||
|
enabled = !v; // Revert on error
|
||||||
|
} else {
|
||||||
|
console.log('Mutation succeeded');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format pricing information
|
||||||
|
const pricingInfo = $derived.by(() => {
|
||||||
|
if (!model.pricing) return null;
|
||||||
|
|
||||||
|
const { inputTokens, outputTokens } = model.pricing;
|
||||||
|
const inputPrice = inputTokens < 1 ? `$${(inputTokens * 1000).toFixed(3)}/1K` : `$${inputTokens.toFixed(3)}/1M`;
|
||||||
|
const outputPrice = outputTokens < 1 ? `$${(outputTokens * 1000).toFixed(3)}/1K` : `$${outputTokens.toFixed(3)}/1M`;
|
||||||
|
|
||||||
|
return `${inputPrice} → ${outputPrice}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format context window
|
||||||
|
const contextInfo = $derived.by(() => {
|
||||||
|
const contextWindow = model.contextWindow;
|
||||||
|
if (contextWindow >= 1000000) {
|
||||||
|
return `${(contextWindow / 1000000).toFixed(1)}M context`;
|
||||||
|
} else if (contextWindow >= 1000) {
|
||||||
|
return `${(contextWindow / 1000).toFixed(0)}K context`;
|
||||||
|
} else {
|
||||||
|
return `${contextWindow} tokens`;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card.Root>
|
<Card.Root>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<div class="flex place-items-center gap-2">
|
<div class="flex place-items-center gap-2">
|
||||||
<Card.Title>{model.name}</Card.Title>
|
<Card.Title>{model.name}</Card.Title>
|
||||||
<span class="text-muted-foreground hidden text-xs xl:block">{model.id}</span>
|
<span class="px-2 py-0.5 text-xs rounded-full bg-secondary text-secondary-foreground">
|
||||||
|
{providerMeta.title}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Switch bind:value={() => enabled, toggleEnabled} {disabled} />
|
<span class="text-muted-foreground text-xs">{model.id}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<Switch bind:value={enabled} onValueChange={toggleEnabled} {disabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if model.description}
|
||||||
<Card.Description>
|
<Card.Description>
|
||||||
{showMore ? fullDescription : (shortDescription ?? fullDescription)}
|
{showMore ? fullDescription : (shortDescription ?? fullDescription)}
|
||||||
</Card.Description>
|
</Card.Description>
|
||||||
{#if shortDescription !== null}
|
{#if shortDescription !== null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="text-muted-foreground w-fit text-start text-xs"
|
class="text-muted-foreground w-fit text-start text-xs hover:text-foreground transition-colors"
|
||||||
onclick={() => (showMore = !showMore)}
|
onclick={() => (showMore = !showMore)}
|
||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
{showMore ? 'Show less' : 'Show more'}
|
{showMore ? 'Show less' : 'Show more'}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<!-- Capabilities badges -->
|
||||||
<div class="flex place-items-center gap-1">
|
<div class="flex place-items-center gap-1">
|
||||||
{#if model && provider === 'openrouter' && supportsImages(model)}
|
{#if model.capabilities.vision}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -88,11 +130,11 @@
|
||||||
<EyeIcon class="size-3" />
|
<EyeIcon class="size-3" />
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
Supports image analysis
|
Supports vision/image analysis
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if model && provider === 'openrouter' && supportsReasoning(model)}
|
{#if model.capabilities.reasoning}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -105,6 +147,43 @@
|
||||||
Supports reasoning
|
Supports reasoning
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if model.capabilities.streaming}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-blue-500 bg-blue-500/50 p-1 text-blue-400"
|
||||||
|
>
|
||||||
|
<ZapIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports streaming responses
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if model.capabilities.toolCalls}
|
||||||
|
<Tooltip>
|
||||||
|
{#snippet trigger(tooltip)}
|
||||||
|
<div
|
||||||
|
{...tooltip.trigger}
|
||||||
|
class="rounded-md border-orange-500 bg-orange-500/50 p-1 text-orange-400"
|
||||||
|
>
|
||||||
|
<CpuIcon class="size-3" />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
Supports tool/function calling
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Model info -->
|
||||||
|
<div class="flex items-center gap-3 text-xs text-muted-foreground">
|
||||||
|
{#if pricingInfo}
|
||||||
|
<span>{pricingInfo}</span>
|
||||||
|
{/if}
|
||||||
|
<span>{contextInfo}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
@ -44,7 +44,7 @@ async function getUserApiKeys(sessionToken: string): Promise<UserApiKeys | null>
|
||||||
return {
|
return {
|
||||||
openai: keys.openai,
|
openai: keys.openai,
|
||||||
anthropic: keys.anthropic,
|
anthropic: keys.anthropic,
|
||||||
gemini: keys.gemini,
|
gemini: keys.google,
|
||||||
mistral: keys.mistral,
|
mistral: keys.mistral,
|
||||||
cohere: keys.cohere,
|
cohere: keys.cohere,
|
||||||
openrouter: keys.openrouter,
|
openrouter: keys.openrouter,
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ async function getUserApiKeys(sessionToken: string): Promise<Result<UserApiKeys,
|
||||||
return ok({
|
return ok({
|
||||||
openai: keys.openai,
|
openai: keys.openai,
|
||||||
anthropic: keys.anthropic,
|
anthropic: keys.anthropic,
|
||||||
gemini: keys.gemini,
|
google: keys.gemini,
|
||||||
mistral: keys.mistral,
|
mistral: keys.mistral,
|
||||||
cohere: keys.cohere,
|
cohere: keys.cohere,
|
||||||
openrouter: keys.openrouter,
|
openrouter: keys.openrouter,
|
||||||
|
|
@ -127,8 +127,8 @@ async function generateConversationTitle({
|
||||||
// Try to find a fast, cheap model for title generation
|
// Try to find a fast, cheap model for title generation
|
||||||
const availableModels = await modelManager.listAvailableModels();
|
const availableModels = await modelManager.listAvailableModels();
|
||||||
const titleModel =
|
const titleModel =
|
||||||
availableModels.find((model) => model.id.includes('kimi-k2')) ||
|
|
||||||
availableModels.find((model) => model.id.includes('gemini-2.5-flash-lite')) ||
|
availableModels.find((model) => model.id.includes('gemini-2.5-flash-lite')) ||
|
||||||
|
availableModels.find((model) => model.id.includes('kimi-k2')) ||
|
||||||
availableModels.find((model) => model.id.includes('gpt-5-mini')) ||
|
availableModels.find((model) => model.id.includes('gpt-5-mini')) ||
|
||||||
availableModels[0];
|
availableModels[0];
|
||||||
|
|
||||||
|
|
@ -396,21 +396,19 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate completion with streaming
|
// Generate streaming completion
|
||||||
const streamResult = await ResultAsync.fromPromise(
|
let stream: AsyncIterable<any>;
|
||||||
provider.generateCompletion({
|
try {
|
||||||
|
stream = provider.streamCompletion({
|
||||||
model: finalModelId,
|
model: finalModelId,
|
||||||
messages: messagesToSend,
|
messages: messagesToSend,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
stream: true,
|
|
||||||
...(reasoningEffort && { reasoning_effort: reasoningEffort }),
|
...(reasoningEffort && { reasoning_effort: reasoningEffort }),
|
||||||
}),
|
});
|
||||||
(e) => `API call failed: ${e}`
|
log('Background: Stream created successfully', startTime);
|
||||||
);
|
} catch (error) {
|
||||||
|
|
||||||
if (streamResult.isErr()) {
|
|
||||||
handleGenerationError({
|
handleGenerationError({
|
||||||
error: `Failed to create stream: ${streamResult.error}`,
|
error: `Failed to create stream: API call failed: ${error}`,
|
||||||
conversationId,
|
conversationId,
|
||||||
messageId: mid,
|
messageId: mid,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
|
|
@ -419,9 +417,6 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = streamResult.value;
|
|
||||||
log('Background: Stream created successfully', startTime);
|
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
let reasoning = '';
|
let reasoning = '';
|
||||||
let chunkCount = 0;
|
let chunkCount = 0;
|
||||||
|
|
@ -430,7 +425,6 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle streaming response
|
// Handle streaming response
|
||||||
if (stream && typeof stream[Symbol.asyncIterator] === 'function') {
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
if (abortSignal?.aborted) {
|
if (abortSignal?.aborted) {
|
||||||
log('AI response generation aborted during streaming', startTime);
|
log('AI response generation aborted during streaming', startTime);
|
||||||
|
|
@ -439,9 +433,9 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
|
|
||||||
chunkCount++;
|
chunkCount++;
|
||||||
|
|
||||||
// Extract content from chunk based on the stream format
|
// Extract content from chunk based on the kepler-ai-sdk format
|
||||||
if (chunk && typeof chunk === 'object') {
|
if (chunk && typeof chunk === 'object') {
|
||||||
const chunkContent = chunk.content || chunk.text || '';
|
const chunkContent = chunk.delta || chunk.content || chunk.text || '';
|
||||||
const chunkReasoning = chunk.reasoning || '';
|
const chunkReasoning = chunk.reasoning || '';
|
||||||
const chunkAnnotations = chunk.annotations || [];
|
const chunkAnnotations = chunk.annotations || [];
|
||||||
|
|
||||||
|
|
@ -449,7 +443,7 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
content += chunkContent;
|
content += chunkContent;
|
||||||
annotations.push(...chunkAnnotations);
|
annotations.push(...chunkAnnotations);
|
||||||
|
|
||||||
if (!content && !reasoning) continue;
|
if (!chunkContent && !chunkReasoning) continue;
|
||||||
|
|
||||||
generationId = chunk.id || generationId;
|
generationId = chunk.id || generationId;
|
||||||
|
|
||||||
|
|
@ -474,34 +468,6 @@ ${attachedRules.map((r) => `- ${r.name}: ${r.rule}`).join('\n')}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Handle non-streaming response
|
|
||||||
const response = stream as any;
|
|
||||||
content = response.content || response.text || '';
|
|
||||||
reasoning = response.reasoning || '';
|
|
||||||
generationId = response.id;
|
|
||||||
|
|
||||||
if (response.annotations) {
|
|
||||||
annotations.push(...response.annotations);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateResult = await ResultAsync.fromPromise(
|
|
||||||
client.mutation(api.messages.updateContent, {
|
|
||||||
message_id: mid,
|
|
||||||
content,
|
|
||||||
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
||||||
session_token: sessionToken,
|
|
||||||
generation_id: generationId,
|
|
||||||
annotations,
|
|
||||||
reasoning_effort: reasoningEffort,
|
|
||||||
}),
|
|
||||||
(e) => `Failed to update message content: ${e}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.isErr()) {
|
|
||||||
log(`Background message update failed: ${updateResult.error}`, startTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
log(
|
||||||
`Background stream processing completed. Processed ${chunkCount} chunks, final content length: ${content.length}`,
|
`Background stream processing completed. Processed ${chunkCount} chunks, final content length: ${content.length}`,
|
||||||
|
|
|
||||||
218
src/routes/api/validate-key/+server.ts
Normal file
218
src/routes/api/validate-key/+server.ts
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { Provider } from '$lib/types';
|
||||||
|
import { Result, ResultAsync } from 'neverthrow';
|
||||||
|
|
||||||
|
export type ProviderApiKeyData = {
|
||||||
|
label: string;
|
||||||
|
usage?: number;
|
||||||
|
is_free_tier?: boolean;
|
||||||
|
is_provisioning_key?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
limit_remaining?: number;
|
||||||
|
valid: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||||
|
const session = await locals.auth();
|
||||||
|
if (!session) {
|
||||||
|
return json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { provider, key } = await request.json();
|
||||||
|
|
||||||
|
if (!provider || !key) {
|
||||||
|
return json({ error: 'Missing provider or key' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.values(Provider).includes(provider)) {
|
||||||
|
return json({ error: 'Invalid provider' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await validateApiKey(provider, key);
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
return json({ error: result.error }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({ data: result.value });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API key validation error:', error);
|
||||||
|
return json({ error: 'Internal server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function validateApiKey(provider: Provider, key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
switch (provider) {
|
||||||
|
case Provider.OpenRouter:
|
||||||
|
return await validateOpenRouterKey(key);
|
||||||
|
case Provider.OpenAI:
|
||||||
|
return await validateOpenAIKey(key);
|
||||||
|
case Provider.Anthropic:
|
||||||
|
return await validateAnthropicKey(key);
|
||||||
|
case Provider.Gemini:
|
||||||
|
return await validateGeminiKey(key);
|
||||||
|
case Provider.Mistral:
|
||||||
|
return await validateMistralKey(key);
|
||||||
|
case Provider.Cohere:
|
||||||
|
return await validateCohereKey(key);
|
||||||
|
default:
|
||||||
|
return Result.err(`Validation not implemented for provider: ${provider}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider-specific validation functions
|
||||||
|
async function validateOpenRouterKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
const res = await fetch('https://openrouter.ai/api/v1/auth/key', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await res.json();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new Error('No key information returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: data.label || 'OpenRouter API Key',
|
||||||
|
usage: data.usage,
|
||||||
|
is_free_tier: data.is_free_tier,
|
||||||
|
is_provisioning_key: data.is_provisioning_key,
|
||||||
|
limit: data.limit,
|
||||||
|
limit_remaining: data.limit_remaining,
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate OpenRouter API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateOpenAIKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
const res = await fetch('https://api.openai.com/v1/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: 'OpenAI API Key',
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate OpenAI API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateAnthropicKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
// Anthropic doesn't have a simple key validation endpoint
|
||||||
|
// We'll try a minimal request to test the key
|
||||||
|
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'x-api-key': key,
|
||||||
|
'anthropic-version': '2023-06-01',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: 'claude-3-haiku-20240307',
|
||||||
|
max_tokens: 1,
|
||||||
|
messages: [{ role: 'user', content: 'test' }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Even a 400 error means the key is valid (just bad request format)
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
throw new Error('Invalid API key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: 'Anthropic API Key',
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate Anthropic API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateGeminiKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
const res = await fetch(`https://generativelanguage.googleapis.com/v1/models?key=${key}`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: 'Google Gemini API Key',
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate Gemini API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateMistralKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
const res = await fetch('https://api.mistral.ai/v1/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: 'Mistral API Key',
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate Mistral API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateCohereKey(key: string): Promise<Result<ProviderApiKeyData, string>> {
|
||||||
|
return await ResultAsync.fromPromise(
|
||||||
|
(async () => {
|
||||||
|
const res = await fetch('https://api.cohere.ai/v1/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${key}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: 'Cohere API Key',
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
(e) => `Failed to validate Cohere API key: ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -213,15 +213,15 @@
|
||||||
|
|
||||||
const currentModelSupportsImages = $derived.by(() => {
|
const currentModelSupportsImages = $derived.by(() => {
|
||||||
if (!settings.modelId) return false;
|
if (!settings.modelId) return false;
|
||||||
const openRouterModels = models.from(Provider.OpenRouter);
|
const allModels = models.all();
|
||||||
const currentModel = openRouterModels.find((m) => m.id === settings.modelId);
|
const currentModel = allModels.find((m) => m.id === settings.modelId);
|
||||||
return currentModel ? supportsImages(currentModel) : false;
|
return currentModel ? supportsImages(currentModel) : false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentModelSupportsReasoning = $derived.by(() => {
|
const currentModelSupportsReasoning = $derived.by(() => {
|
||||||
if (!settings.modelId) return false;
|
if (!settings.modelId) return false;
|
||||||
const openRouterModels = models.from(Provider.OpenRouter);
|
const allModels = models.all();
|
||||||
const currentModel = openRouterModels.find((m) => m.id === settings.modelId);
|
const currentModel = allModels.find((m) => m.id === settings.modelId);
|
||||||
if (!currentModel) return false;
|
if (!currentModel) return false;
|
||||||
return supportsReasoning(currentModel);
|
return supportsReasoning(currentModel);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,7 @@
|
||||||
|
|
||||||
let selectedCategory = $state<string | null>(null);
|
let selectedCategory = $state<string | null>(null);
|
||||||
|
|
||||||
const openRouterKeyQuery = useCachedQuery(api.user_keys.get, {
|
const userKeysQuery = useCachedQuery(api.user_keys.all, {
|
||||||
provider: Provider.OpenRouter,
|
|
||||||
session_token: session.current?.session.token ?? '',
|
session_token: session.current?.session.token ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -76,7 +75,7 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="flex h-svh flex-col items-center justify-center">
|
<div class="flex h-svh flex-col items-center justify-center">
|
||||||
{#if prompt.current.length === 0 && openRouterKeyQuery.data}
|
{#if prompt.current.length === 0 && userKeysQuery.data && Object.values(userKeysQuery.data).some(key => key !== null)}
|
||||||
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
|
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
|
||||||
<h2 class="text-left font-serif text-3xl font-semibold">
|
<h2 class="text-left font-serif text-3xl font-semibold">
|
||||||
Hey there <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
Hey there <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
||||||
|
|
@ -131,7 +130,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if !openRouterKeyQuery.data && !openRouterKeyQuery.isLoading}
|
{:else if userKeysQuery.data && !Object.values(userKeysQuery.data).some(key => key !== null) && !userKeysQuery.isLoading}
|
||||||
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
|
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
|
||||||
<h2 class="text-left font-serif text-3xl font-semibold">
|
<h2 class="text-left font-serif text-3xl font-semibold">
|
||||||
Hey there, <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
Hey there, <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue