mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
feat: setting plugin config
This commit is contained in:
@@ -17,17 +17,6 @@ class PluginsRouterGroup(group.RouterGroup):
|
||||
|
||||
return self.success(data={'plugins': plugins})
|
||||
|
||||
@self.route(
|
||||
'/<author>/<plugin_name>/toggle',
|
||||
methods=['PUT'],
|
||||
auth_type=group.AuthType.USER_TOKEN,
|
||||
)
|
||||
async def _(author: str, plugin_name: str) -> str:
|
||||
data = await quart.request.json
|
||||
target_enabled = data.get('target_enabled')
|
||||
await self.ap.plugin_mgr.update_plugin_switch(plugin_name, target_enabled)
|
||||
return self.success()
|
||||
|
||||
@self.route(
|
||||
'/<author>/<plugin_name>/upgrade',
|
||||
methods=['POST'],
|
||||
@@ -76,21 +65,16 @@ class PluginsRouterGroup(group.RouterGroup):
|
||||
plugin = await self.ap.plugin_connector.get_plugin_info(author, plugin_name)
|
||||
if plugin is None:
|
||||
return self.http_status(404, -1, 'plugin not found')
|
||||
|
||||
if quart.request.method == 'GET':
|
||||
return self.success(data={'config': plugin['plugin_config']})
|
||||
elif quart.request.method == 'PUT':
|
||||
data = await quart.request.json
|
||||
|
||||
await self.ap.plugin_mgr.set_plugin_config(plugin, data)
|
||||
await self.ap.plugin_connector.set_plugin_config(author, plugin_name, data)
|
||||
|
||||
return self.success(data={})
|
||||
|
||||
@self.route('/reorder', methods=['PUT'], auth_type=group.AuthType.USER_TOKEN)
|
||||
async def _() -> str:
|
||||
data = await quart.request.json
|
||||
await self.ap.plugin_mgr.reorder_plugins(data.get('plugins'))
|
||||
return self.success()
|
||||
|
||||
@self.route('/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
|
||||
async def _() -> str:
|
||||
data = await quart.request.json
|
||||
|
||||
@@ -150,6 +150,9 @@ class PluginRuntimeConnector:
|
||||
async def get_plugin_info(self, author: str, plugin_name: str) -> dict[str, Any]:
|
||||
return await self.handler.get_plugin_info(author, plugin_name)
|
||||
|
||||
async def set_plugin_config(self, plugin_author: str, plugin_name: str, config: dict[str, Any]) -> dict[str, Any]:
|
||||
return await self.handler.set_plugin_config(plugin_author, plugin_name, config)
|
||||
|
||||
async def emit_event(
|
||||
self,
|
||||
event: events.BaseEventModel,
|
||||
|
||||
@@ -486,6 +486,29 @@ class RuntimeConnectionHandler(handler.Handler):
|
||||
)
|
||||
return result['plugin']
|
||||
|
||||
async def set_plugin_config(self, plugin_author: str, plugin_name: str, config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Set plugin config"""
|
||||
# update plugin setting
|
||||
await self.ap.persistence_mgr.execute_async(
|
||||
sqlalchemy.update(persistence_plugin.PluginSetting)
|
||||
.where(persistence_plugin.PluginSetting.plugin_author == plugin_author)
|
||||
.where(persistence_plugin.PluginSetting.plugin_name == plugin_name)
|
||||
.values(config=config)
|
||||
)
|
||||
|
||||
# restart plugin
|
||||
gen = self.call_action_generator(
|
||||
LangBotToRuntimeAction.RESTART_PLUGIN,
|
||||
{
|
||||
'plugin_author': plugin_author,
|
||||
'plugin_name': plugin_name,
|
||||
},
|
||||
)
|
||||
async for ret in gen:
|
||||
pass
|
||||
|
||||
return {}
|
||||
|
||||
async def emit_event(
|
||||
self,
|
||||
event_context: dict[str, Any],
|
||||
|
||||
@@ -271,9 +271,15 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
||||
<PluginForm
|
||||
pluginAuthor={selectedPlugin.author}
|
||||
pluginName={selectedPlugin.name}
|
||||
onFormSubmit={() => {
|
||||
onFormSubmit={(timeout?: number) => {
|
||||
setModalOpen(false);
|
||||
getPluginList();
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
getPluginList();
|
||||
}, timeout);
|
||||
} else {
|
||||
getPluginList();
|
||||
}
|
||||
}}
|
||||
onFormCancel={() => {
|
||||
setModalOpen(false);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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 { toast } from 'sonner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TFunction } from 'i18next';
|
||||
import {
|
||||
@@ -81,26 +78,8 @@ export default function PluginCardComponent({
|
||||
onUpgradeClick: (cardVO: PluginCardVO) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [enabled, setEnabled] = useState(cardVO.enabled);
|
||||
const [switchEnable, setSwitchEnable] = useState(true);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
function handleEnable(e: React.MouseEvent) {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
setSwitchEnable(false);
|
||||
httpClient
|
||||
.togglePlugin(cardVO.author, cardVO.name, !enabled)
|
||||
.then(() => {
|
||||
setEnabled(!enabled);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(t('plugins.modifyFailed') + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setSwitchEnable(true);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -198,14 +177,7 @@ export default function PluginCardComponent({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-between h-full">
|
||||
<div className="flex items-center justify-center">
|
||||
<Switch
|
||||
className="cursor-pointer"
|
||||
checked={enabled}
|
||||
onClick={(e) => handleEnable(e)}
|
||||
disabled={!switchEnable}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center"></div>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function PluginForm({
|
||||
}: {
|
||||
pluginAuthor: string;
|
||||
pluginName: string;
|
||||
onFormSubmit: () => void;
|
||||
onFormSubmit: (timeout?: number) => void;
|
||||
onFormCancel: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -37,14 +37,19 @@ export default function PluginForm({
|
||||
|
||||
const handleSubmit = async (values: object) => {
|
||||
setIsLoading(true);
|
||||
const isDebugPlugin = pluginInfo?.debug;
|
||||
httpClient
|
||||
.updatePluginConfig(pluginAuthor, pluginName, values)
|
||||
.then(() => {
|
||||
onFormSubmit();
|
||||
toast.success('保存成功');
|
||||
toast.success(
|
||||
isDebugPlugin
|
||||
? t('plugins.saveConfigSuccessDebugPlugin')
|
||||
: t('plugins.saveConfigSuccessNormal'),
|
||||
);
|
||||
onFormSubmit(1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error('保存失败:' + error.message);
|
||||
toast.error(t('plugins.saveConfigError') + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
ApiRespPlugins,
|
||||
ApiRespPlugin,
|
||||
ApiRespPluginConfig,
|
||||
PluginReorderElement,
|
||||
AsyncTaskCreatedResp,
|
||||
ApiRespSystemInfo,
|
||||
ApiRespAsyncTasks,
|
||||
@@ -226,20 +225,6 @@ export class BackendClient extends BaseHttpClient {
|
||||
return this.put(`/api/v1/plugins/${author}/${name}/config`, config);
|
||||
}
|
||||
|
||||
public togglePlugin(
|
||||
author: string,
|
||||
name: string,
|
||||
target_enabled: boolean,
|
||||
): Promise<object> {
|
||||
return this.put(`/api/v1/plugins/${author}/${name}/toggle`, {
|
||||
target_enabled,
|
||||
});
|
||||
}
|
||||
|
||||
public reorderPlugins(plugins: PluginReorderElement[]): Promise<object> {
|
||||
return this.put('/api/v1/plugins/reorder', { plugins });
|
||||
}
|
||||
|
||||
public installPluginFromGithub(
|
||||
source: string,
|
||||
): Promise<AsyncTaskCreatedResp> {
|
||||
|
||||
@@ -170,7 +170,7 @@ const enUS = {
|
||||
saveConfig: 'Save Config',
|
||||
saving: 'Saving...',
|
||||
confirmDeletePlugin:
|
||||
'Are you sure you want to delete the plugin ({{author}}/{{name}})?',
|
||||
'Are you sure you want to delete the plugin ({{author}}/{{name}})? This will also delete the plugin configuration.',
|
||||
confirmDelete: 'Confirm Delete',
|
||||
deleteError: 'Delete failed: ',
|
||||
close: 'Close',
|
||||
@@ -205,6 +205,10 @@ const enUS = {
|
||||
updating: 'Updating...',
|
||||
updateSuccess: 'Plugin updated successfully',
|
||||
updateError: 'Update failed: ',
|
||||
saveConfigSuccessNormal: 'Configuration saved successfully',
|
||||
saveConfigSuccessDebugPlugin:
|
||||
'Configuration saved successfully, please manually restart the plugin',
|
||||
saveConfigError: 'Configuration save failed: ',
|
||||
},
|
||||
market: {
|
||||
searchPlaceholder: 'Search plugins...',
|
||||
|
||||
@@ -170,7 +170,7 @@ const jaJP = {
|
||||
saveConfig: '設定を保存',
|
||||
saving: '保存中...',
|
||||
confirmDeletePlugin:
|
||||
'プラグイン「{{author}}/{{name}}」を削除してもよろしいですか?',
|
||||
'プラグイン「{{author}}/{{name}}」を削除してもよろしいですか?この操作により、プラグインの設定も削除されます。',
|
||||
confirmDelete: '削除を確認',
|
||||
deleteError: '削除に失敗しました:',
|
||||
close: '閉じる',
|
||||
@@ -205,6 +205,10 @@ const jaJP = {
|
||||
updating: '更新中...',
|
||||
updateSuccess: 'プラグインの更新に成功しました',
|
||||
updateError: '更新に失敗しました:',
|
||||
saveConfigSuccessNormal: '設定を保存しました',
|
||||
saveConfigSuccessDebugPlugin:
|
||||
'設定を保存しました。手動でプラグインを再起動してください',
|
||||
saveConfigError: '設定の保存に失敗しました:',
|
||||
},
|
||||
market: {
|
||||
searchPlaceholder: 'プラグインを検索...',
|
||||
|
||||
@@ -166,7 +166,8 @@ const zhHans = {
|
||||
cancel: '取消',
|
||||
saveConfig: '保存配置',
|
||||
saving: '保存中...',
|
||||
confirmDeletePlugin: '你确定要删除插件({{author}}/{{name}})吗?',
|
||||
confirmDeletePlugin:
|
||||
'你确定要删除插件({{author}}/{{name}})吗?这将同时删除插件的配置。',
|
||||
confirmDelete: '确认删除',
|
||||
deleteError: '删除失败:',
|
||||
close: '关闭',
|
||||
@@ -199,6 +200,9 @@ const zhHans = {
|
||||
updating: '更新中...',
|
||||
updateSuccess: '插件更新成功',
|
||||
updateError: '更新失败:',
|
||||
saveConfigSuccessNormal: '保存配置成功',
|
||||
saveConfigSuccessDebugPlugin: '保存配置成功,请手动重启插件',
|
||||
saveConfigError: '保存配置失败:',
|
||||
},
|
||||
market: {
|
||||
searchPlaceholder: '搜索插件...',
|
||||
|
||||
Reference in New Issue
Block a user