generated from kevisual/vite-react-template
feat: 添加配置页面和状态管理,集成 AI SDK
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.38",
|
||||
"@ai-sdk/openai": "^3.0.26",
|
||||
"@ai-sdk/openai-compatible": "^2.0.28",
|
||||
"@kevisual/cnb": "^0.0.19",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/router": "0.0.70",
|
||||
@@ -29,6 +32,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@tanstack/react-router": "^1.158.1",
|
||||
"ai": "^6.0.77",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
@@ -40,6 +44,7 @@
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"sonner": "^2.0.7",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
127
pnpm-lock.yaml
generated
127
pnpm-lock.yaml
generated
@@ -8,6 +8,15 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@ai-sdk/anthropic':
|
||||
specifier: ^3.0.38
|
||||
version: 3.0.38(zod@4.3.6)
|
||||
'@ai-sdk/openai':
|
||||
specifier: ^3.0.26
|
||||
version: 3.0.26(zod@4.3.6)
|
||||
'@ai-sdk/openai-compatible':
|
||||
specifier: ^2.0.28
|
||||
version: 2.0.28(zod@4.3.6)
|
||||
'@kevisual/cnb':
|
||||
specifier: ^0.0.19
|
||||
version: 0.0.19(dotenv@17.2.3)(idb-keyval@6.2.1)
|
||||
@@ -44,6 +53,9 @@ importers:
|
||||
'@tanstack/react-router':
|
||||
specifier: ^1.158.1
|
||||
version: 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
ai:
|
||||
specifier: ^6.0.77
|
||||
version: 6.0.77(zod@4.3.6)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -77,6 +89,9 @@ importers:
|
||||
sonner:
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
zod:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
zustand:
|
||||
specifier: ^5.0.11
|
||||
version: 5.0.11(@types/react@19.2.13)(immer@10.1.1)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||
@@ -169,6 +184,40 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@ai-sdk/anthropic@3.0.38':
|
||||
resolution: {integrity: sha512-9MchyPRPni0WzrFeIGNevZpQVfWxaS+MQFupIXYQo9VgHnuO1Vyrp9SBmjkkuoAdBs7GomsWqLZCcNMJAVbdFA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/gateway@3.0.39':
|
||||
resolution: {integrity: sha512-SeCZBAdDNbWpVUXiYgOAqis22p5MEYfrjRw0hiBa5hM+7sDGYQpMinUjkM8kbPXMkY+AhKLrHleBl+SuqpzlgA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.28':
|
||||
resolution: {integrity: sha512-WzDnU0B13FMSSupDtm2lksFZvWGXnOfhG5S0HoPI0pkX5uVkr6N1UTATMyVaxLCG0MRkMhXCjkg4NXgEbb330Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/openai@3.0.26':
|
||||
resolution: {integrity: sha512-W/hiwxIfG29IO0Fob1HwWpFssMsNrxWoX8A7DwNGOtKArDBmJNuGzQeU/k0Fnh8WyvZEnfxkjO4oXkSXfVBayg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.14':
|
||||
resolution: {integrity: sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/provider@3.0.8':
|
||||
resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -703,6 +752,10 @@ packages:
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@oxc-project/runtime@0.112.0':
|
||||
resolution: {integrity: sha512-4vYtWXMnXM6EaweCxbJ6bISAhkNHeN33SihvuX3wrpqaSJA4ZEoW35i9mSvE74+GDf1yTeVE+aEHA+WBpjDk/g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -1349,6 +1402,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@tailwindcss/node@4.1.18':
|
||||
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
|
||||
|
||||
@@ -1571,6 +1627,10 @@ packages:
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
|
||||
'@vercel/oidc@3.1.0':
|
||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@vitejs/plugin-react@5.1.3':
|
||||
resolution: {integrity: sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -1582,6 +1642,12 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
ai@6.0.77:
|
||||
resolution: {integrity: sha512-tyyhrRpCRFVlivdNIFLK8cexSBB2jwTqO0z1qJQagk+UxZ+MW8h5V8xsvvb+xdKDY482Y8KAm0mr7TDnPKvvlw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
ansis@4.2.0:
|
||||
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1753,6 +1819,10 @@ packages:
|
||||
eventemitter3@5.0.4:
|
||||
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
||||
|
||||
eventsource-parser@3.0.6:
|
||||
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
fdir@6.4.4:
|
||||
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
|
||||
peerDependencies:
|
||||
@@ -1894,6 +1964,9 @@ packages:
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||
|
||||
json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2625,6 +2698,42 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ai-sdk/anthropic@3.0.38(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/gateway@3.0.39(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
'@vercel/oidc': 3.1.0
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.28(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/openai@3.0.26(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.14(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@standard-schema/spec': 1.1.0
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/provider@3.0.8':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
@@ -3212,6 +3321,8 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@oxc-project/runtime@0.112.0': {}
|
||||
|
||||
'@oxc-project/types@0.112.0': {}
|
||||
@@ -3745,6 +3856,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.40.1':
|
||||
optional: true
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@tailwindcss/node@4.1.18':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
@@ -3971,6 +4084,8 @@ snapshots:
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
|
||||
'@vercel/oidc@3.1.0': {}
|
||||
|
||||
'@vitejs/plugin-react@5.1.3(vite@8.0.0-beta.13(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.5.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@@ -3985,6 +4100,14 @@ snapshots:
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
ai@6.0.77(zod@4.3.6):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 3.0.39(zod@4.3.6)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@4.3.6)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 4.3.6
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
@@ -4161,6 +4284,8 @@ snapshots:
|
||||
|
||||
eventemitter3@5.0.4: {}
|
||||
|
||||
eventsource-parser@3.0.6: {}
|
||||
|
||||
fdir@6.4.4(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
@@ -4276,6 +4401,8 @@ snapshots:
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-schema@0.4.0: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
|
||||
116
src/app/config/page.tsx
Normal file
116
src/app/config/page.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useConfigStore } from './store';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { configSchema } from './store/schema';
|
||||
|
||||
export const ConfigPage = () => {
|
||||
const { config, setConfig, resetConfig } = useConfigStore();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const result = configSchema.safeParse(config);
|
||||
if (result.success) {
|
||||
console.log('配置已保存:', config);
|
||||
// 可以在此处添加 toast 通知
|
||||
} else {
|
||||
console.error('验证错误:', result.error.format());
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field: keyof typeof config, value: string) => {
|
||||
setConfig({ [field]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-2xl py-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>CNB 配置</CardTitle>
|
||||
<CardDescription>
|
||||
配置您的 CNB API 设置。这些设置会保存在浏览器的本地存储中。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api-key">API 密钥</Label>
|
||||
<Input
|
||||
id="api-key"
|
||||
type="text"
|
||||
value={config.CNB_API_KEY}
|
||||
onChange={(e) => handleChange('CNB_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 CNB API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cookie">Cookie</Label>
|
||||
<Input
|
||||
id="cookie"
|
||||
type="text"
|
||||
value={config.CNB_COOKIE}
|
||||
onChange={(e) => handleChange('CNB_COOKIE', e.target.value)}
|
||||
placeholder="请输入您的 CNB Cookie"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cors-url">跨域地址</Label>
|
||||
<Input
|
||||
id="cors-url"
|
||||
type="url"
|
||||
value={config.CNB_CORS_URL}
|
||||
onChange={(e) => handleChange('CNB_CORS_URL', e.target.value)}
|
||||
placeholder="https://cors.example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-base-url">AI 基础地址</Label>
|
||||
<Input
|
||||
id="ai-base-url"
|
||||
type="url"
|
||||
value={config.AI_BASE_URL}
|
||||
onChange={(e) => handleChange('AI_BASE_URL', e.target.value)}
|
||||
placeholder="请输入 AI 基础地址"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-model">AI 模型</Label>
|
||||
<Input
|
||||
id="ai-model"
|
||||
type="text"
|
||||
value={config.AI_MODEL}
|
||||
onChange={(e) => handleChange('AI_MODEL', e.target.value)}
|
||||
placeholder="请输入 AI 模型名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-api-key">AI 密钥</Label>
|
||||
<Input
|
||||
id="ai-api-key"
|
||||
type="password"
|
||||
value={config.AI_API_KEY}
|
||||
onChange={(e) => handleChange('AI_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 AI API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button type="submit">保存配置</Button>
|
||||
<Button type="button" variant="outline" onClick={resetConfig}>
|
||||
重置为默认值
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigPage;
|
||||
56
src/app/config/store/index.ts
Normal file
56
src/app/config/store/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { Config, defaultConfig } from './schema';
|
||||
|
||||
type ConfigState = {
|
||||
config: Config;
|
||||
setConfig: (config: Partial<Config>) => void;
|
||||
resetConfig: () => void;
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'cnb-config';
|
||||
|
||||
const loadInitialConfig = (): Config => {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
return {
|
||||
CNB_API_KEY: '',
|
||||
CNB_COOKIE: '',
|
||||
CNB_CORS_URL: 'https://cors.kevisual.cn',
|
||||
AI_BASE_URL: '',
|
||||
AI_MODEL: '',
|
||||
AI_API_KEY: ''
|
||||
};
|
||||
};
|
||||
|
||||
export const useConfigStore = create<ConfigState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
config: loadInitialConfig(),
|
||||
setConfig: (newConfig) =>
|
||||
set((state) => ({
|
||||
config: { ...state.config, ...newConfig },
|
||||
})),
|
||||
resetConfig: () =>
|
||||
set({
|
||||
config: {
|
||||
CNB_API_KEY: '',
|
||||
CNB_COOKIE: '',
|
||||
CNB_CORS_URL: 'https://cors.kevisual.cn',
|
||||
AI_BASE_URL: 'https://api.cnb.cool/kevisual/cnb-ai/-/ai/',
|
||||
AI_MODEL: 'CNB-Models',
|
||||
AI_API_KEY: ''
|
||||
},
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
21
src/app/config/store/schema.ts
Normal file
21
src/app/config/store/schema.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const configSchema = z.object({
|
||||
CNB_API_KEY: z.string().min(1, 'API Key is required'),
|
||||
CNB_COOKIE: z.string().min(1, 'Cookie is required'),
|
||||
CNB_CORS_URL: z.url('Must be a valid URL'),
|
||||
AI_BASE_URL: z.url('Must be a valid URL'),
|
||||
AI_MODEL: z.string().min(1, 'AI Model is required'),
|
||||
AI_API_KEY: z.string().min(1, 'AI API Key is required'),
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof configSchema>;
|
||||
|
||||
export const defaultConfig: Config = {
|
||||
CNB_API_KEY: '',
|
||||
CNB_COOKIE: '',
|
||||
CNB_CORS_URL: 'https://cors.kevisual.cn',
|
||||
AI_BASE_URL: '',
|
||||
AI_MODEL: '',
|
||||
AI_API_KEY: ''
|
||||
};
|
||||
43
src/app/page.tsx
Normal file
43
src/app/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CNB, Issue } from '@kevisual/cnb'
|
||||
import { useLayoutEffect } from 'react'
|
||||
import { useConfigStore } from './config/store'
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
||||
import { generateText } from 'ai';
|
||||
const init2 = async () => {
|
||||
const cnb = new CNB({
|
||||
token: 'cIDfLOOIr1Trt15cdnwfndupEZG',
|
||||
cookie: 'CNBSESSION=1770014410.1935321989751226368.7f386c282d80efb5256180ef94c2865e20a8be72e2927a5f8eb1eb72142de39f;csrfkey=2028873452',
|
||||
cors: {
|
||||
baseUrl: 'https://cors.kevisual.cn'
|
||||
}
|
||||
})
|
||||
// const res = await cnb.issue.getList('kevisual/kevisual')
|
||||
// console.log('res', res)
|
||||
const token = await cnb.user.getCurrentUser()
|
||||
console.log('token', token)
|
||||
}
|
||||
|
||||
const initAi = async () => {
|
||||
const state = useConfigStore.getState()
|
||||
const config = state.config
|
||||
const cors = state.config.CNB_CORS_URL
|
||||
const base = cors + '/' + config.AI_BASE_URL.replace('https://', '')
|
||||
const cnb = createOpenAICompatible({
|
||||
baseURL: base,
|
||||
name: 'custom-cnb',
|
||||
apiKey: config.AI_API_KEY,
|
||||
});
|
||||
const model = config.AI_MODEL;
|
||||
// const model = 'hunyuan';
|
||||
const { text } = await generateText({
|
||||
model: cnb(model),
|
||||
prompt: '你好',
|
||||
});
|
||||
console.log('text', text)
|
||||
}
|
||||
export const Home = () => {
|
||||
useLayoutEffect(() => { initAi() }, [])
|
||||
return <div>Home Page</div>
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { CNB, Issue } from '@kevisual/cnb/src/index.ts'
|
||||
import { useLayoutEffect } from 'react'
|
||||
const init2 = async () => {
|
||||
const cnb = new CNB({
|
||||
token: 'cIDfLOOIr1Trt15cdnwfndupEZG',
|
||||
cookie: 'CNBSESSION=1770014410.1935321989751226368.7f386c282d80efb5256180ef94c2865e20a8be72e2927a5f8eb1eb72142de39f;csrfkey=2028873452',
|
||||
cors: {
|
||||
baseUrl: 'https://cors.kevisual.cn'
|
||||
}
|
||||
})
|
||||
// const res = await cnb.issue.getList('kevisual/kevisual')
|
||||
// console.log('res', res)
|
||||
const token = await cnb.user.getCurrentUser()
|
||||
console.log('token', token)
|
||||
}
|
||||
export const Home = () => {
|
||||
useLayoutEffect(() => { init2() }, [])
|
||||
return <div>Home Page</div>
|
||||
}
|
||||
@@ -9,8 +9,14 @@
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as ConfigRouteImport } from './routes/config'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
|
||||
const ConfigRoute = ConfigRouteImport.update({
|
||||
id: '/config',
|
||||
path: '/config',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
@@ -19,28 +25,39 @@ const IndexRoute = IndexRouteImport.update({
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/'
|
||||
fullPaths: '/' | '/config'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/'
|
||||
id: '__root__' | '/'
|
||||
to: '/' | '/config'
|
||||
id: '__root__' | '/' | '/config'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ConfigRoute: typeof ConfigRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/config': {
|
||||
id: '/config'
|
||||
path: '/config'
|
||||
fullPath: '/config'
|
||||
preLoaderRoute: typeof ConfigRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
@@ -53,6 +70,7 @@ declare module '@tanstack/react-router' {
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
ConfigRoute: ConfigRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
|
||||
9
src/routes/config.tsx
Normal file
9
src/routes/config.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import Home from '@/app/config/page'
|
||||
export const Route = createFileRoute('/config')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <Home />
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { Home } from '@/pages/Home'
|
||||
import Home from '@/app/page'
|
||||
export const Route = createFileRoute('/')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
17
test/ai.ts
Normal file
17
test/ai.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
||||
import { generateText } from 'ai';
|
||||
|
||||
|
||||
const cnb = createOpenAICompatible({
|
||||
baseURL: 'https://api.cnb.cool/kevisual/cnb-ai/-/ai',
|
||||
name: 'custom-cnb',
|
||||
apiKey: 'cIDfLOOIr1Trt15cdnwfndupEZG',
|
||||
});
|
||||
// const model = config.AI_MODEL;
|
||||
const model = 'hunyuan';
|
||||
const { text } = await generateText({
|
||||
model: cnb(model),
|
||||
prompt: 'Say hello in one sentence.',
|
||||
});
|
||||
console.log('text', text)
|
||||
// https://api.cnb.cool
|
||||
Reference in New Issue
Block a user