fix: lint code to build success

This commit is contained in:
HYana
2025-05-10 01:19:30 +08:00
parent 4031ff2835
commit 7a8102430f
48 changed files with 1657 additions and 1240 deletions

View File

@@ -1,3 +1,3 @@
{
"*.{js,jsx,ts,tsx}": ["eslint --fix", "eslint"]
"*.{js,jsx,ts,tsx}": ["pnpm lint --fix", "pnpm lint"]
}

View File

@@ -4,38 +4,50 @@ import styles from './botCard.module.css';
export default function BotCard({ botCardVO }: { botCardVO: BotCardVO }) {
return (
<div className={`${styles.cardContainer}`}>
<div className={`${styles.iconBasicInfoContainer}`}>
<img className={`${styles.iconImage}`} src={botCardVO.iconURL} alt="icon" />
<img
className={`${styles.iconImage}`}
src={botCardVO.iconURL}
alt="icon"
/>
<div className={`${styles.basicInfoContainer}`}>
<div className={`${styles.basicInfoNameContainer}`}>
<div className={`${styles.basicInfoName}`}>
{botCardVO.name}
</div>
<div className={`${styles.basicInfoName}`}>{botCardVO.name}</div>
<div className={`${styles.basicInfoDescription}`}>
{botCardVO.description}
</div>
</div>
<div className={`${styles.basicInfoAdapterContainer}`}>
<svg className={`${styles.basicInfoAdapterIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 8.99374C2 5.68349 4.67654 3 8.00066 3H15.9993C19.3134 3 22 5.69478 22 8.99374V21H8.00066C4.68659 21 2 18.3052 2 15.0063V8.99374ZM20 19V8.99374C20 6.79539 18.2049 5 15.9993 5H8.00066C5.78458 5 4 6.78458 4 8.99374V15.0063C4 17.2046 5.79512 19 8.00066 19H20ZM14 11H16V13H14V11ZM8 11H10V13H8V11Z"></path></svg>
<svg
className={`${styles.basicInfoAdapterIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M2 8.99374C2 5.68349 4.67654 3 8.00066 3H15.9993C19.3134 3 22 5.69478 22 8.99374V21H8.00066C4.68659 21 2 18.3052 2 15.0063V8.99374ZM20 19V8.99374C20 6.79539 18.2049 5 15.9993 5H8.00066C5.78458 5 4 6.78458 4 8.99374V15.0063C4 17.2046 5.79512 19 8.00066 19H20ZM14 11H16V13H14V11ZM8 11H10V13H8V11Z"></path>
</svg>
<span className={`${styles.basicInfoAdapterLabel}`}>
{botCardVO.adapterLabel}
</span>
</div>
<div className={`${styles.basicInfoPipelineContainer}`}>
<svg className={`${styles.basicInfoPipelineIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6 21.5C4.067 21.5 2.5 19.933 2.5 18C2.5 16.067 4.067 14.5 6 14.5C7.5852 14.5 8.92427 15.5539 9.35481 16.9992L15 16.9994V15L17 14.9994V9.24339L14.757 6.99938H9V9.00003H3V3.00003H9V4.99939H14.757L18 1.75739L22.2426 6.00003L19 9.24139V14.9994L21 15V21H15V18.9994L9.35499 19.0003C8.92464 20.4459 7.58543 21.5 6 21.5ZM6 16.5C5.17157 16.5 4.5 17.1716 4.5 18C4.5 18.8285 5.17157 19.5 6 19.5C6.82843 19.5 7.5 18.8285 7.5 18C7.5 17.1716 6.82843 16.5 6 16.5ZM19 17H17V19H19V17ZM18 4.58581L16.5858 6.00003L18 7.41424L19.4142 6.00003L18 4.58581ZM7 5.00003H5V7.00003H7V5.00003Z"></path></svg>
<svg
className={`${styles.basicInfoPipelineIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M6 21.5C4.067 21.5 2.5 19.933 2.5 18C2.5 16.067 4.067 14.5 6 14.5C7.5852 14.5 8.92427 15.5539 9.35481 16.9992L15 16.9994V15L17 14.9994V9.24339L14.757 6.99938H9V9.00003H3V3.00003H9V4.99939H14.757L18 1.75739L22.2426 6.00003L19 9.24139V14.9994L21 15V21H15V18.9994L9.35499 19.0003C8.92464 20.4459 7.58543 21.5 6 21.5ZM6 16.5C5.17157 16.5 4.5 17.1716 4.5 18C4.5 18.8285 5.17157 19.5 6 19.5C6.82843 19.5 7.5 18.8285 7.5 18C7.5 17.1716 6.82843 16.5 6 16.5ZM19 17H17V19H19V17ZM18 4.58581L16.5858 6.00003L18 7.41424L19.4142 6.00003L18 4.58581ZM7 5.00003H5V7.00003H7V5.00003Z"></path>
</svg>
<span className={`${styles.basicInfoPipelineLabel}`}>
{botCardVO.usePipelineName}
</span>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,5 +1,8 @@
import { useEffect, useState } from 'react';
import { IChooseAdapterEntity, IPipelineEntity } from '@/app/home/bots/components/bot-form/ChooseEntity';
import {
IChooseAdapterEntity,
IPipelineEntity,
} from '@/app/home/bots/components/bot-form/ChooseEntity';
import {
DynamicFormItemConfig,
getDefaultValues,
@@ -11,10 +14,10 @@ import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicForm
import { httpClient } from '@/app/infra/http/HttpClient';
import { Bot } from '@/app/infra/entities/api';
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { toast } from "sonner"
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { toast } from 'sonner';
import {
Dialog,
@@ -22,23 +25,27 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { Switch } from "@/components/ui/switch"
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
const formSchema = z.object({
name: z.string().min(1, { message: '机器人名称不能为空' }),
@@ -60,7 +67,6 @@ export default function BotForm({
onFormCancel: () => void;
onBotDeleted: () => void;
}) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -73,7 +79,6 @@ export default function BotForm({
},
});
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] =
@@ -91,38 +96,38 @@ export default function BotForm({
Record<string, string>
>({});
const [pipelineNameList, setPipelineNameList] = useState<
IPipelineEntity[]
>([]);
const [pipelineNameList, setPipelineNameList] = useState<IPipelineEntity[]>(
[],
);
const [dynamicFormConfigList, setDynamicFormConfigList] = useState<
IDynamicFormItemSchema[]
>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [, setIsLoading] = useState<boolean>(false);
useEffect(() => {
initBotFormComponent().then(() => {
// 拉取初始化表单信息
if (initBotId) {
getBotConfig(initBotId).then((val) => {
form.setValue('name', val.name);
form.setValue('description', val.description);
form.setValue('adapter', val.adapter);
form.setValue('adapter_config', val.adapter_config);
form.setValue('enable', val.enable);
form.setValue('use_pipeline_uuid', val.use_pipeline_uuid || '');
console.log('form', form.getValues());
handleAdapterSelect(val.adapter);
// dynamicForm.setFieldsValue(val.adapter_config);
}).catch((err) => {
toast.error("获取机器人配置失败:" + err.message);
});
} else {
form.reset();
}
})
// 拉取初始化表单信息
if (initBotId) {
getBotConfig(initBotId)
.then((val) => {
form.setValue('name', val.name);
form.setValue('description', val.description);
form.setValue('adapter', val.adapter);
form.setValue('adapter_config', val.adapter_config);
form.setValue('enable', val.enable);
form.setValue('use_pipeline_uuid', val.use_pipeline_uuid || '');
console.log('form', form.getValues());
handleAdapterSelect(val.adapter);
// dynamicForm.setFieldsValue(val.adapter_config);
})
.catch((err) => {
toast.error('获取机器人配置失败:' + err.message);
});
} else {
form.reset();
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function initBotFormComponent() {
@@ -152,18 +157,24 @@ export default function BotForm({
// 初始化适配器图标列表
setAdapterIconList(
adaptersRes.adapters.reduce((acc, item) => {
acc[item.name] = httpClient.getAdapterIconURL(item.name);
return acc;
}, {} as Record<string, string>),
adaptersRes.adapters.reduce(
(acc, item) => {
acc[item.name] = httpClient.getAdapterIconURL(item.name);
return acc;
},
{} as Record<string, string>,
),
);
// 初始化适配器描述列表
setAdapterDescriptionList(
adaptersRes.adapters.reduce((acc, item) => {
acc[item.name] = item.description.zh_CN;
return acc;
}, {} as Record<string, string>),
adaptersRes.adapters.reduce(
(acc, item) => {
acc[item.name] = item.description.zh_CN;
return acc;
},
{} as Record<string, string>,
),
);
// 初始化适配器表单map
@@ -185,21 +196,24 @@ export default function BotForm({
});
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap);
}
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
async function getBotConfig(
botId: string,
): Promise<z.infer<typeof formSchema>> {
return new Promise((resolve, reject) => {
httpClient.getBot(botId)
.then(res => {
httpClient
.getBot(botId)
.then((res) => {
const bot = res.bot;
resolve({
adapter: bot.adapter,
description: bot.description,
description: bot.description,
name: bot.name,
adapter_config: bot.adapter_config,
enable: bot.enable ?? true,
use_pipeline_uuid: bot.use_pipeline_uuid ?? '',
});
})
.catch(err => {
.catch((err) => {
reject(err);
});
});
@@ -212,7 +226,10 @@ export default function BotForm({
if (dynamicFormConfigList) {
setDynamicFormConfigList(dynamicFormConfigList);
if (!initBotId) {
form.setValue('adapter_config', getDefaultValues(dynamicFormConfigList));
form.setValue(
'adapter_config',
getDefaultValues(dynamicFormConfigList),
);
}
}
setShowDynamicForm(true);
@@ -221,14 +238,6 @@ export default function BotForm({
}
}
function handleSubmitButton() {
// form.submit();
}
function handleFormFinish() {
// dynamicForm.submit();
}
// 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里
function onDynamicFormSubmit(value: object) {
setIsLoading(true);
@@ -250,10 +259,10 @@ export default function BotForm({
.then((res) => {
console.log('update bot success', res);
onFormSubmit(form.getValues());
toast.success("保存成功");
toast.success('保存成功');
})
.catch((err) => {
toast.error("保存失败:" + err.message);
toast.error('保存失败:' + err.message);
})
.finally(() => {
setIsLoading(false);
@@ -274,10 +283,10 @@ export default function BotForm({
.then((res) => {
console.log(res);
onFormSubmit(form.getValues());
toast.success("创建成功");
toast.success('创建成功');
})
.catch((err) => {
toast.error("创建失败:" + err.message);
toast.error('创建失败:' + err.message);
})
.finally(() => {
setIsLoading(false);
@@ -289,38 +298,44 @@ export default function BotForm({
console.log('set loading', false);
}
function handleSaveButton() {
form.handleSubmit(onDynamicFormSubmit)();
}
function deleteBot() {
if (initBotId) {
httpClient.deleteBot(initBotId).then(() => {
onBotDeleted();
}).catch((err) => {
toast.error("删除失败:" + err.message);
});
httpClient
.deleteBot(initBotId)
.then(() => {
onBotDeleted();
})
.catch((err) => {
toast.error('删除失败:' + err.message);
});
}
}
return (
<div>
<Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
<Dialog
open={showDeleteConfirmModal}
onOpenChange={setShowDeleteConfirmModal}
>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<DialogDescription>
</DialogDescription>
<DialogDescription></DialogDescription>
<DialogFooter>
<Button variant="outline" onClick={() => setShowDeleteConfirmModal(false)}>
<Button
variant="outline"
onClick={() => setShowDeleteConfirmModal(false)}
>
</Button>
<Button variant="destructive" onClick={() => {
deleteBot();
setShowDeleteConfirmModal(false);
}}>
<Button
variant="destructive"
onClick={() => {
deleteBot();
setShowDeleteConfirmModal(false);
}}
>
</Button>
</DialogFooter>
@@ -328,7 +343,10 @@ export default function BotForm({
</Dialog>
<Form {...form}>
<form onSubmit={form.handleSubmit(onDynamicFormSubmit)} className="space-y-8">
<form
onSubmit={form.handleSubmit(onDynamicFormSubmit)}
className="space-y-8"
>
<div className="space-y-4">
{/* 是否启用 & 绑定流水线 仅在编辑模式 */}
{initBotId && (
@@ -340,7 +358,10 @@ export default function BotForm({
<FormItem className="flex flex-col justify-start gap-[0.8rem] h-[3.8rem]">
<FormLabel></FormLabel>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
@@ -353,10 +374,7 @@ export default function BotForm({
<FormItem className="flex flex-col justify-start gap-[0.8rem] h-[3.8rem]">
<FormLabel>线</FormLabel>
<FormControl>
<Select
onValueChange={field.onChange}
{...field}
>
<Select onValueChange={field.onChange} {...field}>
<SelectTrigger>
<SelectValue placeholder="选择流水线" />
</SelectTrigger>
@@ -375,7 +393,6 @@ export default function BotForm({
)}
/>
</div>
)}
<FormField
@@ -383,7 +400,9 @@ export default function BotForm({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -396,7 +415,9 @@ export default function BotForm({
name="description"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -410,7 +431,9 @@ export default function BotForm({
name="adapter"
render={({ field }) => (
<FormItem>
<FormLabel>/<span className="text-red-500">*</span></FormLabel>
<FormLabel>
/<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<div className="relative">
<Select
@@ -449,7 +472,11 @@ export default function BotForm({
/>
<div className="flex flex-col gap-1">
<div className="font-medium">
{adapterNameList.find(item => item.value === form.watch('adapter'))?.label}
{
adapterNameList.find(
(item) => item.value === form.watch('adapter'),
)?.label
}
</div>
<div className="text-sm text-gray-500">
{adapterDescriptionList[form.watch('adapter')]}
@@ -470,25 +497,40 @@ export default function BotForm({
/>
</div>
)}
</div>
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
<div className="flex justify-end gap-2">
{!initBotId && (
<Button type="submit" onClick={form.handleSubmit(onDynamicFormSubmit)}></Button>
<Button
type="submit"
onClick={form.handleSubmit(onDynamicFormSubmit)}
>
</Button>
)}
{initBotId && (
<>
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
<Button
type="button"
variant="destructive"
onClick={() => setShowDeleteConfirmModal(true)}
>
</Button>
<Button type="button" onClick={form.handleSubmit(onDynamicFormSubmit)}>
<Button
type="button"
onClick={form.handleSubmit(onDynamicFormSubmit)}
>
</Button>
</>
)}
<Button type="button" variant="outline" onClick={() => onFormCancel()}>
<Button
type="button"
variant="outline"
onClick={() => onFormCancel()}
>
</Button>
</div>

View File

@@ -6,4 +6,4 @@ export interface IChooseAdapterEntity {
export interface IPipelineEntity {
label: string;
value: string;
}
}

View File

@@ -2,8 +2,6 @@
import { useEffect, useState } from 'react';
import styles from './botConfig.module.css';
import EmptyAndCreateComponent from '@/app/home/components/empty-and-create-component/EmptyAndCreateComponent';
import { useRouter } from 'next/navigation';
import { BotCardVO } from '@/app/home/bots/components/bot-card/BotCardVO';
import BotForm from '@/app/home/bots/components/bot-form/BotForm';
import BotCard from '@/app/home/bots/components/bot-card/BotCard';
@@ -13,12 +11,10 @@ import { Bot, Adapter } from '@/app/infra/entities/api';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { toast } from "sonner";
} from '@/components/ui/dialog';
import { toast } from 'sonner';
export default function BotConfigPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [botList, setBotList] = useState<BotCardVO[]>([]);
@@ -30,7 +26,6 @@ export default function BotConfigPage() {
}, []);
async function getBotList() {
const adapterListResp = await httpClient.getAdapters();
const adapterList = adapterListResp.adapters.map((adapter: Adapter) => {
return {
@@ -48,7 +43,9 @@ export default function BotConfigPage() {
iconURL: httpClient.getAdapterIconURL(bot.adapter),
name: bot.name,
description: bot.description,
adapterLabel: adapterList.find((item) => item.value === bot.adapter)?.label || bot.adapter.substring(0, 10),
adapterLabel:
adapterList.find((item) => item.value === bot.adapter)?.label ||
bot.adapter.substring(0, 10),
usePipelineName: bot.use_pipeline_name || '',
});
});
@@ -56,7 +53,7 @@ export default function BotConfigPage() {
})
.catch((err) => {
console.error('get bot list error', err);
toast.error("获取机器人列表失败:" + err.message);
toast.error('获取机器人列表失败:' + err.message);
})
.finally(() => {
// setIsLoading(false);
@@ -82,11 +79,12 @@ export default function BotConfigPage() {
return (
<div className={styles.configPageContainer}>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle>{isEditForm ? '编辑机器人' : '创建机器人'}</DialogTitle>
<DialogTitle>
{isEditForm ? '编辑机器人' : '创建机器人'}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6">
<BotForm
@@ -106,27 +104,26 @@ export default function BotConfigPage() {
</Dialog>
{/* 注意其余的返回内容需要保持在Spin组件外部 */}
<div className={`${styles.botListContainer}`}>
<CreateCardComponent
width={'24rem'}
height={'10rem'}
plusSize={'90px'}
onClick={handleCreateBotClick}
/>
{botList.map((cardVO) => {
return (
<div
key={cardVO.id}
onClick={() => {
selectBot(cardVO);
}}
>
<BotCard botCardVO={cardVO} />
</div>
);
})}
</div>
<div className={`${styles.botListContainer}`}>
<CreateCardComponent
width={'24rem'}
height={'10rem'}
plusSize={'90px'}
onClick={handleCreateBotClick}
/>
{botList.map((cardVO) => {
return (
<div
key={cardVO.id}
onClick={() => {
selectBot(cardVO);
}}
>
<BotCard botCardVO={cardVO} />
</div>
);
})}
</div>
</div>
);
}

View File

@@ -1,7 +1,7 @@
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
Form,
FormControl,
@@ -9,7 +9,7 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
} from '@/components/ui/form';
import DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent';
import { useEffect } from 'react';
@@ -20,53 +20,62 @@ export default function DynamicFormComponent({
}: {
itemConfigList: IDynamicFormItemSchema[];
onSubmit?: (val: object) => unknown;
initialValues?: Record<string, any>;
initialValues?: Record<string, object>;
}) {
// 根据 itemConfigList 动态生成 zod schema
const formSchema = z.object(
itemConfigList.reduce((acc, item) => {
let fieldSchema;
switch (item.type) {
case 'integer':
fieldSchema = z.number();
break;
case 'float':
fieldSchema = z.number();
break;
case 'boolean':
fieldSchema = z.boolean();
break;
case 'string':
fieldSchema = z.string();
break;
case 'array[string]':
fieldSchema = z.array(z.string());
break;
case 'select':
fieldSchema = z.string();
break;
case 'llm-model-selector':
fieldSchema = z.string();
break;
case 'prompt-editor':
fieldSchema = z.array(z.object({
content: z.string(),
role: z.string(),
}));
break;
default:
fieldSchema = z.string();
}
itemConfigList.reduce(
(acc, item) => {
let fieldSchema;
switch (item.type) {
case 'integer':
fieldSchema = z.number();
break;
case 'float':
fieldSchema = z.number();
break;
case 'boolean':
fieldSchema = z.boolean();
break;
case 'string':
fieldSchema = z.string();
break;
case 'array[string]':
fieldSchema = z.array(z.string());
break;
case 'select':
fieldSchema = z.string();
break;
case 'llm-model-selector':
fieldSchema = z.string();
break;
case 'prompt-editor':
fieldSchema = z.array(
z.object({
content: z.string(),
role: z.string(),
}),
);
break;
default:
fieldSchema = z.string();
}
if (item.required && (fieldSchema instanceof z.ZodString || fieldSchema instanceof z.ZodArray)) {
fieldSchema = fieldSchema.min(1, { message: '此字段为必填项' });
}
if (
item.required &&
(fieldSchema instanceof z.ZodString ||
fieldSchema instanceof z.ZodArray)
) {
fieldSchema = fieldSchema.min(1, { message: '此字段为必填项' });
}
return {
...acc,
[item.name]: fieldSchema,
};
}, {} as Record<string, z.ZodTypeAny>)
return {
...acc,
[item.name]: fieldSchema,
};
},
{} as Record<string, z.ZodTypeAny>,
),
);
type FormValues = z.infer<typeof formSchema>;
@@ -88,11 +97,14 @@ export default function DynamicFormComponent({
console.log('initialValues', initialValues);
if (initialValues) {
// 合并默认值和初始值
const mergedValues = itemConfigList.reduce((acc, item) => {
acc[item.name] = initialValues[item.name] ?? item.default;
return acc;
}, {} as Record<string, any>);
const mergedValues = itemConfigList.reduce(
(acc, item) => {
acc[item.name] = initialValues[item.name] ?? item.default;
return acc;
},
{} as Record<string, object>,
);
Object.entries(mergedValues).forEach(([key, value]) => {
form.setValue(key as keyof FormValues, value);
});
@@ -101,14 +113,17 @@ export default function DynamicFormComponent({
// 监听表单值变化
useEffect(() => {
const subscription = form.watch((value) => {
const subscription = form.watch(() => {
// 获取完整的表单值,确保包含所有默认值
const formValues = form.getValues();
console.log('formValues', formValues);
const finalValues = itemConfigList.reduce((acc, item) => {
acc[item.name] = formValues[item.name] ?? item.default;
return acc;
}, {} as Record<string, any>);
const finalValues = itemConfigList.reduce(
(acc, item) => {
acc[item.name] = formValues[item.name] ?? item.default;
return acc;
},
{} as Record<string, object>,
);
console.log('finalValues', finalValues);
onSubmit?.(finalValues);
});
@@ -125,12 +140,12 @@ export default function DynamicFormComponent({
name={config.name as keyof FormValues}
render={({ field }) => (
<FormItem>
<FormLabel>{config.label.zh_CN} {config.required && <span className="text-red-500">*</span>}</FormLabel>
<FormLabel>
{config.label.zh_CN}{' '}
{config.required && <span className="text-red-500">*</span>}
</FormLabel>
<FormControl>
<DynamicFormItemComponent
config={config}
field={field}
/>
<DynamicFormItemComponent config={config} field={field} />
</FormControl>
{config.description && (
<p className="text-sm text-muted-foreground">

View File

@@ -2,32 +2,43 @@ import {
DynamicFormItemType,
IDynamicFormItemSchema,
} from '@/app/infra/entities/form/dynamic';
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { ControllerRenderProps } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
import { httpClient } from "@/app/infra/http/HttpClient";
import { LLMModel } from "@/app/infra/entities/api";
import { toast } from "sonner";
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { ControllerRenderProps } from 'react-hook-form';
import { Button } from '@/components/ui/button';
import { useEffect, useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { LLMModel } from '@/app/infra/entities/api';
import { toast } from 'sonner';
export default function DynamicFormItemComponent({
config,
field,
}: {
config: IDynamicFormItemSchema;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
field: ControllerRenderProps<any, any>;
}) {
const [llmModels, setLlmModels] = useState<LLMModel[]>([]);
useEffect(() => {
if (config.type === DynamicFormItemType.LLM_MODEL_SELECTOR) {
httpClient.getProviderLLMModels().then((resp) => {
setLlmModels(resp.models);
}).catch((err) => {
toast.error("获取 LLM 模型列表失败:" + err.message);
});
httpClient
.getProviderLLMModels()
.then((resp) => {
setLlmModels(resp.models);
})
.catch((err) => {
toast.error('获取 LLM 模型列表失败:' + err.message);
});
}
}, [config.type]);
@@ -46,12 +57,7 @@ export default function DynamicFormItemComponent({
return <Input {...field} />;
case DynamicFormItemType.BOOLEAN:
return (
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
);
return <Switch checked={field.value} onCheckedChange={field.onChange} />;
case DynamicFormItemType.STRING_ARRAY:
return (
@@ -67,15 +73,22 @@ export default function DynamicFormItemComponent({
field.onChange(newValue);
}}
/>
<button
<button
type="button"
className="p-2 hover:bg-gray-100 rounded"
onClick={() => {
const newValue = field.value.filter((_: string, i: number) => i !== index);
const newValue = field.value.filter(
(_: string, i: number) => i !== index,
);
field.onChange(newValue);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5 text-red-500">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-5 h-5 text-red-500"
>
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
</svg>
</button>
@@ -95,10 +108,7 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.SELECT:
return (
<Select
value={field.value}
onValueChange={field.onChange}
>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="请选择" />
</SelectTrigger>
@@ -116,10 +126,7 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.LLM_MODEL_SELECTOR:
return (
<Select
value={field.value}
onValueChange={field.onChange}
>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="请选择模型" />
</SelectTrigger>
@@ -138,66 +145,78 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.PROMPT_EDITOR:
return (
<div className="space-y-2">
{field.value.map((item: { role: string; content: string }, index: number) => (
<div key={index} className="flex gap-2 items-center">
{/* 角色选择 */}
{index === 0 ? (
<div className="w-[120px] px-3 py-2 border rounded bg-gray-50 text-gray-500">system</div>
) : (
<Select
value={item.role}
onValueChange={(value) => {
{field.value.map(
(item: { role: string; content: string }, index: number) => (
<div key={index} className="flex gap-2 items-center">
{/* 角色选择 */}
{index === 0 ? (
<div className="w-[120px] px-3 py-2 border rounded bg-gray-50 text-gray-500">
system
</div>
) : (
<Select
value={item.role}
onValueChange={(value) => {
const newValue = [...field.value];
newValue[index] = { ...newValue[index], role: value };
field.onChange(newValue);
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="user">user</SelectItem>
<SelectItem value="assistant">assistant</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
)}
{/* 内容输入 */}
<Input
className="w-[300px]"
value={item.content}
onChange={(e) => {
const newValue = [...field.value];
newValue[index] = { ...newValue[index], role: value };
newValue[index] = {
...newValue[index],
content: e.target.value,
};
field.onChange(newValue);
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="user">user</SelectItem>
<SelectItem value="assistant">assistant</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
)}
{/* 内容输入 */}
<Input
className="w-[300px]"
value={item.content}
onChange={(e) => {
const newValue = [...field.value];
newValue[index] = { ...newValue[index], content: e.target.value };
field.onChange(newValue);
}}
/>
{/* 删除按钮,第一轮不显示 */}
{index !== 0 && (
<button
type="button"
className="p-2 hover:bg-gray-100 rounded"
onClick={() => {
const newValue = field.value.filter((_: any, i: number) => i !== index);
field.onChange(newValue);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5 text-red-500">
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
</svg>
</button>
)}
</div>
))}
/>
{/* 删除按钮,第一轮不显示 */}
{index !== 0 && (
<button
type="button"
className="p-2 hover:bg-gray-100 rounded"
onClick={() => {
const newValue = field.value.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_: any, i: number) => i !== index,
);
field.onChange(newValue);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-5 h-5 text-red-500"
>
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
</svg>
</button>
)}
</div>
),
)}
<Button
type="button"
variant="outline"
onClick={() => {
field.onChange([
...field.value,
{ role: 'user', content: '' },
]);
field.onChange([...field.value, { role: 'user', content: '' }]);
}}
>

View File

@@ -1,4 +1,8 @@
import { IDynamicFormItemSchema, DynamicFormItemType, IDynamicFormItemOption } from '@/app/infra/entities/form/dynamic';
import {
IDynamicFormItemSchema,
DynamicFormItemType,
IDynamicFormItemOption,
} from '@/app/infra/entities/form/dynamic';
import { I18nLabel } from '@/app/infra/entities/common';
export class DynamicFormItemConfig implements IDynamicFormItemSchema {
@@ -23,7 +27,6 @@ export class DynamicFormItemConfig implements IDynamicFormItemSchema {
}
}
export function isDynamicFormItemType(
value: string,
): value is DynamicFormItemType {
@@ -36,9 +39,16 @@ export function parseDynamicFormItemType(value: string): DynamicFormItemType {
return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN;
}
export function getDefaultValues(itemConfigList: IDynamicFormItemSchema[]): Record<string, any> {
return itemConfigList.reduce((acc, item) => {
acc[item.name] = item.default;
return acc;
}, {} as Record<string, any>);
export function getDefaultValues(
itemConfigList: IDynamicFormItemSchema[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Record<string, any> {
return itemConfigList.reduce(
(acc, item) => {
acc[item.name] = item.default;
return acc;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{} as Record<string, any>,
);
}

View File

@@ -9,7 +9,6 @@ import {
import { useRouter, usePathname } from 'next/navigation';
import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList';
import langbotIcon from '@/app/assets/langbot-logo.webp';
import { Button } from '@/components/ui/button';
// TODO 侧边导航栏要加动画
export default function HomeSidebar({
@@ -75,7 +74,11 @@ export default function HomeSidebar({
{/* LangBot、ICON区域 */}
<div className={`${styles.langbotIconContainer}`}>
{/* icon */}
<img className={`${styles.langbotIcon}`} src={langbotIcon.src} alt="langbot-icon" />
<img
className={`${styles.langbotIcon}`}
src={langbotIcon.src}
alt="langbot-icon"
/>
{/* 文字 */}
<div className={`${styles.langbotTextContainer}`}>
<div className={`${styles.langbotText}`}>LangBot</div>
@@ -106,23 +109,37 @@ export default function HomeSidebar({
</div>
<div className={`${styles.sidebarBottomContainer}`}>
<SidebarChild
onClick={() => {}}
isSelected={false}
icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 11.9998C2 11.1353 2.1097 10.2964 2.31595 9.49631C3.40622 9.55283 4.48848 9.01015 5.0718 7.99982C5.65467 6.99025 5.58406 5.78271 4.99121 4.86701C6.18354 3.69529 7.66832 2.82022 9.32603 2.36133C9.8222 3.33385 10.8333 3.99982 12 3.99982C13.1667 3.99982 14.1778 3.33385 14.674 2.36133C16.3317 2.82022 17.8165 3.69529 19.0088 4.86701C18.4159 5.78271 18.3453 6.99025 18.9282 7.99982C19.5115 9.01015 20.5938 9.55283 21.6841 9.49631C21.8903 10.2964 22 11.1353 22 11.9998C22 12.8643 21.8903 13.7032 21.6841 14.5033C20.5938 14.4468 19.5115 14.9895 18.9282 15.9998C18.3453 17.0094 18.4159 18.2169 19.0088 19.1326C17.8165 20.3043 16.3317 21.1794 14.674 21.6383C14.1778 20.6658 13.1667 19.9998 12 19.9998C10.8333 19.9998 9.8222 20.6658 9.32603 21.6383C7.66832 21.1794 6.18354 20.3043 4.99121 19.1326C5.58406 18.2169 5.65467 17.0094 5.0718 15.9998C4.48848 14.9895 3.40622 14.4468 2.31595 14.5033C2.1097 13.7032 2 12.8643 2 11.9998ZM6.80385 14.9998C7.43395 16.0912 7.61458 17.3459 7.36818 18.5236C7.77597 18.8138 8.21005 19.0652 8.66489 19.2741C9.56176 18.4712 10.7392 17.9998 12 17.9998C13.2608 17.9998 14.4382 18.4712 15.3351 19.2741C15.7899 19.0652 16.224 18.8138 16.6318 18.5236C16.3854 17.3459 16.566 16.0912 17.1962 14.9998C17.8262 13.9085 18.8225 13.1248 19.9655 12.7493C19.9884 12.5015 20 12.2516 20 11.9998C20 11.7481 19.9884 11.4981 19.9655 11.2504C18.8225 10.8749 17.8262 10.0912 17.1962 8.99982C16.566 7.90845 16.3854 6.65378 16.6318 5.47605C16.224 5.18588 15.7899 4.93447 15.3351 4.72552C14.4382 5.52844 13.2608 5.99982 12 5.99982C10.7392 5.99982 9.56176 5.52844 8.66489 4.72552C8.21005 4.93447 7.77597 5.18588 7.36818 5.47605C7.61458 6.65378 7.43395 7.90845 6.80385 8.99982C6.17376 10.0912 5.17754 10.8749 4.03451 11.2504C4.01157 11.4981 4 11.7481 4 11.9998C4 12.2516 4.01157 12.5015 4.03451 12.7493C5.17754 13.1248 6.17376 13.9085 6.80385 14.9998ZM12 14.9998C10.3431 14.9998 9 13.6567 9 11.9998C9 10.343 10.3431 8.99982 12 8.99982C13.6569 8.99982 15 10.343 15 11.9998C15 13.6567 13.6569 14.9998 12 14.9998ZM12 12.9998C12.5523 12.9998 13 12.5521 13 11.9998C13 11.4475 12.5523 10.9998 12 10.9998C11.4477 10.9998 11 11.4475 11 11.9998C11 12.5521 11.4477 12.9998 12 12.9998Z"></path></svg>}
name="系统设置"
/>
<SidebarChild
onClick={() => {
// open docs.langbot.app
window.open('https://docs.langbot.app', '_blank');
}}
isSelected={false}
icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM13 13.3551V14H11V12.5C11 11.9477 11.4477 11.5 12 11.5C12.8284 11.5 13.5 10.8284 13.5 10C13.5 9.17157 12.8284 8.5 12 8.5C11.2723 8.5 10.6656 9.01823 10.5288 9.70577L8.56731 9.31346C8.88637 7.70919 10.302 6.5 12 6.5C13.933 6.5 15.5 8.067 15.5 10C15.5 11.5855 14.4457 12.9248 13 13.3551Z"></path></svg>}
name="帮助文档"
/>
<SidebarChild
onClick={() => {}}
isSelected={false}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M2 11.9998C2 11.1353 2.1097 10.2964 2.31595 9.49631C3.40622 9.55283 4.48848 9.01015 5.0718 7.99982C5.65467 6.99025 5.58406 5.78271 4.99121 4.86701C6.18354 3.69529 7.66832 2.82022 9.32603 2.36133C9.8222 3.33385 10.8333 3.99982 12 3.99982C13.1667 3.99982 14.1778 3.33385 14.674 2.36133C16.3317 2.82022 17.8165 3.69529 19.0088 4.86701C18.4159 5.78271 18.3453 6.99025 18.9282 7.99982C19.5115 9.01015 20.5938 9.55283 21.6841 9.49631C21.8903 10.2964 22 11.1353 22 11.9998C22 12.8643 21.8903 13.7032 21.6841 14.5033C20.5938 14.4468 19.5115 14.9895 18.9282 15.9998C18.3453 17.0094 18.4159 18.2169 19.0088 19.1326C17.8165 20.3043 16.3317 21.1794 14.674 21.6383C14.1778 20.6658 13.1667 19.9998 12 19.9998C10.8333 19.9998 9.8222 20.6658 9.32603 21.6383C7.66832 21.1794 6.18354 20.3043 4.99121 19.1326C5.58406 18.2169 5.65467 17.0094 5.0718 15.9998C4.48848 14.9895 3.40622 14.4468 2.31595 14.5033C2.1097 13.7032 2 12.8643 2 11.9998ZM6.80385 14.9998C7.43395 16.0912 7.61458 17.3459 7.36818 18.5236C7.77597 18.8138 8.21005 19.0652 8.66489 19.2741C9.56176 18.4712 10.7392 17.9998 12 17.9998C13.2608 17.9998 14.4382 18.4712 15.3351 19.2741C15.7899 19.0652 16.224 18.8138 16.6318 18.5236C16.3854 17.3459 16.566 16.0912 17.1962 14.9998C17.8262 13.9085 18.8225 13.1248 19.9655 12.7493C19.9884 12.5015 20 12.2516 20 11.9998C20 11.7481 19.9884 11.4981 19.9655 11.2504C18.8225 10.8749 17.8262 10.0912 17.1962 8.99982C16.566 7.90845 16.3854 6.65378 16.6318 5.47605C16.224 5.18588 15.7899 4.93447 15.3351 4.72552C14.4382 5.52844 13.2608 5.99982 12 5.99982C10.7392 5.99982 9.56176 5.52844 8.66489 4.72552C8.21005 4.93447 7.77597 5.18588 7.36818 5.47605C7.61458 6.65378 7.43395 7.90845 6.80385 8.99982C6.17376 10.0912 5.17754 10.8749 4.03451 11.2504C4.01157 11.4981 4 11.7481 4 11.9998C4 12.2516 4.01157 12.5015 4.03451 12.7493C5.17754 13.1248 6.17376 13.9085 6.80385 14.9998ZM12 14.9998C10.3431 14.9998 9 13.6567 9 11.9998C9 10.343 10.3431 8.99982 12 8.99982C13.6569 8.99982 15 10.343 15 11.9998C15 13.6567 13.6569 14.9998 12 14.9998ZM12 12.9998C12.5523 12.9998 13 12.5521 13 11.9998C13 11.4475 12.5523 10.9998 12 10.9998C11.4477 10.9998 11 11.4475 11 11.9998C11 12.5521 11.4477 12.9998 12 12.9998Z"></path>
</svg>
}
name="系统设置"
/>
<SidebarChild
onClick={() => {
// open docs.langbot.app
window.open('https://docs.langbot.app', '_blank');
}}
isSelected={false}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM13 13.3551V14H11V12.5C11 11.9477 11.4477 11.5 12 11.5C12.8284 11.5 13.5 10.8284 13.5 10C13.5 9.17157 12.8284 8.5 12 8.5C11.2723 8.5 10.6656 9.01823 10.5288 9.70577L8.56731 9.31346C8.88637 7.70919 10.302 6.5 12 6.5C13.933 6.5 15.5 8.067 15.5 10C15.5 11.5855 14.4457 12.9248 13 13.3551Z"></path>
</svg>
}
name="帮助文档"
/>
</div>
</div>
);

View File

@@ -37,12 +37,8 @@ export function SidebarChild({
className={`${styles.sidebarChildContainer} ${isSelected ? styles.sidebarSelected : styles.sidebarUnselected}`}
onClick={onClick}
>
<div className={`${styles.sidebarChildIcon}`}>
{icon}
</div>
<span className={`${styles.sidebarChildName}`}>
{name}
</span>
<div className={`${styles.sidebarChildIcon}`}>{icon}</div>
<span className={`${styles.sidebarChildName}`}>{name}</span>
</div>
);
}

View File

@@ -5,25 +5,61 @@ export const sidebarConfigList = [
new SidebarChildVO({
id: 'models',
name: '模型配置',
icon: <svg className={`${styles.sidebarChildIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.6144 17.7956C10.277 18.5682 9.20776 18.5682 8.8704 17.7956L7.99275 15.7854C7.21171 13.9966 5.80589 12.5726 4.0523 11.7942L1.63658 10.7219C.868536 10.381.868537 9.26368 1.63658 8.92276L3.97685 7.88394C5.77553 7.08552 7.20657 5.60881 7.97427 3.75892L8.8633 1.61673C9.19319.821767 10.2916.821765 10.6215 1.61673L11.5105 3.75894C12.2782 5.60881 13.7092 7.08552 15.5079 7.88394L17.8482 8.92276C18.6162 9.26368 18.6162 10.381 17.8482 10.7219L15.4325 11.7942C13.6789 12.5726 12.2731 13.9966 11.492 15.7854L10.6144 17.7956ZM4.53956 9.82234C6.8254 10.837 8.68402 12.5048 9.74238 14.7996 10.8008 12.5048 12.6594 10.837 14.9452 9.82234 12.6321 8.79557 10.7676 7.04647 9.74239 4.71088 8.71719 7.04648 6.85267 8.79557 4.53956 9.82234ZM19.4014 22.6899 19.6482 22.1242C20.0882 21.1156 20.8807 20.3125 21.8695 19.8732L22.6299 19.5353C23.0412 19.3526 23.0412 18.7549 22.6299 18.5722L21.9121 18.2532C20.8978 17.8026 20.0911 16.9698 19.6586 15.9269L19.4052 15.3156C19.2285 14.8896 18.6395 14.8896 18.4628 15.3156L18.2094 15.9269C17.777 16.9698 16.9703 17.8026 15.956 18.2532L15.2381 18.5722C14.8269 18.7549 14.8269 19.3526 15.2381 19.5353L15.9985 19.8732C16.9874 20.3125 17.7798 21.1156 18.2198 22.1242L18.4667 22.6899C18.6473 23.104 19.2207 23.104 19.4014 22.6899ZM18.3745 19.0469 18.937 18.4883 19.4878 19.0469 18.937 19.5898 18.3745 19.0469Z"></path></svg>,
icon: (
<svg
className={`${styles.sidebarChildIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M10.6144 17.7956C10.277 18.5682 9.20776 18.5682 8.8704 17.7956L7.99275 15.7854C7.21171 13.9966 5.80589 12.5726 4.0523 11.7942L1.63658 10.7219C.868536 10.381.868537 9.26368 1.63658 8.92276L3.97685 7.88394C5.77553 7.08552 7.20657 5.60881 7.97427 3.75892L8.8633 1.61673C9.19319.821767 10.2916.821765 10.6215 1.61673L11.5105 3.75894C12.2782 5.60881 13.7092 7.08552 15.5079 7.88394L17.8482 8.92276C18.6162 9.26368 18.6162 10.381 17.8482 10.7219L15.4325 11.7942C13.6789 12.5726 12.2731 13.9966 11.492 15.7854L10.6144 17.7956ZM4.53956 9.82234C6.8254 10.837 8.68402 12.5048 9.74238 14.7996 10.8008 12.5048 12.6594 10.837 14.9452 9.82234 12.6321 8.79557 10.7676 7.04647 9.74239 4.71088 8.71719 7.04648 6.85267 8.79557 4.53956 9.82234ZM19.4014 22.6899 19.6482 22.1242C20.0882 21.1156 20.8807 20.3125 21.8695 19.8732L22.6299 19.5353C23.0412 19.3526 23.0412 18.7549 22.6299 18.5722L21.9121 18.2532C20.8978 17.8026 20.0911 16.9698 19.6586 15.9269L19.4052 15.3156C19.2285 14.8896 18.6395 14.8896 18.4628 15.3156L18.2094 15.9269C17.777 16.9698 16.9703 17.8026 15.956 18.2532L15.2381 18.5722C14.8269 18.7549 14.8269 19.3526 15.2381 19.5353L15.9985 19.8732C16.9874 20.3125 17.7798 21.1156 18.2198 22.1242L18.4667 22.6899C18.6473 23.104 19.2207 23.104 19.4014 22.6899ZM18.3745 19.0469 18.937 18.4883 19.4878 19.0469 18.937 19.5898 18.3745 19.0469Z"></path>
</svg>
),
route: '/home/models',
}),
new SidebarChildVO({
id: 'bots',
name: '机器人',
icon: <svg className={`${styles.sidebarChildIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 2C13.5 2.44425 13.3069 2.84339 13 3.11805V5H18C19.6569 5 21 6.34315 21 8V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V8C3 6.34315 4.34315 5 6 5H11V3.11805C10.6931 2.84339 10.5 2.44425 10.5 2C10.5 1.17157 11.1716 0.5 12 0.5C12.8284 0.5 13.5 1.17157 13.5 2ZM6 7C5.44772 7 5 7.44772 5 8V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V8C19 7.44772 18.5523 7 18 7H13H11H6ZM2 10H0V16H2V10ZM22 10H24V16H22V10ZM9 14.5C9.82843 14.5 10.5 13.8284 10.5 13C10.5 12.1716 9.82843 11.5 9 11.5C8.17157 11.5 7.5 12.1716 7.5 13C7.5 13.8284 8.17157 14.5 9 14.5ZM15 14.5C15.8284 14.5 16.5 13.8284 16.5 13C16.5 12.1716 15.8284 11.5 15 11.5C14.1716 11.5 13.5 12.1716 13.5 13C13.5 13.8284 14.1716 14.5 15 14.5Z"></path></svg>,
icon: (
<svg
className={`${styles.sidebarChildIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M13.5 2C13.5 2.44425 13.3069 2.84339 13 3.11805V5H18C19.6569 5 21 6.34315 21 8V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V8C3 6.34315 4.34315 5 6 5H11V3.11805C10.6931 2.84339 10.5 2.44425 10.5 2C10.5 1.17157 11.1716 0.5 12 0.5C12.8284 0.5 13.5 1.17157 13.5 2ZM6 7C5.44772 7 5 7.44772 5 8V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V8C19 7.44772 18.5523 7 18 7H13H11H6ZM2 10H0V16H2V10ZM22 10H24V16H22V10ZM9 14.5C9.82843 14.5 10.5 13.8284 10.5 13C10.5 12.1716 9.82843 11.5 9 11.5C8.17157 11.5 7.5 12.1716 7.5 13C7.5 13.8284 8.17157 14.5 9 14.5ZM15 14.5C15.8284 14.5 16.5 13.8284 16.5 13C16.5 12.1716 15.8284 11.5 15 11.5C14.1716 11.5 13.5 12.1716 13.5 13C13.5 13.8284 14.1716 14.5 15 14.5Z"></path>
</svg>
),
route: '/home/bots',
}),
new SidebarChildVO({
id: 'pipelines',
name: '流水线',
icon: <svg className={`${styles.sidebarChildIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6 21.5C4.067 21.5 2.5 19.933 2.5 18C2.5 16.067 4.067 14.5 6 14.5C7.5852 14.5 8.92427 15.5539 9.35481 16.9992L15 16.9994V15L17 14.9994V9.24339L14.757 6.99938H9V9.00003H3V3.00003H9V4.99939H14.757L18 1.75739L22.2426 6.00003L19 9.24139V14.9994L21 15V21H15V18.9994L9.35499 19.0003C8.92464 20.4459 7.58543 21.5 6 21.5ZM6 16.5C5.17157 16.5 4.5 17.1716 4.5 18C4.5 18.8285 5.17157 19.5 6 19.5C6.82843 19.5 7.5 18.8285 7.5 18C7.5 17.1716 6.82843 16.5 6 16.5ZM19 17H17V19H19V17ZM18 4.58581L16.5858 6.00003L18 7.41424L19.4142 6.00003L18 4.58581ZM7 5.00003H5V7.00003H7V5.00003Z"></path></svg>,
icon: (
<svg
className={`${styles.sidebarChildIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M6 21.5C4.067 21.5 2.5 19.933 2.5 18C2.5 16.067 4.067 14.5 6 14.5C7.5852 14.5 8.92427 15.5539 9.35481 16.9992L15 16.9994V15L17 14.9994V9.24339L14.757 6.99938H9V9.00003H3V3.00003H9V4.99939H14.757L18 1.75739L22.2426 6.00003L19 9.24139V14.9994L21 15V21H15V18.9994L9.35499 19.0003C8.92464 20.4459 7.58543 21.5 6 21.5ZM6 16.5C5.17157 16.5 4.5 17.1716 4.5 18C4.5 18.8285 5.17157 19.5 6 19.5C6.82843 19.5 7.5 18.8285 7.5 18C7.5 17.1716 6.82843 16.5 6 16.5ZM19 17H17V19H19V17ZM18 4.58581L16.5858 6.00003L18 7.41424L19.4142 6.00003L18 4.58581ZM7 5.00003H5V7.00003H7V5.00003Z"></path>
</svg>
),
route: '/home/pipelines',
}),
new SidebarChildVO({
id: 'plugins',
name: '插件管理',
icon: <svg className={`${styles.sidebarChildIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M7 5C7 2.79086 8.79086 1 11 1C13.2091 1 15 2.79086 15 5H18C18.5523 5 19 5.44772 19 6V9C21.2091 9 23 10.7909 23 13C23 15.2091 21.2091 17 19 17V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H7ZM11 3C9.89543 3 9 3.89543 9 5C9 5.23554 9.0403 5.45952 9.11355 5.66675C9.22172 5.97282 9.17461 6.31235 8.98718 6.57739C8.79974 6.84243 8.49532 7 8.17071 7H5V19H17V15.8293C17 15.5047 17.1576 15.2003 17.4226 15.0128C17.6877 14.8254 18.0272 14.7783 18.3332 14.8865C18.5405 14.9597 18.7645 15 19 15C20.1046 15 21 14.1046 21 13C21 11.8954 20.1046 11 19 11C18.7645 11 18.5405 11.0403 18.3332 11.1135C18.0272 11.2217 17.6877 11.1746 17.4226 10.9872C17.1576 10.7997 17 10.4953 17 10.1707V7H13.8293C13.5047 7 13.2003 6.84243 13.0128 6.57739C12.8254 6.31235 12.7783 5.97282 12.8865 5.66675C12.9597 5.45952 13 5.23555 13 5C13 3.89543 12.1046 3 11 3Z"></path></svg>,
icon: (
<svg
className={`${styles.sidebarChildIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M7 5C7 2.79086 8.79086 1 11 1C13.2091 1 15 2.79086 15 5H18C18.5523 5 19 5.44772 19 6V9C21.2091 9 23 10.7909 23 13C23 15.2091 21.2091 17 19 17V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H7ZM11 3C9.89543 3 9 3.89543 9 5C9 5.23554 9.0403 5.45952 9.11355 5.66675C9.22172 5.97282 9.17461 6.31235 8.98718 6.57739C8.79974 6.84243 8.49532 7 8.17071 7H5V19H17V15.8293C17 15.5047 17.1576 15.2003 17.4226 15.0128C17.6877 14.8254 18.0272 14.7783 18.3332 14.8865C18.5405 14.9597 18.7645 15 19 15C20.1046 15 21 14.1046 21 13C21 11.8954 20.1046 11 19 11C18.7645 11 18.5405 11.0403 18.3332 11.1135C18.0272 11.2217 17.6877 11.1746 17.4226 10.9872C17.1576 10.7997 17 10.4953 17 10.1707V7H13.8293C13.5047 7 13.2003 6.84243 13.0128 6.57739C12.8254 6.31235 12.7783 5.97282 12.8865 5.66675C12.9597 5.45952 13 5.23555 13 5C13 3.89543 12.1046 3 11 3Z"></path>
</svg>
),
route: '/home/plugins',
}),
];

View File

@@ -17,20 +17,16 @@ export default function HomeLayout({
};
return (
<div className={styles.homeLayoutContainer}>
<aside className={styles.sidebar}>
<HomeSidebar onSelectedChangeAction={onSelectedChangeAction} />
</aside>
<div className={styles.main}>
<HomeTitleBar title={title} />
<main className={styles.mainContent}>
{children}
</main>
</div>
<main className={styles.mainContent}>{children}</main>
</div>
</div>
);
}

View File

@@ -1,19 +1,35 @@
import styles from './LLMCard.module.css';
import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO';
import { Button } from '@/components/ui/button';
function checkAbilityBadges(abilities: string[]) {
const abilityBadges = {
'vision': <div key="vision" className={`${styles.abilityBadge}`}>
<svg className={`${styles.abilityIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM12 7C14.7614 7 17 9.23858 17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 11.4872 7.07719 10.9925 7.22057 10.5268C7.61175 11.3954 8.48527 12 9.5 12C10.8807 12 12 10.8807 12 9.5C12 8.48527 11.3954 7.61175 10.5269 7.21995C10.9925 7.07719 11.4872 7 12 7Z"></path></svg>
<span className={`${styles.abilityLabel}`}></span>
</div>,
'func_call': <div key="func_call" className={`${styles.abilityBadge}`}>
<svg className={`${styles.abilityIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path></svg>
<span className={`${styles.abilityLabel}`}></span>
</div>,
}
vision: (
<div key="vision" className={`${styles.abilityBadge}`}>
<svg
className={`${styles.abilityIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM12 7C14.7614 7 17 9.23858 17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 11.4872 7.07719 10.9925 7.22057 10.5268C7.61175 11.3954 8.48527 12 9.5 12C10.8807 12 12 10.8807 12 9.5C12 8.48527 11.3954 7.61175 10.5269 7.21995C10.9925 7.07719 11.4872 7 12 7Z"></path>
</svg>
<span className={`${styles.abilityLabel}`}></span>
</div>
),
func_call: (
<div key="func_call" className={`${styles.abilityBadge}`}>
<svg
className={`${styles.abilityIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path>
</svg>
<span className={`${styles.abilityLabel}`}></span>
</div>
),
};
return abilities.map((ability) => {
return abilityBadges[ability as keyof typeof abilityBadges];
@@ -24,7 +40,11 @@ export default function LLMCard({ cardVO }: { cardVO: LLMCardVO }) {
return (
<div className={`${styles.cardContainer}`}>
<div className={`${styles.iconBasicInfoContainer}`}>
<img className={`${styles.iconImage}`} src={cardVO.iconURL} alt="icon" />
<img
className={`${styles.iconImage}`}
src={cardVO.iconURL}
alt="icon"
/>
<div className={`${styles.basicInfoContainer}`}>
{/* 名称 */}
@@ -33,24 +53,39 @@ export default function LLMCard({ cardVO }: { cardVO: LLMCardVO }) {
</div>
{/* 厂商 */}
<div className={`${styles.providerContainer}`}>
<svg className={`${styles.providerIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="36" height="36" fill="currentColor"><path d="M21 13.2422V20H22V22H2V20H3V13.2422C1.79401 12.435 1 11.0602 1 9.5C1 8.67286 1.22443 7.87621 1.63322 7.19746L4.3453 2.5C4.52393 2.1906 4.85406 2 5.21132 2H18.7887C19.1459 2 19.4761 2.1906 19.6547 2.5L22.3575 7.18172C22.7756 7.87621 23 8.67286 23 9.5C23 11.0602 22.206 12.435 21 13.2422ZM19 13.9725C18.8358 13.9907 18.669 14 18.5 14C17.2409 14 16.0789 13.478 15.25 12.6132C14.4211 13.478 13.2591 14 12 14C10.7409 14 9.5789 13.478 8.75 12.6132C7.9211 13.478 6.75911 14 5.5 14C5.331 14 5.16417 13.9907 5 13.9725V20H19V13.9725ZM5.78865 4L3.35598 8.21321C3.12409 8.59843 3 9.0389 3 9.5C3 10.8807 4.11929 12 5.5 12C6.53096 12 7.44467 11.3703 7.82179 10.4295C8.1574 9.59223 9.3426 9.59223 9.67821 10.4295C10.0553 11.3703 10.969 12 12 12C13.031 12 13.9447 11.3703 14.3218 10.4295C14.6574 9.59223 15.8426 9.59223 16.1782 10.4295C16.5553 11.3703 17.469 12 18.5 12C19.8807 12 21 10.8807 21 9.5C21 9.0389 20.8759 8.59843 20.6347 8.19746L18.2113 4H5.78865Z"></path></svg>
<svg
className={`${styles.providerIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="36"
height="36"
fill="currentColor"
>
<path d="M21 13.2422V20H22V22H2V20H3V13.2422C1.79401 12.435 1 11.0602 1 9.5C1 8.67286 1.22443 7.87621 1.63322 7.19746L4.3453 2.5C4.52393 2.1906 4.85406 2 5.21132 2H18.7887C19.1459 2 19.4761 2.1906 19.6547 2.5L22.3575 7.18172C22.7756 7.87621 23 8.67286 23 9.5C23 11.0602 22.206 12.435 21 13.2422ZM19 13.9725C18.8358 13.9907 18.669 14 18.5 14C17.2409 14 16.0789 13.478 15.25 12.6132C14.4211 13.478 13.2591 14 12 14C10.7409 14 9.5789 13.478 8.75 12.6132C7.9211 13.478 6.75911 14 5.5 14C5.331 14 5.16417 13.9907 5 13.9725V20H19V13.9725ZM5.78865 4L3.35598 8.21321C3.12409 8.59843 3 9.0389 3 9.5C3 10.8807 4.11929 12 5.5 12C6.53096 12 7.44467 11.3703 7.82179 10.4295C8.1574 9.59223 9.3426 9.59223 9.67821 10.4295C10.0553 11.3703 10.969 12 12 12C13.031 12 13.9447 11.3703 14.3218 10.4295C14.6574 9.59223 15.8426 9.59223 16.1782 10.4295C16.5553 11.3703 17.469 12 18.5 12C19.8807 12 21 10.8807 21 9.5C21 9.0389 20.8759 8.59843 20.6347 8.19746L18.2113 4H5.78865Z"></path>
</svg>
<span className={`${styles.providerLabel}`}>
{cardVO.providerLabel}
</span>
</div>
{/* baseURL */}
<div className={`${styles.baseURLContainer}`}>
<svg className={`${styles.baseURLIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="36" height="36" fill="rgba(98,98,98,1)"><path d="M13.0607 8.11097L14.4749 9.52518C17.2086 12.2589 17.2086 16.691 14.4749 19.4247L14.1214 19.7782C11.3877 22.5119 6.95555 22.5119 4.22188 19.7782C1.48821 17.0446 1.48821 12.6124 4.22188 9.87874L5.6361 11.293C3.68348 13.2456 3.68348 16.4114 5.6361 18.364C7.58872 20.3166 10.7545 20.3166 12.7072 18.364L13.0607 18.0105C15.0133 16.0578 15.0133 12.892 13.0607 10.9394L11.6465 9.52518L13.0607 8.11097ZM19.7782 14.1214L18.364 12.7072C20.3166 10.7545 20.3166 7.58872 18.364 5.6361C16.4114 3.68348 13.2456 3.68348 11.293 5.6361L10.9394 5.98965C8.98678 7.94227 8.98678 11.1081 10.9394 13.0607L12.3536 14.4749L10.9394 15.8891L9.52518 14.4749C6.79151 11.7413 6.79151 7.30911 9.52518 4.57544L9.87874 4.22188C12.6124 1.48821 17.0446 1.48821 19.7782 4.22188C22.5119 6.95555 22.5119 11.3877 19.7782 14.1214Z"></path></svg>
<span className={`${styles.baseURLText}`}>
{cardVO.baseURL}
</span>
<svg
className={`${styles.baseURLIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="36"
height="36"
fill="rgba(98,98,98,1)"
>
<path d="M13.0607 8.11097L14.4749 9.52518C17.2086 12.2589 17.2086 16.691 14.4749 19.4247L14.1214 19.7782C11.3877 22.5119 6.95555 22.5119 4.22188 19.7782C1.48821 17.0446 1.48821 12.6124 4.22188 9.87874L5.6361 11.293C3.68348 13.2456 3.68348 16.4114 5.6361 18.364C7.58872 20.3166 10.7545 20.3166 12.7072 18.364L13.0607 18.0105C15.0133 16.0578 15.0133 12.892 13.0607 10.9394L11.6465 9.52518L13.0607 8.11097ZM19.7782 14.1214L18.364 12.7072C20.3166 10.7545 20.3166 7.58872 18.364 5.6361C16.4114 3.68348 13.2456 3.68348 11.293 5.6361L10.9394 5.98965C8.98678 7.94227 8.98678 11.1081 10.9394 13.0607L12.3536 14.4749L10.9394 15.8891L9.52518 14.4749C6.79151 11.7413 6.79151 7.30911 9.52518 4.57544L9.87874 4.22188C12.6124 1.48821 17.0446 1.48821 19.7782 4.22188C22.5119 6.95555 22.5119 11.3877 19.7782 14.1214Z"></path>
</svg>
<span className={`${styles.baseURLText}`}>{cardVO.baseURL}</span>
</div>
{/* 能力 */}
<div className={`${styles.abilitiesContainer}`}>
{checkAbilityBadges(cardVO.abilities)}
</div>
</div>
</div>
</div>
);

View File

@@ -5,9 +5,9 @@ import { httpClient } from '@/app/infra/http/HttpClient';
import { LLMModel } from '@/app/infra/entities/api';
import { UUID } from 'uuidjs';
import { zodResolver } from "@hookform/resolvers/zod"
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 {
Dialog,
@@ -15,10 +15,9 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import {
Form,
FormControl,
@@ -27,31 +26,44 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { toast } from "sonner"
const extraArgSchema = z.object({
key: z.string().min(1, { message: '键名不能为空' }),
type: z.enum(['string', 'number', 'boolean']),
value: z.string(),
}).superRefine((data, ctx) => {
if (data.type === 'number' && isNaN(Number(data.value))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "必须是有效的数字",
path: ['value'],
});
}
if (data.type === 'boolean' && data.value !== 'true' && data.value !== 'false') {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "必须是 true 或 false",
path: ['value'],
});
}
});
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { toast } from 'sonner';
const extraArgSchema = z
.object({
key: z.string().min(1, { message: '键名不能为空' }),
type: z.enum(['string', 'number', 'boolean']),
value: z.string(),
})
.superRefine((data, ctx) => {
if (data.type === 'number' && isNaN(Number(data.value))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: '必须是有效的数字',
path: ['value'],
});
}
if (
data.type === 'boolean' &&
data.value !== 'true' &&
data.value !== 'false'
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: '必须是 true 或 false',
path: ['value'],
});
}
});
const formSchema = z.object({
name: z.string().min(1, { message: '模型名称不能为空' }),
@@ -60,7 +72,7 @@ const formSchema = z.object({
api_key: z.string().min(1, { message: 'API Key不能为空' }),
abilities: z.array(z.string()),
extra_args: z.array(extraArgSchema).optional(),
})
});
export default function LLMForm({
editMode,
@@ -87,10 +99,12 @@ export default function LLMForm({
},
});
const [extraArgs, setExtraArgs] = useState<{key: string, type: 'string' | 'number' | 'boolean', value: string}[]>([]);
const [extraArgs, setExtraArgs] = useState<
{ key: string; type: 'string' | 'number' | 'boolean'; value: string }[]
>([]);
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const abilityOptions: { label: string, value: string }[] = [
const abilityOptions: { label: string; value: string }[] = [
{
label: '视觉能力',
value: 'vision',
@@ -115,21 +129,21 @@ export default function LLMForm({
form.setValue('model_provider', val.model_provider);
form.setValue('url', val.url);
form.setValue('api_key', val.api_key);
form.setValue('abilities', val.abilities as ("vision" | "func_call")[]);
form.setValue('abilities', val.abilities as ('vision' | 'func_call')[]);
// 转换extra_args为新格式
if(val.extra_args) {
const args = val.extra_args.map(arg => {
if (val.extra_args) {
const args = val.extra_args.map((arg) => {
const [key, value] = arg.split(':');
let type: 'string' | 'number' | 'boolean' = 'string';
if(!isNaN(Number(value))) {
if (!isNaN(Number(value))) {
type = 'number';
} else if(value === 'true' || value === 'false') {
} else if (value === 'true' || value === 'false') {
type = 'boolean';
}
return {
key,
type,
value
value,
};
});
setExtraArgs(args);
@@ -143,14 +157,18 @@ export default function LLMForm({
}, []);
const addExtraArg = () => {
setExtraArgs([...extraArgs, {key: '', type: 'string', value: ''}]);
setExtraArgs([...extraArgs, { key: '', type: 'string', value: '' }]);
};
const updateExtraArg = (index: number, field: 'key' | 'type' | 'value', value: string) => {
const updateExtraArg = (
index: number,
field: 'key' | 'type' | 'value',
value: string,
) => {
const newArgs = [...extraArgs];
newArgs[index] = {
...newArgs[index],
[field]: value
[field]: value,
};
setExtraArgs(newArgs);
form.setValue('extra_args', newArgs);
@@ -216,11 +234,12 @@ export default function LLMForm({
function onCreateLLM(value: z.infer<typeof formSchema>) {
console.log('create llm', value);
// 转换extra_args为对象格式
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extraArgsObj: Record<string, any> = {};
value.extra_args?.forEach(arg => {
if(arg.type === 'number') {
value.extra_args?.forEach((arg) => {
if (arg.type === 'number') {
extraArgsObj[arg.key] = Number(arg.value);
} else if(arg.type === 'boolean') {
} else if (arg.type === 'boolean') {
extraArgsObj[arg.key] = arg.value === 'true';
} else {
extraArgsObj[arg.key] = arg.value;
@@ -240,46 +259,56 @@ export default function LLMForm({
api_keys: [value.api_key],
abilities: value.abilities,
};
httpClient.createProviderLLMModel(requestParam).then(() => {
onFormSubmit(value);
toast.success("创建成功");
}).catch((err) => {
toast.error("创建失败:" + err.message);
});
httpClient
.createProviderLLMModel(requestParam)
.then(() => {
onFormSubmit(value);
toast.success('创建成功');
})
.catch((err) => {
toast.error('创建失败:' + err.message);
});
}
function handleAbilitiesChange() { }
function deleteModel() {
if (initLLMId) {
httpClient.deleteProviderLLMModel(initLLMId).then(() => {
onLLMDeleted();
toast.success("删除成功");
}).catch((err) => {
toast.error("删除失败:" + err.message);
});
httpClient
.deleteProviderLLMModel(initLLMId)
.then(() => {
onLLMDeleted();
toast.success('删除成功');
})
.catch((err) => {
toast.error('删除失败:' + err.message);
});
}
}
return (
<div>
<Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
<Dialog
open={showDeleteConfirmModal}
onOpenChange={setShowDeleteConfirmModal}
>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<DialogDescription>
</DialogDescription>
<DialogDescription></DialogDescription>
<DialogFooter>
<Button variant="outline" onClick={() => setShowDeleteConfirmModal(false)}>
<Button
variant="outline"
onClick={() => setShowDeleteConfirmModal(false)}
>
</Button>
<Button variant="destructive" onClick={() => {
deleteModel();
setShowDeleteConfirmModal(false);
}}>
<Button
variant="destructive"
onClick={() => {
deleteModel();
setShowDeleteConfirmModal(false);
}}
>
</Button>
</DialogFooter>
@@ -287,14 +316,19 @@ export default function LLMForm({
</Dialog>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleFormSubmit)} className="space-y-8">
<form
onSubmit={form.handleSubmit(handleFormSubmit)}
className="space-y-8"
>
<div className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -311,15 +345,22 @@ export default function LLMForm({
name="model_provider"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Select onValueChange={(value) => {
field.onChange(value);
const index = requesterNameList.findIndex(item => item.value === value);
if(index !== -1) {
form.setValue('url', requesterDefaultURLList[index]);
}
}} value={field.value}>
<Select
onValueChange={(value) => {
field.onChange(value);
const index = requesterNameList.findIndex(
(item) => item.value === value,
);
if (index !== -1) {
form.setValue('url', requesterDefaultURLList[index]);
}
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="选择模型供应商" />
</SelectTrigger>
@@ -338,13 +379,15 @@ export default function LLMForm({
</FormItem>
)}
/>
<FormField
control={form.control}
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>URL<span className="text-red-500">*</span></FormLabel>
<FormLabel>
URL<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -357,7 +400,9 @@ export default function LLMForm({
name="api_key"
render={({ field }) => (
<FormItem>
<FormLabel>API Key<span className="text-red-500">*</span></FormLabel>
<FormLabel>
API Key<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -368,13 +413,11 @@ export default function LLMForm({
<FormField
control={form.control}
name="abilities"
render={({ field }) => (
render={() => (
<FormItem>
<FormLabel></FormLabel>
<div className="mb-0">
<FormDescription>
</FormDescription>
<FormDescription></FormDescription>
</div>
{abilityOptions.map((item) => (
<FormField
@@ -389,15 +432,21 @@ export default function LLMForm({
>
<FormControl>
<Checkbox
checked={Array.isArray(field.value) && field.value?.includes(item.value)}
checked={
Array.isArray(field.value) &&
field.value?.includes(item.value)
}
onCheckedChange={(checked) => {
return checked
? field.onChange([...(field.value || []), item.value])
? field.onChange([
...(field.value || []),
item.value,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.value
)
)
(value) => value !== item.value,
),
);
}}
/>
</FormControl>
@@ -405,7 +454,7 @@ export default function LLMForm({
{item.label}
</FormLabel>
</FormItem>
)
);
}}
/>
))}
@@ -419,14 +468,18 @@ export default function LLMForm({
<div className="space-y-2">
{extraArgs.map((arg, index) => (
<div key={index} className="flex gap-2">
<Input
<Input
placeholder="键名"
value={arg.key}
onChange={(e) => updateExtraArg(index, 'key', e.target.value)}
onChange={(e) =>
updateExtraArg(index, 'key', e.target.value)
}
/>
<Select
<Select
value={arg.type}
onValueChange={(value) => updateExtraArg(index, 'type', value)}
onValueChange={(value) =>
updateExtraArg(index, 'type', value)
}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="类型" />
@@ -437,17 +490,24 @@ export default function LLMForm({
<SelectItem value="boolean"></SelectItem>
</SelectContent>
</Select>
<Input
<Input
placeholder="值"
value={arg.value}
onChange={(e) => updateExtraArg(index, 'value', e.target.value)}
onChange={(e) =>
updateExtraArg(index, 'value', e.target.value)
}
/>
<button
<button
type="button"
className="p-2 hover:bg-gray-100 rounded"
onClick={() => removeExtraArg(index)}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5 text-red-500">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-5 h-5 text-red-500"
>
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
</svg>
</button>
@@ -462,24 +522,28 @@ export default function LLMForm({
</FormDescription>
<FormMessage />
</FormItem>
</div>
<DialogFooter>
{!editMode && (
<Button type="submit"></Button>
)}
{!editMode && <Button type="submit"></Button>}
{editMode && (
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
<Button
type="button"
variant="destructive"
onClick={() => setShowDeleteConfirmModal(true)}
>
</Button>
)}
<Button type="button" variant="outline" onClick={() => onFormCancel()}>
<Button
type="button"
variant="outline"
onClick={() => onFormCancel()}
>
</Button>
</DialogFooter>
</form>
</Form>
</div>
);
}

View File

@@ -3,7 +3,6 @@
import { useState, useEffect } from 'react';
import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO';
import styles from './LLMConfig.module.css';
import EmptyAndCreateComponent from '@/app/home/components/empty-and-create-component/EmptyAndCreateComponent';
import LLMCard from '@/app/home/models/component/llm-card/LLMCard';
import LLMForm from '@/app/home/models/component/llm-form/LLMForm';
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
@@ -12,12 +11,10 @@ import { LLMModel } from '@/app/infra/entities/api';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { toast } from "sonner";
} from '@/components/ui/dialog';
import { toast } from 'sonner';
export default function LLMConfigPage() {
const [cardList, setCardList] = useState<LLMCardVO[]>([]);
@@ -47,7 +44,9 @@ export default function LLMConfigPage() {
id: model.uuid,
iconURL: httpClient.getProviderRequesterIconURL(model.requester),
name: model.name,
providerLabel: requesterNameList.find((item) => item.value === model.requester)?.label || model.requester.substring(0, 10),
providerLabel:
requesterNameList.find((item) => item.value === model.requester)
?.label || model.requester.substring(0, 10),
baseURL: model.requester_config?.base_url,
abilities: model.abilities || [],
});
@@ -57,7 +56,7 @@ export default function LLMConfigPage() {
})
.catch((err) => {
console.error('get LLM model list error', err);
toast.error("获取模型列表失败:" + err.message);
toast.error('获取模型列表失败:' + err.message);
});
}
@@ -74,8 +73,7 @@ export default function LLMConfigPage() {
}
return (
<div >
<div>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] p-6">
<DialogHeader>
@@ -99,7 +97,6 @@ export default function LLMConfigPage() {
</DialogContent>
</Dialog>
<div className={`${styles.modelListContainer}`}>
<CreateCardComponent
width={'24rem'}
height={'10rem'}

View File

@@ -1,17 +1,10 @@
import styles from './pipelineCard.module.css';
import { PipelineCardVO } from '@/app/home/pipelines/components/pipeline-card/PipelineCardVO';
import { Button } from '@/components/ui/button';
export default function PipelineCard({
cardVO,
}: {
cardVO: PipelineCardVO;
}) {
export default function PipelineCard({ cardVO }: { cardVO: PipelineCardVO }) {
return (
<div className={`${styles.cardContainer}`}>
<div className={`${styles.basicInfoContainer}`}>
<div className={`${styles.basicInfoNameContainer}`}>
<div className={`${styles.basicInfoNameText} ${styles.bigText}`}>
{cardVO.name}
@@ -22,27 +15,35 @@ export default function PipelineCard({
</div>
<div className={`${styles.basicInfoLastUpdatedTimeContainer}`}>
<svg className={`${styles.basicInfoUpdateTimeIcon}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM13 12H17V14H11V7H13V12Z"></path></svg>
<svg
className={`${styles.basicInfoUpdateTimeIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM13 12H17V14H11V7H13V12Z"></path>
</svg>
<div className={`${styles.basicInfoUpdateTimeText}`}>
{cardVO.lastUpdatedTimeAgo}
</div>
</div>
</div>
<div className={styles.operationContainer}>
{cardVO.isDefault && (
<div className={styles.operationDefaultBadge}>
<svg className={styles.operationDefaultBadgeIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path></svg>
<div className={styles.operationDefaultBadgeText}>
<div className={styles.operationDefaultBadge}>
<svg
className={styles.operationDefaultBadgeIcon}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path>
</svg>
<div className={styles.operationDefaultBadgeText}></div>
</div>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,15 +1,18 @@
import { useEffect, useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { Pipeline } from '@/app/infra/entities/api';
import { PipelineFormEntity, PipelineConfigTab, PipelineConfigStage } from '@/app/infra/entities/pipeline';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
PipelineFormEntity,
PipelineConfigTab,
PipelineConfigStage,
} from '@/app/infra/entities/pipeline';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { Button } from '@/components/ui/button';
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Input } from "@/components/ui/input"
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Input } from '@/components/ui/input';
import {
Form,
FormControl,
@@ -17,8 +20,8 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { toast } from "sonner"
} from '@/components/ui/form';
import { toast } from 'sonner';
export default function PipelineFormComponent({
initValues,
@@ -26,7 +29,6 @@ export default function PipelineFormComponent({
onNewPipelineCreated,
isEditMode,
pipelineId,
disableForm,
}: {
pipelineId?: string;
isEditMode: boolean;
@@ -36,48 +38,52 @@ export default function PipelineFormComponent({
onFinish: () => void;
onNewPipelineCreated: (pipelineId: string) => void;
}) {
const formSchema = isEditMode ? z.object({
basic: z.object({
name: z.string().min(1, { message: '名称不能为空' }),
description: z.string().min(1, { message: '描述不能为空' }),
}),
ai: z.record(z.string(), z.any()),
trigger: z.record(z.string(), z.any()),
safety: z.record(z.string(), z.any()),
output: z.record(z.string(), z.any()),
})
const formSchema = isEditMode
? z.object({
basic: z.object({
name: z.string().min(1, { message: '名称不能为空' }),
description: z.string().min(1, { message: '描述不能为空' }),
}),
ai: z.record(z.string(), z.any()),
trigger: z.record(z.string(), z.any()),
safety: z.record(z.string(), z.any()),
output: z.record(z.string(), z.any()),
})
: z.object({
basic: z.object({
name: z.string().min(1, { message: '名称不能为空' }),
description: z.string().min(1, { message: '描述不能为空' }),
}),
ai: z.record(z.string(), z.any()).optional(),
trigger: z.record(z.string(), z.any()).optional(),
safety: z.record(z.string(), z.any()).optional(),
output: z.record(z.string(), z.any()).optional(),
});
basic: z.object({
name: z.string().min(1, { message: '名称不能为空' }),
description: z.string().min(1, { message: '描述不能为空' }),
}),
ai: z.record(z.string(), z.any()).optional(),
trigger: z.record(z.string(), z.any()).optional(),
safety: z.record(z.string(), z.any()).optional(),
output: z.record(z.string(), z.any()).optional(),
});
type FormValues = z.infer<typeof formSchema>;
// 这里不好可以改成enum等
const formLabelList: FormLabel[] = isEditMode ? [
{ label: '基础信息', name: 'basic' },
{ label: 'AI能力', name: 'ai' },
{ label: '触发条件', name: 'trigger' },
{ label: '安全能力', name: 'safety' },
{ label: '输出处理', name: 'output' },
] : [
{ label: '基础信息', name: 'basic' },
];
const formLabelList: FormLabel[] = isEditMode
? [
{ label: '基础信息', name: 'basic' },
{ label: 'AI能力', name: 'ai' },
{ label: '触发条件', name: 'trigger' },
{ label: '安全能力', name: 'safety' },
{ label: '输出处理', name: 'output' },
]
: [{ label: '基础信息', name: 'basic' }];
// const [basicForm] = Form.useForm();
// const [aiForm] = Form.useForm();
// const [triggerForm] = Form.useForm();
// const [safetyForm] = Form.useForm();
// const [outputForm] = Form.useForm();
const [aiConfigTabSchema, setAIConfigTabSchema] = useState<PipelineConfigTab>();
const [triggerConfigTabSchema, setTriggerConfigTabSchema] = useState<PipelineConfigTab>();
const [safetyConfigTabSchema, setSafetyConfigTabSchema] = useState<PipelineConfigTab>();
const [outputConfigTabSchema, setOutputConfigTabSchema] = useState<PipelineConfigTab>();
const [aiConfigTabSchema, setAIConfigTabSchema] =
useState<PipelineConfigTab>();
const [triggerConfigTabSchema, setTriggerConfigTabSchema] =
useState<PipelineConfigTab>();
const [safetyConfigTabSchema, setSafetyConfigTabSchema] =
useState<PipelineConfigTab>();
const [outputConfigTabSchema, setOutputConfigTabSchema] =
useState<PipelineConfigTab>();
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
@@ -91,7 +97,6 @@ export default function PipelineFormComponent({
});
useEffect(() => {
// get config schema from metadata
httpClient.getGeneralPipelineMetadata().then((resp) => {
for (const config of resp.configs) {
@@ -121,7 +126,7 @@ export default function PipelineFormComponent({
},
});
}
}, [initValues, form]);
}, [initValues, form, isEditMode]);
function handleFormSubmit(values: FormValues) {
console.log('handleFormSubmit', values);
@@ -139,17 +144,19 @@ export default function PipelineFormComponent({
description: values.basic.description,
name: values.basic.name,
};
httpClient.createPipeline(pipeline).then((resp) => {
onFinish();
onNewPipelineCreated(resp.uuid);
toast.success("创建成功 请编辑流水线详细参数");
}).catch((err) => {
toast.error("创建失败:" + err.message);
});
httpClient
.createPipeline(pipeline)
.then((resp) => {
onFinish();
onNewPipelineCreated(resp.uuid);
toast.success('创建成功 请编辑流水线详细参数');
})
.catch((err) => {
toast.error('创建失败:' + err.message);
});
}
function handleModify(values: FormValues) {
const realConfig = {
ai: values.ai,
trigger: values.trigger,
@@ -168,15 +175,21 @@ export default function PipelineFormComponent({
// uuid: pipelineId || '',
// is_default: false,
};
httpClient.updatePipeline(pipelineId || '', pipeline).then(() => {
onFinish();
toast.success("保存成功");
}).catch((err) => {
toast.error("保存失败:" + err.message);
});
httpClient
.updatePipeline(pipelineId || '', pipeline)
.then(() => {
onFinish();
toast.success('保存成功');
})
.catch((err) => {
toast.error('保存失败:' + err.message);
});
}
function renderDynamicForms(stage: PipelineConfigStage, formName: keyof FormValues) {
function renderDynamicForms(
stage: PipelineConfigStage,
formName: keyof FormValues,
) {
// 如果是 AI 配置,需要特殊处理
if (formName === 'ai') {
// 获取当前选择的 runner
@@ -188,13 +201,21 @@ export default function PipelineFormComponent({
<div key={stage.name} className="space-y-4 mb-6">
<div className="text-lg font-medium">{stage.label.zh_CN}</div>
{stage.description && (
<div className="text-sm text-gray-500">{stage.description.zh_CN}</div>
<div className="text-sm text-gray-500">
{stage.description.zh_CN}
</div>
)}
<DynamicFormComponent
itemConfigList={stage.config}
initialValues={(form.watch(formName) as Record<string, any>)?.[stage.name] || {}}
initialValues={
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(form.watch(formName) as Record<string, any>)?.[stage.name] ||
{}
}
onSubmit={(values) => {
const currentValues = form.getValues(formName) as Record<string, any> || {};
const currentValues =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(form.getValues(formName) as Record<string, any>) || {};
form.setValue(formName, {
...currentValues,
[stage.name]: values,
@@ -219,9 +240,14 @@ export default function PipelineFormComponent({
)}
<DynamicFormComponent
itemConfigList={stage.config}
initialValues={(form.watch(formName) as Record<string, any>)?.[stage.name] || {}}
initialValues={
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(form.watch(formName) as Record<string, any>)?.[stage.name] || {}
}
onSubmit={(values) => {
const currentValues = form.getValues(formName) as Record<string, any> || {};
const currentValues =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(form.getValues(formName) as Record<string, any>) || {};
form.setValue(formName, {
...currentValues,
[stage.name]: values,
@@ -246,7 +272,11 @@ export default function PipelineFormComponent({
</TabsList>
{formLabelList.map((formLabel) => (
<TabsContent key={formLabel.name} value={formLabel.name} className='pr-6'>
<TabsContent
key={formLabel.name}
value={formLabel.name}
className="pr-6"
>
<h1 className="text-xl font-bold mb-4">{formLabel.label}</h1>
{formLabel.name === 'basic' && (
@@ -256,7 +286,9 @@ export default function PipelineFormComponent({
name="basic.name"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -270,7 +302,9 @@ export default function PipelineFormComponent({
name="basic.description"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormLabel>
<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -285,25 +319,33 @@ export default function PipelineFormComponent({
<>
{formLabel.name === 'ai' && aiConfigTabSchema && (
<div className="space-y-6">
{aiConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'ai'))}
{aiConfigTabSchema.stages.map((stage) =>
renderDynamicForms(stage, 'ai'),
)}
</div>
)}
{formLabel.name === 'trigger' && triggerConfigTabSchema && (
<div className="space-y-6">
{triggerConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'trigger'))}
{triggerConfigTabSchema.stages.map((stage) =>
renderDynamicForms(stage, 'trigger'),
)}
</div>
)}
{formLabel.name === 'safety' && safetyConfigTabSchema && (
<div className="space-y-6">
{safetyConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'safety'))}
{safetyConfigTabSchema.stages.map((stage) =>
renderDynamicForms(stage, 'safety'),
)}
</div>
)}
{formLabel.name === 'output' && outputConfigTabSchema && (
<div className="space-y-6">
{outputConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'output'))}
{outputConfigTabSchema.stages.map((stage) =>
renderDynamicForms(stage, 'output'),
)}
</div>
)}
</>
@@ -314,9 +356,7 @@ export default function PipelineFormComponent({
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
<div className="flex justify-end gap-2">
<Button type="submit">
{isEditMode ? '保存' : '提交'}
</Button>
<Button type="submit">{isEditMode ? '保存' : '提交'}</Button>
<Button type="button" variant="outline" onClick={onFinish}>
</Button>

View File

@@ -10,13 +10,10 @@ import styles from './pipelineConfig.module.css';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from '@/components/ui/button';
import { toast } from "sonner"
} from '@/components/ui/dialog';
import { toast } from 'sonner';
export default function PluginConfigPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [isEditForm, setIsEditForm] = useState(false);
@@ -40,11 +37,21 @@ export default function PluginConfigPage() {
httpClient
.getPipelines()
.then((value) => {
let currentTime = new Date();
const currentTime = new Date();
const pipelineList = value.pipelines.map((pipeline) => {
let lastUpdatedTimeAgo = Math.floor((currentTime.getTime() - new Date(pipeline.updated_at ?? currentTime.getTime()).getTime()) / 1000 / 60 / 60 / 24);
const lastUpdatedTimeAgo = Math.floor(
(currentTime.getTime() -
new Date(
pipeline.updated_at ?? currentTime.getTime(),
).getTime()) /
1000 /
60 /
60 /
24,
);
let lastUpdatedTimeAgoText = lastUpdatedTimeAgo > 0 ? ` ${lastUpdatedTimeAgo} 天前` : '今天';
const lastUpdatedTimeAgoText =
lastUpdatedTimeAgo > 0 ? ` ${lastUpdatedTimeAgo} 天前` : '今天';
return new PipelineCardVO({
lastUpdatedTimeAgo: lastUpdatedTimeAgoText,
@@ -58,7 +65,7 @@ export default function PluginConfigPage() {
})
.catch((error) => {
console.log(error);
toast.error("获取流水线列表失败:" + error.message);
toast.error('获取流水线列表失败:' + error.message);
});
}
@@ -80,7 +87,6 @@ export default function PluginConfigPage() {
return (
<div className={styles.configPageContainer}>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-4">

View File

@@ -1,14 +1,22 @@
'use client';
import PluginInstalledComponent, { PluginInstalledComponentRef } from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
import PluginInstalledComponent, {
PluginInstalledComponentRef,
} from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
import PluginMarketComponent from '@/app/home/plugins/plugin-market/PluginMarketComponent';
import styles from './plugins.module.css';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { PlusIcon } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { GithubIcon } from "lucide-react";
import { useState, useRef, useEffect } from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { PlusIcon } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { GithubIcon } from 'lucide-react';
import { useState, useRef } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
enum PluginInstallStatus {
@@ -18,9 +26,9 @@ enum PluginInstallStatus {
}
export default function PluginConfigPage() {
const [modalOpen, setModalOpen] = useState(false);
const [pluginInstallStatus, setPluginInstallStatus] = useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
const [pluginInstallStatus, setPluginInstallStatus] =
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
const [installError, setInstallError] = useState<string | null>(null);
const [githubURL, setGithubURL] = useState('');
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
@@ -44,7 +52,8 @@ export default function PluginConfigPage() {
if (resp.runtime.exception) {
setInstallError(resp.runtime.exception);
setPluginInstallStatus(PluginInstallStatus.ERROR);
} else { // success
} else {
// success
setGithubURL('');
setModalOpen(false);
pluginInstalledRef.current?.refreshPluginList();
@@ -52,7 +61,6 @@ export default function PluginConfigPage() {
}
});
}, 1000);
})
.catch((err) => {
console.log('error when install plugin:', err);
@@ -64,20 +72,27 @@ export default function PluginConfigPage() {
return (
<div className={styles.pageContainer}>
<Tabs defaultValue="installed" className="w-full">
<div className='flex flex-row justify-between items-center'>
<TabsList className='shadow-md py-5 bg-[#f0f0f0]'>
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer"></TabsTrigger>
<TabsTrigger value="market" className="px-6 py-4 cursor-pointer"></TabsTrigger>
<div className="flex flex-row justify-between items-center">
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">
</TabsTrigger>
<TabsTrigger value="market" className="px-6 py-4 cursor-pointer">
</TabsTrigger>
</TabsList>
<div className='flex flex-row justify-end items-center'>
<Button variant="default" className='px-6 py-4 cursor-pointer' onClick={() => {
setModalOpen(true);
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
setInstallError(null);
}}>
<PlusIcon className='w-4 h-4' />
<div className="flex flex-row justify-end items-center">
<Button
variant="default"
className="px-6 py-4 cursor-pointer"
onClick={() => {
setModalOpen(true);
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
setInstallError(null);
}}
>
<PlusIcon className="w-4 h-4" />
</Button>
</div>
@@ -86,16 +101,17 @@ export default function PluginConfigPage() {
<PluginInstalledComponent ref={pluginInstalledRef} />
</TabsContent>
<TabsContent value="market">
<PluginMarketComponent askInstallPlugin={(githubURL) => {
setGithubURL(githubURL);
setModalOpen(true);
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
setInstallError(null);
}} />
<PluginMarketComponent
askInstallPlugin={(githubURL) => {
setGithubURL(githubURL);
setModalOpen(true);
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
setInstallError(null);
}}
/>
</TabsContent>
</Tabs>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[500px] p-6">
<DialogHeader>
@@ -105,14 +121,14 @@ export default function PluginConfigPage() {
</DialogTitle>
</DialogHeader>
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
<div className="mt-4">
<p className="mb-2"> GitHub </p>
<Input
placeholder="请输入插件的Github链接"
value={githubURL}
onChange={(e) => setGithubURL(e.target.value)}
className="mb-4"
/>
<div className="mt-4">
<p className="mb-2"> GitHub </p>
<Input
placeholder="请输入插件的Github链接"
value={githubURL}
onChange={(e) => setGithubURL(e.target.value)}
className="mb-4"
/>
</div>
)}
{pluginInstallStatus === PluginInstallStatus.INSTALLING && (
@@ -129,12 +145,16 @@ export default function PluginConfigPage() {
<DialogFooter>
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
<>
<Button variant="outline" onClick={() => setModalOpen(false)}></Button>
<Button variant="outline" onClick={() => setModalOpen(false)}>
</Button>
<Button onClick={handleModalConfirm}></Button>
</>
)}
{pluginInstallStatus === PluginInstallStatus.ERROR && (
<Button variant="default" onClick={() => setModalOpen(false)}></Button>
<Button variant="default" onClick={() => setModalOpen(false)}>
</Button>
)}
</DialogFooter>
</DialogContent>

View File

@@ -1,105 +1,108 @@
'use client';
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO';
import PluginCardComponent from '@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent';
import PluginForm from '@/app/home/plugins/plugin-installed/plugin-form/PluginForm';
import styles from '@/app/home/plugins/plugins.module.css';
import { GithubIcon } from 'lucide-react';
import { httpClient } from '@/app/infra/http/HttpClient';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
} from '@/components/ui/dialog';
export interface PluginInstalledComponentRef {
refreshPluginList: () => void;
}
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>((props, ref) => {
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [selectedPlugin, setSelectedPlugin] = useState<PluginCardVO | null>(null);
// eslint-disable-next-line react/display-name
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
(props, ref) => {
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [selectedPlugin, setSelectedPlugin] = useState<PluginCardVO | null>(
null,
);
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function initData() {
getPluginList();
}
function initData() {
getPluginList();
}
function getPluginList() {
httpClient.getPlugins().then((value) => {
setPluginList(
value.plugins.map((plugin) => {
return new PluginCardVO({
author: plugin.author,
description: plugin.description.zh_CN,
enabled: plugin.enabled,
name: plugin.name,
version: plugin.version,
status: plugin.status,
tools: plugin.tools,
event_handlers: plugin.event_handlers,
repository: plugin.repository,
priority: plugin.priority,
});
}),
);
});
}
useImperativeHandle(ref, () => ({
refreshPluginList: getPluginList
}));
function handlePluginClick(plugin: PluginCardVO) {
setSelectedPlugin(plugin);
setModalOpen(true);
}
return (
<div className={`${styles.pluginListContainer}`}>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-2">
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6">
{selectedPlugin && (
<PluginForm
pluginAuthor={selectedPlugin.author}
pluginName={selectedPlugin.name}
onFormSubmit={() => {
setModalOpen(false);
getPluginList();
}}
onFormCancel={() => {
setModalOpen(false);
}}
/>
)}
</div>
</DialogContent>
</Dialog>
{pluginList.map((vo, index) => {
return (
<div key={index}>
<PluginCardComponent cardVO={vo} onCardClick={() => handlePluginClick(vo)} />
</div>
function getPluginList() {
httpClient.getPlugins().then((value) => {
setPluginList(
value.plugins.map((plugin) => {
return new PluginCardVO({
author: plugin.author,
description: plugin.description.zh_CN,
enabled: plugin.enabled,
name: plugin.name,
version: plugin.version,
status: plugin.status,
tools: plugin.tools,
event_handlers: plugin.event_handlers,
repository: plugin.repository,
priority: plugin.priority,
});
}),
);
})}
</div>
);
});
});
}
useImperativeHandle(ref, () => ({
refreshPluginList: getPluginList,
}));
function handlePluginClick(plugin: PluginCardVO) {
setSelectedPlugin(plugin);
setModalOpen(true);
}
return (
<div className={`${styles.pluginListContainer}`}>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-2">
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6">
{selectedPlugin && (
<PluginForm
pluginAuthor={selectedPlugin.author}
pluginName={selectedPlugin.name}
onFormSubmit={() => {
setModalOpen(false);
getPluginList();
}}
onFormCancel={() => {
setModalOpen(false);
}}
/>
)}
</div>
</DialogContent>
</Dialog>
{pluginList.map((vo, index) => {
return (
<div key={index}>
<PluginCardComponent
cardVO={vo}
onCardClick={() => handlePluginClick(vo)}
/>
</div>
);
})}
</div>
);
},
);
export default PluginInstalledComponent;

View File

@@ -1,10 +1,9 @@
import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO';
import { useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
import { Button } from "@/components/ui/button"
import { toast } from "sonner"
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
export default function PluginCardComponent({
cardVO,
@@ -25,39 +24,73 @@ export default function PluginCardComponent({
setEnabled(!enabled);
})
.catch((err) => {
toast.error("修改失败:" + err.message);
toast.error('修改失败:' + err.message);
})
.finally(() => {
setSwitchEnable(true);
});
}
return (
<div className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem] cursor-pointer" onClick={onCardClick}>
<div
className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem] cursor-pointer"
onClick={onCardClick}
>
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
<svg className="w-16 h-16 text-[#2288ee]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path></svg>
<svg
className="w-16 h-16 text-[#2288ee]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path>
</svg>
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
<div className="flex flex-col items-start justify-start">
<div className="flex flex-col items-start justify-start">
<div className="text-[0.7rem] text-[#666]">{cardVO.author} / </div>
<div className="text-[0.7rem] text-[#666]">
{cardVO.author} /{' '}
</div>
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
<Badge variant="outline" className="text-[0.7rem]">v{cardVO.version}</Badge>
<Badge variant="outline" className="text-[0.7rem]">
v{cardVO.version}
</Badge>
</div>
</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">{cardVO.description}</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">
{cardVO.description}
</div>
</div>
<div className="w-full flex flex-row items-start justify-start gap-[0.6rem]">
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg className="w-[1.2rem] h-[1.2rem] text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12L18.3431 17.6569L16.9289 16.2426L21.1716 12L16.9289 7.75736L18.3431 6.34315L24 12ZM2.82843 12L7.07107 16.2426L5.65685 17.6569L0 12L5.65685 6.34315L7.07107 7.75736L2.82843 12ZM9.78845 21H7.66009L14.2116 3H16.3399L9.78845 21Z"></path></svg>
<div className="text-base text-black font-medium"> {Object.keys(cardVO.event_handlers).length}</div>
<svg
className="w-[1.2rem] h-[1.2rem] text-black"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M24 12L18.3431 17.6569L16.9289 16.2426L21.1716 12L16.9289 7.75736L18.3431 6.34315L24 12ZM2.82843 12L7.07107 16.2426L5.65685 17.6569L0 12L5.65685 6.34315L7.07107 7.75736L2.82843 12ZM9.78845 21H7.66009L14.2116 3H16.3399L9.78845 21Z"></path>
</svg>
<div className="text-base text-black font-medium">
{Object.keys(cardVO.event_handlers).length}
</div>
</div>
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg className="w-[1.2rem] h-[1.2rem] text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path></svg>
<div className="text-base text-black font-medium"> {cardVO.tools.length}</div>
<svg
className="w-[1.2rem] h-[1.2rem] text-black"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path>
</svg>
<div className="text-base text-black font-medium">
{cardVO.tools.length}
</div>
</div>
</div>
</div>

View File

@@ -1,9 +1,8 @@
import { useState, useEffect } from 'react';
import { ApiRespPlugin, ApiRespPluginConfig, Plugin } from '@/app/infra/entities/api';
import { ApiRespPluginConfig, Plugin } from '@/app/infra/entities/api';
import { httpClient } from '@/app/infra/http/HttpClient';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { Button } from "@/components/ui/button";
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
@@ -11,8 +10,8 @@ import {
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { toast } from "sonner";
} from '@/components/ui/dialog';
import { toast } from 'sonner';
enum PluginRemoveStatus {
WAIT_INPUT = 'WAIT_INPUT',
@@ -36,8 +35,11 @@ export default function PluginForm({
const [isSaving, setIsLoading] = useState(false);
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const [pluginRemoveStatus, setPluginRemoveStatus] = useState<PluginRemoveStatus>(PluginRemoveStatus.WAIT_INPUT);
const [pluginRemoveError, setPluginRemoveError] = useState<string | null>(null);
const [pluginRemoveStatus, setPluginRemoveStatus] =
useState<PluginRemoveStatus>(PluginRemoveStatus.WAIT_INPUT);
const [pluginRemoveError, setPluginRemoveError] = useState<string | null>(
null,
);
useEffect(() => {
// 获取插件信息
@@ -52,12 +54,16 @@ export default function PluginForm({
const handleSubmit = async (values: object) => {
setIsLoading(true);
httpClient.updatePluginConfig(pluginAuthor, pluginName, values).then(() => {
httpClient
.updatePluginConfig(pluginAuthor, pluginName, values)
.then(() => {
onFormSubmit();
toast.success("保存成功");
}).catch((error) => {
toast.error("保存失败:" + error.message);
}).finally(() => {
toast.success('保存成功');
})
.catch((error) => {
toast.error('保存失败:' + error.message);
})
.finally(() => {
setIsLoading(false);
});
};
@@ -68,36 +74,39 @@ export default function PluginForm({
function deletePlugin() {
setPluginRemoveStatus(PluginRemoveStatus.REMOVING);
httpClient.removePlugin(pluginAuthor, pluginName).then((res) => {
const taskId = res.task_id;
const interval = setInterval(() => {
httpClient.getAsyncTask(taskId).then((res) => {
if (res.runtime.done) {
clearInterval(interval);
if (res.runtime.exception) {
setPluginRemoveError(res.runtime.exception);
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
} else {
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
setShowDeleteConfirmModal(false);
onFormSubmit();
}
}
});
}, 1000);
httpClient
.removePlugin(pluginAuthor, pluginName)
.then((res) => {
const taskId = res.task_id;
}).catch((error) => {
setPluginRemoveError(error.message);
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
})
const interval = setInterval(() => {
httpClient.getAsyncTask(taskId).then((res) => {
if (res.runtime.done) {
clearInterval(interval);
if (res.runtime.exception) {
setPluginRemoveError(res.runtime.exception);
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
} else {
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
setShowDeleteConfirmModal(false);
onFormSubmit();
}
}
});
}, 1000);
})
.catch((error) => {
setPluginRemoveError(error.message);
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
});
}
return (
<div>
<Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
<Dialog
open={showDeleteConfirmModal}
onOpenChange={setShowDeleteConfirmModal}
>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
@@ -120,17 +129,23 @@ export default function PluginForm({
</DialogDescription>
<DialogFooter>
{pluginRemoveStatus === PluginRemoveStatus.WAIT_INPUT && (
<Button variant="outline" onClick={() => {
setShowDeleteConfirmModal(false);
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
}}>
</Button>
<Button
variant="outline"
onClick={() => {
setShowDeleteConfirmModal(false);
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
}}
>
</Button>
)}
{pluginRemoveStatus === PluginRemoveStatus.WAIT_INPUT && (
<Button variant="destructive" onClick={() => {
deletePlugin();
}}>
<Button
variant="destructive"
onClick={() => {
deletePlugin();
}}
>
</Button>
)}
@@ -140,10 +155,13 @@ export default function PluginForm({
</Button>
)}
{pluginRemoveStatus === PluginRemoveStatus.ERROR && (
<Button variant="default" onClick={() => {
setShowDeleteConfirmModal(false);
// setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
}}>
<Button
variant="default"
onClick={() => {
setShowDeleteConfirmModal(false);
// setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
}}
>
</Button>
)}
@@ -151,7 +169,6 @@ export default function PluginForm({
</DialogContent>
</Dialog>
<div className="space-y-2">
<div className="text-lg font-medium">{pluginInfo.name}</div>
<div className="text-sm text-gray-500 pb-2">
@@ -160,7 +177,7 @@ export default function PluginForm({
{pluginInfo.config_schema.length > 0 && (
<DynamicFormComponent
itemConfigList={pluginInfo.config_schema}
initialValues={pluginConfig.config}
initialValues={pluginConfig.config as Record<string, object>}
onSubmit={(values) => {
let config = pluginConfig.config;
config = {
@@ -174,15 +191,12 @@ export default function PluginForm({
/>
)}
{pluginInfo.config_schema.length === 0 && (
<div className="text-sm text-gray-500">
</div>
<div className="text-sm text-gray-500"></div>
)}
</div>
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
<div className="flex justify-end gap-2">
<Button
variant="destructive"
onClick={() => {
@@ -191,7 +205,9 @@ export default function PluginForm({
}}
disabled={pluginRemoveStatus === PluginRemoveStatus.REMOVING}
>
{pluginRemoveStatus === PluginRemoveStatus.REMOVING ? '删除中...' : '删除插件'}
{pluginRemoveStatus === PluginRemoveStatus.REMOVING
? '删除中...'
: '删除插件'}
</Button>
<Button
@@ -208,4 +224,4 @@ export default function PluginForm({
</div>
</div>
);
}
}

View File

@@ -9,18 +9,23 @@ import { Input } from '@/components/ui/input';
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
} from '@/components/ui/pagination';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
export default function PluginMarketComponent({
askInstallPlugin,
}: {
askInstallPlugin: (githubURL: string) => void,
askInstallPlugin: (githubURL: string) => void;
}) {
const [marketPluginList, setMarketPluginList] = useState<
PluginMarketCardVO[]
@@ -45,7 +50,7 @@ export default function PluginMarketComponent({
function onInputSearchKeyword(keyword: string) {
setSearchKeyword(keyword);
// 清除之前的定时器
if (searchTimeout.current) {
clearTimeout(searchTimeout.current);
@@ -69,33 +74,31 @@ export default function PluginMarketComponent({
.getMarketPlugins(page, pageSize, keyword, sortBy, sortOrder)
.then((res) => {
setMarketPluginList(
res.plugins.map(
(marketPlugin) => {
let repository = marketPlugin.repository;
if (repository.startsWith('https://github.com/')) {
repository = repository.replace('https://github.com/', '');
}
res.plugins.map((marketPlugin) => {
let repository = marketPlugin.repository;
if (repository.startsWith('https://github.com/')) {
repository = repository.replace('https://github.com/', '');
}
if (repository.startsWith('github.com/')) {
repository = repository.replace('github.com/', '');
}
if (repository.startsWith('github.com/')) {
repository = repository.replace('github.com/', '');
}
const author = repository.split('/')[0];
const name = repository.split('/')[1];
return new PluginMarketCardVO({
author: author,
description: marketPlugin.description,
githubURL: `https://github.com/${repository}`,
name: name,
pluginId: String(marketPlugin.ID),
starCount: marketPlugin.stars,
version:
'version' in marketPlugin
? String(marketPlugin.version)
: '1.0.0', // Default version if not provided
});
},
),
const author = repository.split('/')[0];
const name = repository.split('/')[1];
return new PluginMarketCardVO({
author: author,
description: marketPlugin.description,
githubURL: `https://github.com/${repository}`,
name: name,
pluginId: String(marketPlugin.ID),
starCount: marketPlugin.stars,
version:
'version' in marketPlugin
? String(marketPlugin.version)
: '1.0.0', // Default version if not provided
});
}),
);
setTotalCount(res.total);
setLoading(false);
@@ -113,7 +116,7 @@ export default function PluginMarketComponent({
}
function handleSortChange(value: string) {
const [newSortBy, newSortOrder] = value.split(',').map(s => s.trim());
const [newSortBy, newSortOrder] = value.split(',').map((s) => s.trim());
setSortByValue(newSortBy);
setSortOrderValue(newSortOrder);
setNowPage(1);
@@ -132,7 +135,10 @@ export default function PluginMarketComponent({
onChange={(e) => onInputSearchKeyword(e.target.value)}
/>
<Select value={`${sortByValue},${sortOrderValue}`} onValueChange={handleSortChange}>
<Select
value={`${sortByValue},${sortOrderValue}`}
onValueChange={handleSortChange}
>
<SelectTrigger className="w-[180px] ml-2 cursor-pointer">
<SelectValue placeholder="排序方式" />
</SelectTrigger>
@@ -147,10 +153,12 @@ export default function PluginMarketComponent({
{totalCount > 0 && (
<Pagination>
<PaginationContent>
<PaginationItem className='cursor-pointer'>
<PaginationItem className="cursor-pointer">
<PaginationPrevious
onClick={() => handlePageChange(nowPage - 1)}
className={nowPage <= 1 ? 'pointer-events-none opacity-50' : ''}
className={
nowPage <= 1 ? 'pointer-events-none opacity-50' : ''
}
/>
</PaginationItem>
@@ -158,35 +166,50 @@ export default function PluginMarketComponent({
{(() => {
const totalPages = Math.ceil(totalCount / pageSize);
const maxVisiblePages = 5;
let startPage = Math.max(1, nowPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
let startPage = Math.max(
1,
nowPage - Math.floor(maxVisiblePages / 2),
);
const endPage = Math.min(
totalPages,
startPage + maxVisiblePages - 1,
);
if (endPage - startPage + 1 < maxVisiblePages) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
return Array.from({ length: endPage - startPage + 1 }, (_, i) => {
const pageNum = startPage + i;
return (
<PaginationItem key={pageNum} className='cursor-pointer'>
<PaginationLink
isActive={pageNum === nowPage}
onClick={() => handlePageChange(pageNum)}
return Array.from(
{ length: endPage - startPage + 1 },
(_, i) => {
const pageNum = startPage + i;
return (
<PaginationItem
key={pageNum}
className="cursor-pointer"
>
<span className="text-black select-none">
{pageNum}
</span>
</PaginationLink>
</PaginationItem>
);
});
<PaginationLink
isActive={pageNum === nowPage}
onClick={() => handlePageChange(pageNum)}
>
<span className="text-black select-none">
{pageNum}
</span>
</PaginationLink>
</PaginationItem>
);
},
);
})()}
<PaginationItem className='cursor-pointer'>
<PaginationItem className="cursor-pointer">
<PaginationNext
onClick={() => handlePageChange(nowPage + 1)}
className={nowPage >= Math.ceil(totalCount / pageSize) ? 'pointer-events-none opacity-50' : ''}
className={
nowPage >= Math.ceil(totalCount / pageSize)
? 'pointer-events-none opacity-50'
: ''
}
/>
</PaginationItem>
</PaginationContent>
@@ -207,9 +230,12 @@ export default function PluginMarketComponent({
) : (
marketPluginList.map((vo, index) => (
<div key={`${vo.pluginId}-${index}`}>
<PluginMarketCardComponent cardVO={vo} installPlugin={(githubURL) => {
askInstallPlugin(githubURL);
}} />
<PluginMarketCardComponent
cardVO={vo}
installPlugin={(githubURL) => {
askInstallPlugin(githubURL);
}}
/>
</div>
))
)}

View File

@@ -15,40 +15,66 @@ export default function PluginMarketCardComponent({
return (
<div className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem]">
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
<svg className="w-16 h-16 text-[#2288ee]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path></svg>
<svg
className="w-16 h-16 text-[#2288ee]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path>
</svg>
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
<div className="flex flex-col items-start justify-start">
<div className="flex flex-col items-start justify-start">
<div className="text-[0.7rem] text-[#666]">{cardVO.author} / </div>
<div className="text-[0.7rem] text-[#666]">
{cardVO.author} /{' '}
</div>
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
</div>
</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">{cardVO.description}</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">
{cardVO.description}
</div>
</div>
<div className="w-full flex flex-row items-start justify-between gap-[0.6rem]">
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg className="w-[1.2rem] h-[1.2rem] text-[#ffcd27]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path></svg>
<div className="text-base text-[#ffcd27] font-medium"> {cardVO.starCount}</div>
<svg
className="w-[1.2rem] h-[1.2rem] text-[#ffcd27]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path>
</svg>
<div className="text-base text-[#ffcd27] font-medium">
{cardVO.starCount}
</div>
</div>
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg
className="w-[1.4rem] h-[1.4rem] text-black cursor-pointer"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
<svg
className="w-[1.4rem] h-[1.4rem] text-black cursor-pointer"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
onClick={() => window.open(cardVO.githubURL, '_blank')}
><path d="M12.001 2C6.47598 2 2.00098 6.475 2.00098 12C2.00098 16.425 4.86348 20.1625 8.83848 21.4875C9.33848 21.575 9.52598 21.275 9.52598 21.0125C9.52598 20.775 9.51348 19.9875 9.51348 19.15C7.00098 19.6125 6.35098 18.5375 6.15098 17.975C6.03848 17.6875 5.55098 16.8 5.12598 16.5625C4.77598 16.375 4.27598 15.9125 5.11348 15.9C5.90098 15.8875 6.46348 16.625 6.65098 16.925C7.55098 18.4375 8.98848 18.0125 9.56348 17.75C9.65098 17.1 9.91348 16.6625 10.201 16.4125C7.97598 16.1625 5.65098 15.3 5.65098 11.475C5.65098 10.3875 6.03848 9.4875 6.67598 8.7875C6.57598 8.5375 6.22598 7.5125 6.77598 6.1375C6.77598 6.1375 7.61348 5.875 9.52598 7.1625C10.326 6.9375 11.176 6.825 12.026 6.825C12.876 6.825 13.726 6.9375 14.526 7.1625C16.4385 5.8625 17.276 6.1375 17.276 6.1375C17.826 7.5125 17.476 8.5375 17.376 8.7875C18.0135 9.4875 18.401 10.375 18.401 11.475C18.401 15.3125 16.0635 16.1625 13.8385 16.4125C14.201 16.725 14.5135 17.325 14.5135 18.2625C14.5135 19.6 14.501 20.675 14.501 21.0125C14.501 21.275 14.6885 21.5875 15.1885 21.4875C19.259 20.1133 21.9999 16.2963 22.001 12C22.001 6.475 17.526 2 12.001 2Z"></path></svg>
<Button variant="default" size="sm"
>
<path d="M12.001 2C6.47598 2 2.00098 6.475 2.00098 12C2.00098 16.425 4.86348 20.1625 8.83848 21.4875C9.33848 21.575 9.52598 21.275 9.52598 21.0125C9.52598 20.775 9.51348 19.9875 9.51348 19.15C7.00098 19.6125 6.35098 18.5375 6.15098 17.975C6.03848 17.6875 5.55098 16.8 5.12598 16.5625C4.77598 16.375 4.27598 15.9125 5.11348 15.9C5.90098 15.8875 6.46348 16.625 6.65098 16.925C7.55098 18.4375 8.98848 18.0125 9.56348 17.75C9.65098 17.1 9.91348 16.6625 10.201 16.4125C7.97598 16.1625 5.65098 15.3 5.65098 11.475C5.65098 10.3875 6.03848 9.4875 6.67598 8.7875C6.57598 8.5375 6.22598 7.5125 6.77598 6.1375C6.77598 6.1375 7.61348 5.875 9.52598 7.1625C10.326 6.9375 11.176 6.825 12.026 6.825C12.876 6.825 13.726 6.9375 14.526 7.1625C16.4385 5.8625 17.276 6.1375 17.276 6.1375C17.826 7.5125 17.476 8.5375 17.376 8.7875C18.0135 9.4875 18.401 10.375 18.401 11.475C18.401 15.3125 16.0635 16.1625 13.8385 16.4125C14.201 16.725 14.5135 17.325 14.5135 18.2625C14.5135 19.6 14.501 20.675 14.501 21.0125C14.501 21.275 14.6885 21.5875 15.1885 21.4875C19.259 20.1133 21.9999 16.2963 22.001 12C22.001 6.475 17.526 2 12.001 2Z"></path>
</svg>
<Button
variant="default"
size="sm"
onClick={() => {
handleInstallClick(cardVO.githubURL);
}}
className="cursor-pointer"
></Button>
>
</Button>
</div>
</div>
</div>

View File

@@ -1,5 +1,4 @@
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { I18nLabel } from '@/app/infra/entities/common';
import { PipelineConfigTab } from '@/app/infra/entities/pipeline';
export interface ApiResponse<T> {
@@ -32,7 +31,7 @@ export interface Requester {
icon?: string;
spec: {
config: IDynamicFormItemSchema[];
}
};
}
export interface ApiRespProviderLLMModels {
@@ -307,4 +306,4 @@ export interface GetPipelineResponseData {
export interface GetPipelineMetadataResponseData {
configs: PipelineConfigTab[];
}
}

View File

@@ -1,29 +1,29 @@
import { I18nLabel } from '@/app/infra/entities/common';
export interface IDynamicFormItemSchema {
id: string;
default: string | number | boolean | Array<unknown>;
label: I18nLabel;
name: string;
required: boolean;
type: DynamicFormItemType;
description?: I18nLabel;
options?: IDynamicFormItemOption[];
}
id: string;
default: string | number | boolean | Array<unknown>;
label: I18nLabel;
name: string;
required: boolean;
type: DynamicFormItemType;
description?: I18nLabel;
options?: IDynamicFormItemOption[];
}
export enum DynamicFormItemType {
INT = 'integer',
FLOAT = 'float',
BOOLEAN = 'boolean',
STRING = 'string',
STRING_ARRAY = 'array[string]',
SELECT = 'select',
LLM_MODEL_SELECTOR = 'llm-model-selector',
PROMPT_EDITOR = 'prompt-editor',
UNKNOWN = 'unknown',
}
INT = 'integer',
FLOAT = 'float',
BOOLEAN = 'boolean',
STRING = 'string',
STRING_ARRAY = 'array[string]',
SELECT = 'select',
LLM_MODEL_SELECTOR = 'llm-model-selector',
PROMPT_EDITOR = 'prompt-editor',
UNKNOWN = 'unknown',
}
export interface IDynamicFormItemOption {
name: string;
label: I18nLabel;
}
name: string;
label: I18nLabel;
}

View File

@@ -20,4 +20,4 @@ export interface PipelineConfigStage {
label: I18nLabel;
description?: I18nLabel;
config: IDynamicFormItemSchema[];
}
}

View File

@@ -28,10 +28,8 @@ import {
MarketPluginResponse,
GetPipelineResponseData,
GetPipelineMetadataResponseData,
AsyncTask
AsyncTask,
} from '@/app/infra/entities/api';
import { toast } from "sonner"
type JSONValue = string | number | boolean | JSONObject | JSONArray | null;
interface JSONObject {
@@ -130,9 +128,8 @@ class HttpClient {
switch (status) {
case 401:
console.log('401 error: ', errMessage, error.request);
console.log('responseURL', error.request.responseURL)
console.log('responseURL', error.request.responseURL);
localStorage.removeItem('token');
if (!error.request.responseURL.includes('/check-token')) {
window.location.href = '/login';
@@ -231,7 +228,10 @@ class HttpClient {
}
public getProviderRequesterIconURL(name: string): string {
return this.instance.defaults.baseURL + `/api/v1/provider/requesters/${name}/icon`;
return (
this.instance.defaults.baseURL +
`/api/v1/provider/requesters/${name}/icon`
);
}
// ============ Provider Model LLM ============
@@ -289,7 +289,9 @@ class HttpClient {
}
public getAdapterIconURL(name: string): string {
return this.instance.defaults.baseURL + `/api/v1/platform/adapters/${name}/icon`;
return (
this.instance.defaults.baseURL + `/api/v1/platform/adapters/${name}/icon`
);
}
// ============ Platform Bots ============
@@ -418,7 +420,7 @@ class HttpClient {
}
// export const httpClient = new HttpClient("https://version-4.langbot.dev");
export const httpClient = new HttpClient("http://localhost:5300");
export const httpClient = new HttpClient('http://localhost:5300');
// 临时写法未来两种Client都继承自HttpClient父类不允许共享方法
export const spaceClient = new HttpClient('https://space.langbot.app');

View File

@@ -1,12 +1,16 @@
'use client';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from '@/components/ui/card';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import {
Form,
FormControl,
@@ -14,17 +18,17 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useEffect, useState } from 'react';
} from '@/components/ui/form';
import { useEffect } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { useRouter } from 'next/navigation';
import { Mail, Lock } from "lucide-react";
import { Mail, Lock } from 'lucide-react';
import langbotIcon from '@/app/assets/langbot-logo.webp';
import { toast } from "sonner"
import { toast } from 'sonner';
const formSchema = z.object({
email: z.string().email("请输入有效的邮箱地址"),
password: z.string().min(1, "请输入密码"),
email: z.string().email('请输入有效的邮箱地址'),
password: z.string().min(1, '请输入密码'),
});
export default function Login() {
@@ -33,8 +37,8 @@ export default function Login() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: "",
email: '',
password: '',
},
});
@@ -57,7 +61,8 @@ export default function Login() {
}
function checkIfAlreadyLoggedIn() {
httpClient.checkUserToken()
httpClient
.checkUserToken()
.then((res) => {
if (res.token) {
localStorage.setItem('token', res.token);
@@ -79,12 +84,12 @@ export default function Login() {
localStorage.setItem('token', res.token);
console.log('login success: ', res);
router.push('/home');
toast.success("登录成功");
toast.success('登录成功');
})
.catch((err) => {
console.log('login error: ', err);
toast.error("登录失败,请检查邮箱和密码是否正确");
toast.error('登录失败,请检查邮箱和密码是否正确');
});
}
@@ -92,13 +97,15 @@ export default function Login() {
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[360px]">
<CardHeader>
<img src={langbotIcon.src} alt="LangBot" className="w-16 h-16 mb-4 mx-auto" />
<img
src={langbotIcon.src}
alt="LangBot"
className="w-16 h-16 mb-4 mx-auto"
/>
<CardTitle className="text-2xl text-center">
LangBot 👋
</CardTitle>
<CardDescription className="text-center">
</CardDescription>
<CardDescription className="text-center"></CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
@@ -146,10 +153,7 @@ export default function Login() {
)}
/>
<Button
type="submit"
className="w-full mt-4 cursor-pointer"
>
<Button type="submit" className="w-full mt-4 cursor-pointer">
</Button>
</form>

View File

@@ -1,8 +1,7 @@
'use client';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { GithubIcon } from "lucide-react";
import { Button } from '@/components/ui/button';
export default function NotFound() {
const router = useRouter();
@@ -22,7 +21,8 @@ export default function NotFound() {
</h1>
<p className="text-base text-gray-600 max-w-[450px] mx-auto mb-8">
URL
URL
</p>
</div>

View File

@@ -1,12 +1,16 @@
'use client';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from '@/components/ui/card';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import {
Form,
FormControl,
@@ -14,18 +18,17 @@ import {
FormItem,
FormLabel,
FormMessage,
FormDescription,
} from "@/components/ui/form";
import { useEffect, useState } from 'react';
} from '@/components/ui/form';
import { useEffect } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { useRouter } from 'next/navigation';
import { Mail, Lock } from "lucide-react";
import { Mail, Lock } from 'lucide-react';
import langbotIcon from '@/app/assets/langbot-logo.webp';
import { toast } from "sonner";
import { toast } from 'sonner';
const formSchema = z.object({
email: z.string().email("请输入有效的邮箱地址"),
password: z.string().min(1, "请输入密码"),
email: z.string().email('请输入有效的邮箱地址'),
password: z.string().min(1, '请输入密码'),
});
export default function Register() {
@@ -34,8 +37,8 @@ export default function Register() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: "",
email: '',
password: '',
},
});
@@ -65,12 +68,12 @@ export default function Register() {
.initUser(username, password)
.then((res) => {
console.log('init user success: ', res);
toast.success("初始化成功 请登录");
toast.success('初始化成功 请登录');
router.push('/login');
})
.catch((err) => {
console.log('init user error: ', err);
toast.error("初始化失败:" + err.message);
toast.error('初始化失败:' + err.message);
});
}
@@ -78,7 +81,11 @@ export default function Register() {
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[360px]">
<CardHeader>
<img src={langbotIcon.src} alt="LangBot" className="w-16 h-16 mb-4 mx-auto" />
<img
src={langbotIcon.src}
alt="LangBot"
className="w-16 h-16 mb-4 mx-auto"
/>
<CardTitle className="text-2xl text-center">
LangBot 👋
</CardTitle>
@@ -134,10 +141,7 @@ export default function Register() {
)}
/>
<Button
type="submit"
className="w-full mt-4 cursor-pointer"
>
<Button type="submit" className="w-full mt-4 cursor-pointer">
</Button>
</form>

View File

@@ -1,29 +1,29 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
{
variants: {
variant: {
default: "bg-card text-card-foreground",
default: 'bg-card text-card-foreground',
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
)
},
);
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
@@ -31,36 +31,36 @@ function Alert({
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
className
'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
className,
)}
{...props}
/>
)
);
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
className
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className,
)}
{...props}
/>
)
);
}
export { Alert, AlertTitle, AlertDescription }
export { Alert, AlertTitle, AlertDescription };

View File

@@ -1,38 +1,38 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
)
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
const Comp = asChild ? Slot : 'span';
return (
<Comp
@@ -40,7 +40,7 @@ function Badge({
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
);
}
export { Badge, badgeVariants }
export { Badge, badgeVariants };

View File

@@ -1,8 +1,8 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -10,30 +10,30 @@ const buttonVariants = cva(
variants: {
variant: {
default:
"bg-[#2288ee] text-primary-foreground shadow-xs hover:bg-[#2277e0]",
'bg-[#2288ee] text-primary-foreground shadow-xs hover:bg-[#2277e0]',
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
)
},
);
function Button({
className,
@@ -41,11 +41,11 @@ function Button({
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : 'button';
return (
<Comp
@@ -53,7 +53,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
);
}
export { Button, buttonVariants }
export { Button, buttonVariants };

View File

@@ -1,84 +1,84 @@
import * as React from "react"
import * as React from 'react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Card({ className, ...props }: React.ComponentProps<"div">) {
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
className,
)}
{...props}
/>
)
);
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
)
);
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
className={cn('leading-none font-semibold', className)}
{...props}
/>
)
);
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
);
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
)
);
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
className={cn('px-6', className)}
{...props}
/>
)
);
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
)
);
}
export {
@@ -89,4 +89,4 @@ export {
CardAction,
CardDescription,
CardContent,
}
};

View File

@@ -1,10 +1,10 @@
"use client"
'use client';
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from 'lucide-react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Checkbox({
className,
@@ -14,8 +14,8 @@ function Checkbox({
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
@@ -26,7 +26,7 @@ function Checkbox({
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
);
}
export { Checkbox }
export { Checkbox };

View File

@@ -1,33 +1,33 @@
"use client"
'use client';
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { XIcon } from 'lucide-react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({
@@ -38,12 +38,12 @@ function DialogOverlay({
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
);
}
function DialogContent({
@@ -57,8 +57,8 @@ function DialogContent({
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className,
)}
{...props}
>
@@ -69,30 +69,30 @@ function DialogContent({
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
)
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...props}
/>
)
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
className,
)}
{...props}
/>
)
);
}
function DialogTitle({
@@ -102,10 +102,10 @@ function DialogTitle({
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
className={cn('text-lg leading-none font-semibold', className)}
{...props}
/>
)
);
}
function DialogDescription({
@@ -115,10 +115,10 @@ function DialogDescription({
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
);
}
export {
@@ -132,4 +132,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
}
};

View File

@@ -1,8 +1,8 @@
"use client"
'use client';
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import {
Controller,
FormProvider,
@@ -11,23 +11,23 @@ import {
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
} from 'react-hook-form';
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
const Form = FormProvider
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
@@ -39,21 +39,21 @@ const FormField = <
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState } = useFormContext();
const formState = useFormState({ name: fieldContext.name });
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
throw new Error('useFormField should be used within <FormField>');
}
const { id } = itemContext
const { id } = itemContext;
return {
id,
@@ -62,50 +62,51 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
};
};
type FormItemContextValue = {
id: string
}
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
{} as FormItemContextValue,
);
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId()
function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
className={cn('grid gap-2', className)}
{...props}
/>
</FormItemContext.Provider>
)
);
}
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField()
const { error, formItemId } = useFormField();
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
className={cn('data-[error=true]:text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
)
);
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
@@ -119,40 +120,40 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
aria-invalid={!!error}
{...props}
/>
)
);
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
const { formDescriptionId } = useFormField()
function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
const { formDescriptionId } = useFormField();
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
);
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : props.children
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? '') : props.children;
if (!body) {
return null
return null;
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
className={cn('text-destructive text-sm', className)}
{...props}
>
{body}
</p>
)
);
}
export {
@@ -164,4 +165,4 @@ export {
FormDescription,
FormMessage,
FormField,
}
};

View File

@@ -1,21 +1,21 @@
import * as React from "react"
import * as React from 'react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
)}
{...props}
/>
)
);
}
export { Input }
export { Input };

View File

@@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Label({
className,
@@ -13,12 +13,12 @@ function Label({
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
)
);
}
export { Label }
export { Label };

View File

@@ -1,68 +1,68 @@
import * as React from "react"
import * as React from 'react';
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
} from 'lucide-react';
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from '@/lib/utils';
import { Button, buttonVariants } from '@/components/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
)
);
}
function PaginationContent({
className,
...props
}: React.ComponentProps<"ul">) {
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
)
);
}
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} />
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
className,
isActive,
size = "icon",
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? "page" : undefined}
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
variant: isActive ? 'outline' : 'ghost',
size,
}),
className
className,
)}
{...props}
/>
)
);
}
function PaginationPrevious({
@@ -73,13 +73,13 @@ function PaginationPrevious({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon className="text-black" />
<span className="hidden sm:block"></span>
</PaginationLink>
)
);
}
function PaginationNext({
@@ -90,30 +90,30 @@ function PaginationNext({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<span className="hidden sm:block"></span>
<ChevronRightIcon className="text-black" />
</PaginationLink>
)
);
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
)
);
}
export {
@@ -124,4 +124,4 @@ export {
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
};

View File

@@ -1,36 +1,36 @@
"use client"
'use client';
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
size = 'default',
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
size?: 'sm' | 'default';
}) {
return (
<SelectPrimitive.Trigger
@@ -38,7 +38,7 @@ function SelectTrigger({
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
className,
)}
{...props}
>
@@ -47,13 +47,13 @@ function SelectTrigger({
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
);
}
function SelectContent({
className,
children,
position = "popper",
position = 'popper',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
@@ -61,10 +61,10 @@ function SelectContent({
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
)}
position={position}
{...props}
@@ -72,9 +72,9 @@ function SelectContent({
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
)}
>
{children}
@@ -82,7 +82,7 @@ function SelectContent({
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
);
}
function SelectLabel({
@@ -92,10 +92,10 @@ function SelectLabel({
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
)
);
}
function SelectItem({
@@ -108,7 +108,7 @@ function SelectItem({
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
className,
)}
{...props}
>
@@ -119,7 +119,7 @@ function SelectItem({
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
);
}
function SelectSeparator({
@@ -129,10 +129,10 @@ function SelectSeparator({
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
)
);
}
function SelectScrollUpButton({
@@ -143,14 +143,14 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
);
}
function SelectScrollDownButton({
@@ -161,14 +161,14 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
);
}
export {
@@ -182,4 +182,4 @@ export {
SelectSeparator,
SelectTrigger,
SelectValue,
}
};

View File

@@ -1,25 +1,25 @@
"use client"
'use client';
import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner"
import { useTheme } from 'next-themes';
import { Toaster as Sonner, ToasterProps } from 'sonner';
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
const { theme = 'system' } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
theme={theme as ToasterProps['theme']}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
'--normal-bg': 'var(--popover)',
'--normal-text': 'var(--popover-foreground)',
'--normal-border': 'var(--border)',
} as React.CSSProperties
}
{...props}
/>
)
}
);
};
export { Toaster }
export { Toaster };

View File

@@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch"
import * as React from 'react';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Switch({
className,
@@ -13,19 +13,19 @@ function Switch({
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitive.Root>
)
);
}
export { Switch }
export { Switch };

View File

@@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Tabs({
className,
@@ -12,10 +12,10 @@ function Tabs({
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
className={cn('flex flex-col gap-2', className)}
{...props}
/>
)
);
}
function TabsList({
@@ -26,12 +26,12 @@ function TabsList({
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
/>
)
);
}
function TabsTrigger({
@@ -43,11 +43,11 @@ function TabsTrigger({
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
className,
)}
{...props}
/>
)
);
}
function TabsContent({
@@ -57,10 +57,10 @@ function TabsContent({
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
className={cn('flex-1 outline-none', className)}
{...props}
/>
)
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@@ -1,18 +1,18 @@
"use client"
'use client';
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"
import * as React from 'react';
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
import { type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
import { cn } from '@/lib/utils';
import { toggleVariants } from '@/components/ui/toggle';
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
size: 'default',
variant: 'default',
});
function ToggleGroup({
className,
@@ -28,8 +28,8 @@ function ToggleGroup({
data-variant={variant}
data-size={size}
className={cn(
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
className
'group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs',
className,
)}
{...props}
>
@@ -37,7 +37,7 @@ function ToggleGroup({
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
)
);
}
function ToggleGroupItem({
@@ -48,7 +48,7 @@ function ToggleGroupItem({
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext)
const context = React.useContext(ToggleGroupContext);
return (
<ToggleGroupPrimitive.Item
@@ -60,14 +60,14 @@ function ToggleGroupItem({
variant: context.variant || variant,
size: context.size || size,
}),
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
className
'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l',
className,
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
);
}
export { ToggleGroup, ToggleGroupItem }
export { ToggleGroup, ToggleGroupItem };

View File

@@ -1,32 +1,32 @@
"use client"
'use client';
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
{
variants: {
variant: {
default: "bg-transparent",
default: 'bg-transparent',
outline:
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
},
size: {
default: "h-9 px-2 min-w-9",
sm: "h-8 px-1.5 min-w-8",
lg: "h-10 px-2.5 min-w-10",
default: 'h-9 px-2 min-w-9',
sm: 'h-8 px-1.5 min-w-8',
lg: 'h-10 px-2.5 min-w-10',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
)
},
);
function Toggle({
className,
@@ -41,7 +41,7 @@ function Toggle({
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
)
);
}
export { Toggle, toggleVariants }
export { Toggle, toggleVariants };

View File

@@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}