mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-11 13:20:58 +00:00
fix: page out of control
This commit is contained in:
@@ -68,19 +68,19 @@ enum PluginInstallStatus {
|
|||||||
|
|
||||||
export default function PluginConfigPage(
|
export default function PluginConfigPage(
|
||||||
{
|
{
|
||||||
editMode,
|
editMode = false,
|
||||||
initMCPId,
|
initMCPId,
|
||||||
onFormSubmit,
|
onFormSubmit,
|
||||||
onFormCancel,
|
onFormCancel,
|
||||||
onMcpDeleted,
|
onMcpDeleted,
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
editMode: boolean;
|
editMode?: boolean;
|
||||||
initMCPId?: string;
|
initMCPId?: string;
|
||||||
onFormSubmit: () => void;
|
onFormSubmit?: () => void;
|
||||||
onFormCancel: () => void;
|
onFormCancel?: () => void;
|
||||||
onMcpDeleted: () => void;
|
onMcpDeleted?: () => void;
|
||||||
}
|
} = {}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -227,6 +227,93 @@ export default function PluginConfigPage(
|
|||||||
const mcpComponentRef = useRef<MCPComponentRef>(null);
|
const mcpComponentRef = useRef<MCPComponentRef>(null);
|
||||||
const [mcpTesting, setMcpTesting] = useState(false);
|
const [mcpTesting, setMcpTesting] = useState(false);
|
||||||
|
|
||||||
|
// 强制清理 body 样式以修复 Dialog 关闭后点击失效的问题
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[Dialog Debug] States:', { mcpSSEModalOpen, modalOpen, showDeleteConfirmModal });
|
||||||
|
|
||||||
|
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
||||||
|
console.log('[Dialog Debug] All dialogs closed, cleaning up body styles...');
|
||||||
|
console.log('[Dialog Debug] Before cleanup - body.style.pointerEvents:', document.body.style.pointerEvents);
|
||||||
|
console.log('[Dialog Debug] Before cleanup - body.style.overflow:', document.body.style.overflow);
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
// 强制移除 body 上可能残留的样式
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
|
||||||
|
// 如果 removeProperty 不起作用,强制设置为空字符串
|
||||||
|
if (document.body.style.pointerEvents === 'none') {
|
||||||
|
document.body.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
if (document.body.style.overflow === 'hidden') {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Dialog Debug] After cleanup - body.style.pointerEvents:', document.body.style.pointerEvents);
|
||||||
|
console.log('[Dialog Debug] After cleanup - body.style.overflow:', document.body.style.overflow);
|
||||||
|
|
||||||
|
// 检查计算后的样式
|
||||||
|
const computedStyle = window.getComputedStyle(document.body);
|
||||||
|
console.log('[Dialog Debug] Computed pointerEvents:', computedStyle.pointerEvents);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 多次清理以确保覆盖 Radix 的设置
|
||||||
|
cleanup();
|
||||||
|
const timer1 = setTimeout(cleanup, 0);
|
||||||
|
const timer2 = setTimeout(cleanup, 50);
|
||||||
|
const timer3 = setTimeout(cleanup, 100);
|
||||||
|
const timer4 = setTimeout(cleanup, 200);
|
||||||
|
const timer5 = setTimeout(cleanup, 300);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer1);
|
||||||
|
clearTimeout(timer2);
|
||||||
|
clearTimeout(timer3);
|
||||||
|
clearTimeout(timer4);
|
||||||
|
clearTimeout(timer5);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
|
||||||
|
|
||||||
|
// 额外的全局清理:定期检查并清理
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
||||||
|
if (document.body.style.pointerEvents === 'none') {
|
||||||
|
console.log('[Global Cleanup] Found stale pointer-events, cleaning...');
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
|
||||||
|
|
||||||
|
// MutationObserver:监视 body 的 style 变化
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
|
||||||
|
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
||||||
|
if (document.body.style.pointerEvents === 'none') {
|
||||||
|
console.log('[MutationObserver] Detected pointer-events being set to none, reverting...');
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['style'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
|
||||||
|
|
||||||
function handleModalConfirm() {
|
function handleModalConfirm() {
|
||||||
installPlugin(installSource, installInfo as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
|
installPlugin(installSource, installInfo as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
@@ -277,7 +364,7 @@ export default function PluginConfigPage(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFormSubmit(value: z.infer<typeof formSchema>) {
|
async function handleFormSubmit(value: z.infer<typeof formSchema>) {
|
||||||
const extraArgsObj: Record<string, string | number | boolean> = {};
|
const extraArgsObj: Record<string, string | number | boolean> = {};
|
||||||
value.extra_args?.forEach(
|
value.extra_args?.forEach(
|
||||||
(arg: { key: string; type: string; value: string }) => {
|
(arg: { key: string; type: string; value: string }) => {
|
||||||
@@ -290,6 +377,35 @@ export default function PluginConfigPage(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构造符合 MCPServerConfig 类型的数据
|
||||||
|
const serverConfig = {
|
||||||
|
name: value.name,
|
||||||
|
mode: 'sse' as const,
|
||||||
|
enable: true,
|
||||||
|
url: value.url,
|
||||||
|
headers: extraArgsObj as Record<string, string>,
|
||||||
|
timeout: value.timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
await httpClient.createMCPServer(serverConfig);
|
||||||
|
|
||||||
|
toast.success(t('mcp.createSuccess'));
|
||||||
|
|
||||||
|
// 只有在异步操作成功后才关闭对话框
|
||||||
|
setMcpSSEModalOpen(false);
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
form.reset();
|
||||||
|
setExtraArgs([]);
|
||||||
|
|
||||||
|
// 调用回调通知父组件刷新
|
||||||
|
onFormSubmit?.();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create MCP server:', error);
|
||||||
|
toast.error(t('mcp.createFailed'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMcp() {
|
function testMcp() {
|
||||||
@@ -872,7 +988,12 @@ export default function PluginConfigPage(
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onFormCancel()}
|
onClick={() => {
|
||||||
|
setMcpSSEModalOpen(false);
|
||||||
|
form.reset();
|
||||||
|
setExtraArgs([]);
|
||||||
|
onFormCancel?.();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('common.cancel')}
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -503,7 +503,7 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
public createMCPServer(
|
public createMCPServer(
|
||||||
server: MCPServerConfig,
|
server: MCPServerConfig,
|
||||||
): Promise<AsyncTaskCreatedResp> {
|
): Promise<AsyncTaskCreatedResp> {
|
||||||
return this.post('/api/v1/mcp/servers', server);
|
return this.post('/api/v1/mcp/servers', { source: server });
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateMCPServer(
|
public updateMCPServer(
|
||||||
|
|||||||
@@ -7,9 +7,61 @@ import { XIcon } from 'lucide-react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
function Dialog({
|
function Dialog({
|
||||||
|
onOpenChange,
|
||||||
|
open,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
const handleOpenChange = React.useCallback((isOpen: boolean) => {
|
||||||
|
onOpenChange?.(isOpen);
|
||||||
|
|
||||||
|
// 当对话框关闭时,确保清理 body 样式
|
||||||
|
if (!isOpen) {
|
||||||
|
// 立即清理
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
|
||||||
|
// 延迟再次清理,确保覆盖 Radix 的设置
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
}, [onOpenChange]);
|
||||||
|
|
||||||
|
// 使用 effect 监控 open 状态变化
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (open === false) {
|
||||||
|
const cleanup = () => {
|
||||||
|
document.body.style.removeProperty('pointer-events');
|
||||||
|
document.body.style.removeProperty('overflow');
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
const timer1 = setTimeout(cleanup, 0);
|
||||||
|
const timer2 = setTimeout(cleanup, 50);
|
||||||
|
const timer3 = setTimeout(cleanup, 150);
|
||||||
|
const timer4 = setTimeout(cleanup, 300);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer1);
|
||||||
|
clearTimeout(timer2);
|
||||||
|
clearTimeout(timer3);
|
||||||
|
clearTimeout(timer4);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" open={open} {...props} onOpenChange={handleOpenChange} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTrigger({
|
function DialogTrigger({
|
||||||
@@ -60,7 +112,6 @@ function DialogContent({
|
|||||||
'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',
|
'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,
|
className,
|
||||||
)}
|
)}
|
||||||
onInteractOutside={() => {}}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Reference in New Issue
Block a user