feat: 添加配置页面和状态管理,集成 AI SDK

This commit is contained in:
2026-02-08 13:11:37 +08:00
parent abd9860a90
commit f117302a98
12 changed files with 416 additions and 23 deletions

116
src/app/config/page.tsx Normal file
View 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;

View 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,
}
)
);

View 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
View 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;