fix(web): unify processor creation and entity detail loading states

This commit is contained in:
Hyu
2026-09-15 13:51:39 +08:00
parent d497defbf7
commit 5c98329170
32 changed files with 729 additions and 154 deletions
@@ -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">
@@ -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