mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-05-15 04:17:52 +08:00
feat: 新增模型管理和供应商配置功能
### v1.1.0 - #### Added - 新增 AI 笔记风格选择 - 新增 AI 笔记返回格式选择 - 添加 AI 自定义笔记备注 Prompt - 添加任务失败重试 - 添加全局设置页,可在设置页进行模型设置 - #### Optimize - 优化前端样式,优化用户体验 - 增加生成中间产物,可用于失败后加快生成速度 - #### Fix - 修复视频截图视频过早删除错误
This commit is contained in:
@@ -1,153 +1,281 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useProviderStore } from '@/store/providerStore';
|
||||
import {useEffect, useState} from 'react';
|
||||
FormDescription,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useProviderStore } from '@/store/providerStore'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { testConnection, fetchModels } from '@/services/model.ts'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select.tsx' // ⚡新增 fetchModels
|
||||
import { ModelSelector } from '@/components/Form/modelForm/ModelSelector.tsx'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert.tsx'
|
||||
|
||||
// ✅ 表单校验 schema
|
||||
// ✅ Provider表单schema
|
||||
const ProviderSchema = z.object({
|
||||
name: z.string().min(2, '名称不能少于 2 个字符'),
|
||||
apiKey: z.string().optional(),
|
||||
baseUrl: z.string().url('必须是合法 URL'),
|
||||
type: z.string(), // 只展示,不可改
|
||||
});
|
||||
type: z.string(),
|
||||
})
|
||||
|
||||
type ProviderFormValues = z.infer<typeof ProviderSchema>;
|
||||
type ProviderFormValues = z.infer<typeof ProviderSchema>
|
||||
|
||||
const ProviderForm = () => {
|
||||
const rawId= useParams();
|
||||
console.log('rawId',rawId)
|
||||
// @ts-ignore
|
||||
const [providerName, idPart] = rawId.id.split('&');
|
||||
const [id,setId ]= useState(Number(idPart?.split('=')[1])) // => "1"
|
||||
const getProviderById = useProviderStore((state) => state.getProviderById);
|
||||
const provider = getProviderById(id);
|
||||
// ✅ Model表单schema
|
||||
const ModelSchema = z.object({
|
||||
modelName: z.string().min(1, '请选择或填写模型名称'),
|
||||
})
|
||||
|
||||
const form = useForm<ProviderFormValues>({
|
||||
type ModelFormValues = z.infer<typeof ModelSchema>
|
||||
interface IModel {
|
||||
id: string
|
||||
created: number
|
||||
object: string
|
||||
owned_by: string
|
||||
permission: string
|
||||
root: string
|
||||
}
|
||||
const ProviderForm = ({ isCreate = false }: { isCreate?: boolean }) => {
|
||||
const { id } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const isEditMode = !isCreate
|
||||
|
||||
const getProviderById = useProviderStore(state => state.getProviderById)
|
||||
const loadProviderById = useProviderStore(state => state.loadProviderById)
|
||||
const updateProvider = useProviderStore(state => state.updateProvider)
|
||||
const addNewProvider = useProviderStore(state => state.addNewProvider)
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [isBuiltIn, setIsBuiltIn] = useState(false)
|
||||
const [modelOptions, setModelOptions] = useState<IModel[]>([]) // ⚡新增,保存模型列表
|
||||
const [modelLoading, setModelLoading] = useState(false)
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const providerForm = useForm<ProviderFormValues>({
|
||||
resolver: zodResolver(ProviderSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
type: '',
|
||||
type: 'custom',
|
||||
},
|
||||
});
|
||||
})
|
||||
const filteredModelOptions = modelOptions.filter(model => {
|
||||
const keywords = search.trim().toLowerCase().split(/\s+/) // 支持多个关键词
|
||||
const target = model.id.toLowerCase()
|
||||
return keywords.every(kw => target.includes(kw))
|
||||
})
|
||||
|
||||
const modelForm = useForm<ModelFormValues>({
|
||||
resolver: zodResolver(ModelSchema),
|
||||
defaultValues: {
|
||||
modelName: '',
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log(provider)
|
||||
// if (provider) {
|
||||
// form.reset({
|
||||
// name: provider.name,
|
||||
// apiKey: provider.apiKey,
|
||||
// baseUrl: provider.baseUrl,
|
||||
// type: provider.type,
|
||||
// });
|
||||
// }
|
||||
}, [id,provider, form]);
|
||||
const load = async () => {
|
||||
if (isEditMode) {
|
||||
const data = await loadProviderById(id!)
|
||||
providerForm.reset(data)
|
||||
setIsBuiltIn(data.type === 'built-in')
|
||||
} else {
|
||||
providerForm.reset({
|
||||
name: '',
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
type: 'custom',
|
||||
})
|
||||
setIsBuiltIn(false)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
load()
|
||||
}, [id])
|
||||
|
||||
const isBuiltIn = provider?.type === 'built-in';
|
||||
// 测试连通性
|
||||
const handleTest = async () => {
|
||||
const values = providerForm.getValues()
|
||||
if (!values.apiKey || !values.baseUrl) {
|
||||
toast.error('请填写 API Key 和 Base URL')
|
||||
return
|
||||
}
|
||||
try {
|
||||
setTesting(true)
|
||||
const data = await testConnection({
|
||||
api_key: values.apiKey,
|
||||
base_url: values.baseUrl,
|
||||
})
|
||||
if (data.data.code === 0) {
|
||||
toast.success('测试连通性成功 🎉')
|
||||
} else {
|
||||
toast.error(`连接失败: ${data.data.msg || '未知错误'}`)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('测试连通性异常')
|
||||
} finally {
|
||||
setTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = (values: ProviderFormValues) => {
|
||||
console.log('📝 提交表单数据:', values);
|
||||
// TODO: 提交接口 /update_provider
|
||||
};
|
||||
// 加载模型列表
|
||||
const handleModelLoad = async () => {
|
||||
const values = providerForm.getValues()
|
||||
if (!values.apiKey || !values.baseUrl) {
|
||||
toast.error('请先填写 API Key 和 Base URL')
|
||||
return
|
||||
}
|
||||
try {
|
||||
setModelLoading(true) // ✅ 开始 loading
|
||||
const res = await fetchModels(id!, { noCache: true }) // 这里稍后解释
|
||||
if (res.data.code === 0 && res.data.data.models.data.length > 0) {
|
||||
setModelOptions(res.data.data.models.data)
|
||||
console.log('🔧 模型列表:', res.data.data)
|
||||
toast.success('模型列表加载成功 🎉')
|
||||
} else {
|
||||
toast.error('未获取到模型列表')
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('加载模型列表失败')
|
||||
} finally {
|
||||
setModelLoading(false) // ✅ 结束 loading
|
||||
}
|
||||
}
|
||||
|
||||
// if (!provider) return <div className="p-4">加载中...</div>;
|
||||
// 保存Provider信息
|
||||
const onProviderSubmit = async (values: ProviderFormValues) => {
|
||||
if (isEditMode) {
|
||||
updateProvider({ ...values, id: id! })
|
||||
toast.success('更新供应商成功')
|
||||
} else {
|
||||
addNewProvider({ ...values })
|
||||
toast.success('新增供应商成功')
|
||||
}
|
||||
}
|
||||
|
||||
// 保存Model信息
|
||||
const onModelSubmit = async (values: ModelFormValues) => {
|
||||
console.log('🔧 选择的模型:', values.modelName)
|
||||
toast.success(`保存模型: ${values.modelName}`)
|
||||
}
|
||||
|
||||
if (loading) return <div className="p-4">加载中...</div>
|
||||
|
||||
return (
|
||||
|
||||
<Form {...form}>
|
||||
|
||||
<div className="flex flex-col gap-8 p-4">
|
||||
{/* Provider信息表单 */}
|
||||
<Form {...providerForm}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl p-4 flex flex-col gap-4"
|
||||
onSubmit={providerForm.handleSubmit(onProviderSubmit)}
|
||||
className="flex max-w-xl flex-col gap-4"
|
||||
>
|
||||
<div className="text-lg font-bold">模型供应商配置</div>
|
||||
|
||||
{/* 名称 */}
|
||||
<div className="text-lg font-bold">
|
||||
{isEditMode ? '编辑模型供应商' : '新增模型供应商'}
|
||||
</div>
|
||||
{!isBuiltIn && (
|
||||
<div className="text-sm text-red-500 italic">
|
||||
自定义模型供应商需要确保兼容 OpenAI SDK
|
||||
</div>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">名称</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} disabled={isBuiltIn} className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
control={providerForm.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">名称</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} disabled={isBuiltIn} className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* API Key */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiKey"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">API Key</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={'sk-xxx'} {...field} className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
|
||||
</FormItem>
|
||||
|
||||
|
||||
|
||||
)}
|
||||
control={providerForm.control}
|
||||
name="apiKey"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">API Key</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Base URL */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">API 代理地址</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
control={providerForm.control}
|
||||
name="baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">API地址</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} className="flex-1" />
|
||||
</FormControl>
|
||||
<Button type="button" onClick={handleTest} variant="ghost" disabled={testing}>
|
||||
{testing ? '测试中...' : '测试连通性'}
|
||||
</Button>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 类型 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">类型</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} disabled className="flex-1" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
control={providerForm.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center gap-4">
|
||||
<FormLabel className="w-24 text-right">类型</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} disabled className="flex-1" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="pt-2">
|
||||
<Button type="submit" disabled={!form.formState.isDirty}>
|
||||
保存修改
|
||||
<Button type="submit" disabled={!providerForm.formState.isDirty}>
|
||||
{isEditMode ? '保存修改' : '保存创建'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProviderForm;
|
||||
{/* 模型信息表单 */}
|
||||
<div className="flex max-w-xl flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-bold">模型列表</span>
|
||||
<div className={'flex flex-col gap-2 rounded bg-[#FEF0F0] p-2.5'}>
|
||||
<h2 className={'font-bold'}>注意!</h2>
|
||||
<span>请确保已经保存供应商信息,以及通过测试连通性.</span>
|
||||
</div>
|
||||
<ModelSelector providerId={id!} />
|
||||
|
||||
{/*<datalist id="model-options">*/}
|
||||
{/* {modelOptions.map(model => (*/}
|
||||
{/* <option key={model.id + '1'} value={model.id} />*/}
|
||||
{/* ))}*/}
|
||||
{/*</datalist>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProviderForm
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// iconMap.ts
|
||||
import * as Icons from '@lobehub/icons'
|
||||
|
||||
export const IconMap = Icons;
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as Icons from '@lobehub/icons'
|
||||
import CustomLogo from '@/assets/customAI.png'
|
||||
|
||||
interface AILogoProps {
|
||||
name: string // 图标名称(区分大小写!如 OpenAI、DeepSeek)
|
||||
style?: 'Color' | 'Text' | 'Outlined' | 'Glyph'
|
||||
size?: number
|
||||
}
|
||||
|
||||
const AILogo = ({ name, style = 'Color', size = 24 }: AILogoProps) => {
|
||||
const Icon = Icons[name as keyof typeof Icons]
|
||||
if (!Icon) {
|
||||
console.error(`❌ 图标组件不存在: ${name}`)
|
||||
return (
|
||||
<span style={{ fontSize: size }}>
|
||||
<img src={CustomLogo} alt="CustomLogo" style={{ width: size, height: size }} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const Variant = Icon[style as keyof typeof Icon]
|
||||
if (!Variant) {
|
||||
return <Icon size={size} />
|
||||
}
|
||||
|
||||
return <Variant size={size} />
|
||||
}
|
||||
|
||||
export default AILogo
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useModelStore } from '@/store/modelStore'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
interface ModelSelectorProps {
|
||||
providerId: string
|
||||
}
|
||||
|
||||
export function ModelSelector({ providerId }: ModelSelectorProps) {
|
||||
const { models, loading, selectedModel, loadModels, setSelectedModel, addNewModel } =
|
||||
useModelStore()
|
||||
const [search, setSearch] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const filteredModels = models.filter(model => {
|
||||
const keywords = search.trim().toLowerCase().split(/\s+/)
|
||||
const target = model.id.toLowerCase()
|
||||
return keywords.every(kw => target.includes(kw))
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (providerId) {
|
||||
loadModels(providerId)
|
||||
}
|
||||
}, [providerId])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!selectedModel) {
|
||||
toast.error('请选择一个模型')
|
||||
return
|
||||
}
|
||||
try {
|
||||
setSubmitting(true)
|
||||
await addNewModel(providerId, selectedModel)
|
||||
toast.success('保存模型成功 🎉')
|
||||
} catch (error) {
|
||||
toast.error('保存失败')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2 font-bold">
|
||||
<span>选择模型</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
type="button"
|
||||
onClick={() => loadModels(providerId)}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '加载中...' : '刷新模型'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Select value={selectedModel} onValueChange={setSelectedModel}>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
<SelectValue placeholder="请选择模型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<div className="p-2">
|
||||
<Input
|
||||
placeholder="搜索模型..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
{filteredModels.map(model => (
|
||||
<SelectItem key={model.id} value={model.id}>
|
||||
{model.id}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Button onClick={handleSubmit} disabled={submitting || !selectedModel}>
|
||||
{submitting ? '保存中...' : '保存模型'}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +1,39 @@
|
||||
import ProviderCard from '@/components/Form/modelForm/components/providerCard.tsx'
|
||||
import { Button } from '@/components/ui/button.tsx'
|
||||
import { useProviderStore } from '@/store/providerStore'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const Provider = () => {
|
||||
const providers = useProviderStore(state => state.provider)
|
||||
|
||||
const providers = useProviderStore(state => state.provider)
|
||||
const navigate = useNavigate()
|
||||
const handleClick = () => {
|
||||
navigate(`/settings/model/new`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className={'search flex gap-1 py-1.5'}>
|
||||
<Button type={'button'} className="w-full">
|
||||
<Button
|
||||
type={'button'}
|
||||
onClick={() => {
|
||||
handleClick()
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
添加模型供应商
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-sm font-light">模型供应商列表</div>
|
||||
<div>
|
||||
{providers &&
|
||||
providers.map((provider, index) => {
|
||||
providers.map((provider, index) => {
|
||||
return (
|
||||
<ProviderCard
|
||||
key={index}
|
||||
providerName={provider.name}
|
||||
Icon={provider.logo}
|
||||
id={provider.id}
|
||||
enable={provider.enabled}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { FC } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import {useNavigate, useParams} from 'react-router-dom'
|
||||
import AILogo from "@/components/Icons";
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import AILogo from '@/components/Form/modelForm/Icons'
|
||||
import { useProviderStore } from '@/store/providerStore'
|
||||
export interface IProviderCardProps {
|
||||
id: string
|
||||
providerName: string
|
||||
Icon: string
|
||||
enable: number
|
||||
}
|
||||
const ProviderCard: FC<IProviderCardProps> = ({ providerName, Icon, id }: IProviderCardProps) => {
|
||||
const ProviderCard: FC<IProviderCardProps> = ({
|
||||
providerName,
|
||||
Icon,
|
||||
id,
|
||||
enable,
|
||||
}: IProviderCardProps) => {
|
||||
const navigate = useNavigate()
|
||||
const updateProvider = useProviderStore(state => state.updateProvider)
|
||||
const handleClick = () => {
|
||||
navigate(`/settings/model/${providerName}&id=${id}`)
|
||||
navigate(`/settings/model/${id}`)
|
||||
}
|
||||
const rawId= useParams();
|
||||
console.log('rawId',rawId)
|
||||
const handleEnable = () => {
|
||||
console.log('enable', enable)
|
||||
updateProvider({
|
||||
id,
|
||||
enabled: enable == 1 ? 0 : 1,
|
||||
})
|
||||
}
|
||||
const rawId = useParams()
|
||||
console.log('rawId', rawId)
|
||||
// @ts-ignore
|
||||
const { id: currentId } = useParams();
|
||||
const { id: currentId } = useParams()
|
||||
const isActive = currentId === id
|
||||
return (
|
||||
<div
|
||||
@@ -24,18 +39,26 @@ const ProviderCard: FC<IProviderCardProps> = ({ providerName, Icon, id }: IProvi
|
||||
handleClick()
|
||||
}}
|
||||
className={
|
||||
styles.card + ' flex h-14 items-center justify-between rounded border border-[#f3f3f3] p-2'
|
||||
+(isActive ? ' bg-[#F0F0F0] font-semibold text-blue-600' : '')
|
||||
styles.card +
|
||||
' flex h-14 items-center justify-between rounded border border-[#f3f3f3] p-2' +
|
||||
(isActive ? ' bg-[#F0F0F0] font-semibold text-blue-600' : '')
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-lg">
|
||||
<div className="h-9 w-9 flex items-center">
|
||||
<AILogo name={Icon} />
|
||||
<div className="flex h-9 w-9 items-center">
|
||||
<AILogo name={Icon} />
|
||||
</div>
|
||||
<div className="font-semibold">{providerName}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Switch />
|
||||
<Switch
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
handleEnable()
|
||||
}}
|
||||
checked={enable == 1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user