feat: plugin config editor form

This commit is contained in:
Junyan Qin
2025-05-09 18:52:04 +08:00
parent a5f3331c24
commit 7c15f3ba12
9 changed files with 135 additions and 16 deletions

View File

@@ -183,13 +183,6 @@ export default function BotForm({
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap);
}
async function onCreateMode() { }
function onEditMode() {
console.log('onEditMode', form.getValues());
}
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
const bot = (await httpClient.getBot(botId)).bot;
return {
@@ -503,7 +496,7 @@ export default function BotForm({
</Button>
</>
)}
<Button type="button" onClick={() => onFormCancel()}>
<Button type="button" variant="outline" onClick={() => onFormCancel()}>
</Button>
</div>

View File

@@ -24,8 +24,8 @@
.langbotIcon {
width: 2.8rem;
height: 2.8rem;
color: #fbfbfb;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.langbotTextContainer {

View File

@@ -20,6 +20,7 @@
border-radius: 1.5rem 0 0 1.5rem;
margin-left: 0.6rem;
margin-top: 0.6rem;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.05);
}
.mainContent {

View File

@@ -467,7 +467,7 @@ export default function LLMForm({
</Button>
)}
<Button type="button" onClick={() => onFormCancel()}>
<Button type="button" variant="outline" onClick={() => onFormCancel()}>
</Button>
</DialogFooter>

View File

@@ -4,6 +4,7 @@ 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';
@@ -23,6 +24,8 @@ export interface PluginInstalledComponentRef {
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();
@@ -57,13 +60,38 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>((props,
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] p-6">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
{selectedPlugin && (
<PluginForm
pluginAuthor={selectedPlugin.author}
pluginName={selectedPlugin.name}
onFormSubmit={() => {
setModalOpen(false);
getPluginList();
}}
onFormCancel={() => {
setModalOpen(false);
}}
/>
)}
</DialogContent>
</Dialog>
{pluginList.map((vo, index) => {
return (
<div key={index}>
<div key={index} onClick={() => handlePluginClick(vo)}>
<PluginCardComponent cardVO={vo} />
</div>
);

View File

@@ -0,0 +1,88 @@
import { useState, useEffect } from 'react';
import { ApiRespPlugin, 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 {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
export default function PluginForm({
pluginAuthor,
pluginName,
onFormSubmit,
onFormCancel,
}: {
pluginAuthor: string;
pluginName: string;
onFormSubmit: () => void;
onFormCancel: () => void;
}) {
const [pluginInfo, setPluginInfo] = useState<Plugin>();
const [pluginConfig, setPluginConfig] = useState<ApiRespPluginConfig>();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// 获取插件信息
httpClient.getPlugin(pluginAuthor, pluginName).then((res) => {
setPluginInfo(res.plugin);
});
// 获取插件配置
httpClient.getPluginConfig(pluginAuthor, pluginName).then((res) => {
setPluginConfig(res);
});
}, [pluginAuthor, pluginName]);
const handleSubmit = async (values: object) => {
setIsLoading(true);
try {
await httpClient.updatePluginConfig(pluginAuthor, pluginName, values);
onFormSubmit();
} catch (error) {
console.error('更新插件配置失败:', error);
} finally {
setIsLoading(false);
}
};
if (!pluginInfo || !pluginConfig) {
return <div>...</div>;
}
return (
<div>
<div className="space-y-4">
<div className="text-lg font-medium"></div>
<div className="text-sm text-gray-500">
{pluginInfo.description.zh_CN}
</div>
<DynamicFormComponent
itemConfigList={pluginInfo.config_schema}
initialValues={pluginConfig.config}
onSubmit={handleSubmit}
/>
</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
type="submit"
onClick={() => handleSubmit(pluginConfig.config)}
disabled={isLoading}
>
{isLoading ? '保存中...' : '保存配置'}
</Button>
<Button type="button" variant="outline" onClick={onFormCancel}>
</Button>
</div>
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
'use client';
import { useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';
import styles from '@/app/home/plugins/plugins.module.css';
import { PluginMarketCardVO } from '@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO';
import PluginMarketCardComponent from '@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent';
@@ -31,6 +31,7 @@ export default function PluginMarketComponent({
const [loading, setLoading] = useState(false);
const [sortByValue, setSortByValue] = useState<string>('stars');
const [sortOrderValue, setSortOrderValue] = useState<string>('DESC');
const searchTimeout = useRef<NodeJS.Timeout | null>(null);
const pageSize = 10;
useEffect(() => {
@@ -43,10 +44,18 @@ export default function PluginMarketComponent({
}
function onInputSearchKeyword(keyword: string) {
// 这里记得加防抖,暂时没加
setSearchKeyword(keyword);
setNowPage(1);
getPluginList(1, keyword);
// 清除之前的定时器
if (searchTimeout.current) {
clearTimeout(searchTimeout.current);
}
// 设置新的定时器
searchTimeout.current = setTimeout(() => {
setNowPage(1);
getPluginList(1, keyword);
}, 500);
}
function getPluginList(

View File

@@ -145,7 +145,7 @@ export interface Plugin {
main_file: string;
pkg_path: string;
repository: string;
config_schema: object;
config_schema: IDynamicFormItemSchema[];
}
export interface ApiRespPluginConfig {