mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 23:07:14 +00:00
fix(web): unify processor creation and entity detail loading states
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import EntityLoadState from '@/components/EntityLoadState';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { ApiRespPluginConfig } from '@/app/infra/entities/api';
|
||||
import { Plugin } from '@/app/infra/entities/plugin';
|
||||
@@ -25,6 +26,8 @@ export default function PluginForm({
|
||||
onFormSubmit: (timeout?: number) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [loadFailed, setLoadFailed] = useState(false);
|
||||
const [loadAttempt, setLoadAttempt] = useState(0);
|
||||
const [pluginInfo, setPluginInfo] = useState<Plugin>();
|
||||
const [pluginConfig, setPluginConfig] = useState<ApiRespPluginConfig>();
|
||||
const [isSaving, setIsLoading] = useState(false);
|
||||
@@ -33,36 +36,55 @@ export default function PluginForm({
|
||||
const initialFileKeys = useRef<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoadFailed(false);
|
||||
setPluginInfo(undefined);
|
||||
setPluginConfig(undefined);
|
||||
// 获取插件信息
|
||||
httpClient.getPlugin(pluginAuthor, pluginName).then((res) => {
|
||||
setPluginInfo(res.plugin);
|
||||
});
|
||||
httpClient
|
||||
.getPlugin(pluginAuthor, pluginName)
|
||||
.then((res) => {
|
||||
if (cancelled) return;
|
||||
setPluginInfo(res.plugin);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setLoadFailed(true);
|
||||
});
|
||||
// 获取插件配置
|
||||
httpClient.getPluginConfig(pluginAuthor, pluginName).then((res) => {
|
||||
setPluginConfig(res);
|
||||
httpClient
|
||||
.getPluginConfig(pluginAuthor, pluginName)
|
||||
.then((res) => {
|
||||
if (cancelled) return;
|
||||
setPluginConfig(res);
|
||||
|
||||
// 提取初始配置中的所有文件 key
|
||||
const extractFileKeys = (obj: any): string[] => {
|
||||
const keys: string[] = [];
|
||||
if (obj && typeof obj === 'object') {
|
||||
if ('file_key' in obj && typeof obj.file_key === 'string') {
|
||||
keys.push(obj.file_key);
|
||||
}
|
||||
for (const value of Object.values(obj)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => keys.push(...extractFileKeys(item)));
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
keys.push(...extractFileKeys(value));
|
||||
// 提取初始配置中的所有文件 key
|
||||
const extractFileKeys = (obj: any): string[] => {
|
||||
const keys: string[] = [];
|
||||
if (obj && typeof obj === 'object') {
|
||||
if ('file_key' in obj && typeof obj.file_key === 'string') {
|
||||
keys.push(obj.file_key);
|
||||
}
|
||||
for (const value of Object.values(obj)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => keys.push(...extractFileKeys(item)));
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
keys.push(...extractFileKeys(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
return keys;
|
||||
};
|
||||
|
||||
const fileKeys = extractFileKeys(res.config);
|
||||
initialFileKeys.current = new Set(fileKeys);
|
||||
});
|
||||
}, [pluginAuthor, pluginName]);
|
||||
const fileKeys = extractFileKeys(res.config);
|
||||
initialFileKeys.current = new Set(fileKeys);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setLoadFailed(true);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [pluginAuthor, pluginName, loadAttempt]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -132,13 +154,11 @@ export default function PluginForm({
|
||||
}
|
||||
};
|
||||
|
||||
if (!pluginInfo || !pluginConfig) {
|
||||
if (loadFailed)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full mb-[2rem]">
|
||||
{t('plugins.loading')}
|
||||
</div>
|
||||
<EntityLoadState error onRetry={() => setLoadAttempt((n) => n + 1)} />
|
||||
);
|
||||
}
|
||||
if (!pluginInfo || !pluginConfig) return <EntityLoadState />;
|
||||
|
||||
return (
|
||||
<div className="min-w-0 max-w-full space-y-4">
|
||||
|
||||
+12
-5
@@ -1,3 +1,4 @@
|
||||
import EntityLoadState from '@/components/EntityLoadState';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -88,12 +89,15 @@ export default function PluginReadme({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [readme, setReadme] = useState<string>('');
|
||||
const [isLoadingReadme, setIsLoadingReadme] = useState(false);
|
||||
const [loadFailed, setLoadFailed] = useState(false);
|
||||
const [loadAttempt, setLoadAttempt] = useState(0);
|
||||
const [isLoadingReadme, setIsLoadingReadme] = useState(true);
|
||||
|
||||
const language = getAPILanguageCode();
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch plugin README
|
||||
setLoadFailed(false);
|
||||
setIsLoadingReadme(true);
|
||||
httpClient
|
||||
.getPluginReadme(pluginAuthor, pluginName, language)
|
||||
@@ -101,19 +105,22 @@ export default function PluginReadme({
|
||||
setReadme(res.readme);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadFailed(true);
|
||||
setReadme('');
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingReadme(false);
|
||||
});
|
||||
}, [pluginAuthor, pluginName]);
|
||||
}, [pluginAuthor, pluginName, language, loadAttempt]);
|
||||
|
||||
if (loadFailed)
|
||||
return (
|
||||
<EntityLoadState error onRetry={() => setLoadAttempt((n) => n + 1)} />
|
||||
);
|
||||
return (
|
||||
<div className="w-full h-full overflow-auto overscroll-none">
|
||||
{isLoadingReadme ? (
|
||||
<div className="p-6 text-sm text-gray-500 dark:text-gray-400">
|
||||
{t('plugins.loadingReadme')}
|
||||
</div>
|
||||
<EntityLoadState />
|
||||
) : readme ? (
|
||||
<div className="markdown-body p-6 max-w-none pt-0">
|
||||
<ReactMarkdown
|
||||
|
||||
Reference in New Issue
Block a user