Refactor bypass defaults and crash handling

Set bypass defaults to disabled and simplify loading: napcat.json default bypass flags changed to false and code now reads bypass options without merging a prior "all enabled" default. Removed the progressive bypass-disable logic and related environment variable usage, and added a log when Napi2NativeLoader enables bypasses. Web UI/backend adjustments: default NapCat config is now generated from the AJV schema; the bypass settings UI defaults to false, adds an o3HookMode toggle, and submits o3HookMode as 0/1. UX fixes: extension tabs made horizontally scrollable with fixed tab sizing, and plugin uninstall flow updated to a single confirmation dialog with an optional checkbox to remove plugin config. Overall changes aim to use safer defaults, simplify crash/restart behavior, and improve configuration and UI clarity.
This commit is contained in:
手瓜一十雪
2026-02-20 16:36:16 +08:00
parent 285d352bc8
commit 41d94cd5e2
8 changed files with 116 additions and 195 deletions

View File

@@ -15,16 +15,9 @@ interface BypassFormData {
process: boolean;
container: boolean;
js: boolean;
o3HookMode: boolean;
}
const defaultBypass: BypassFormData = {
hook: true,
window: true,
module: true,
process: true,
container: true,
js: true,
};
const BypassConfigCard = () => {
const [loading, setLoading] = useState(true);
@@ -33,21 +26,20 @@ const BypassConfigCard = () => {
handleSubmit,
formState: { isSubmitting },
setValue,
} = useForm<BypassFormData>({
defaultValues: defaultBypass,
});
} = useForm<BypassFormData>();
const loadConfig = async (showTip = false) => {
try {
setLoading(true);
const config = await QQManager.getNapCatConfig();
const bypass = config.bypass ?? defaultBypass;
setValue('hook', bypass.hook ?? true);
setValue('window', bypass.window ?? true);
setValue('module', bypass.module ?? true);
setValue('process', bypass.process ?? true);
setValue('container', bypass.container ?? true);
setValue('js', bypass.js ?? true);
const bypass = config.bypass ?? {} as Partial<BypassOptions>;
setValue('hook', bypass.hook ?? false);
setValue('window', bypass.window ?? false);
setValue('module', bypass.module ?? false);
setValue('process', bypass.process ?? false);
setValue('container', bypass.container ?? false);
setValue('js', bypass.js ?? false);
setValue('o3HookMode', config.o3HookMode === 1);
if (showTip) toast.success('刷新成功');
} catch (error) {
const msg = (error as Error).message;
@@ -59,7 +51,8 @@ const BypassConfigCard = () => {
const onSubmit = handleSubmit(async (data) => {
try {
await QQManager.setNapCatConfig({ bypass: data });
const { o3HookMode, ...bypass } = data;
await QQManager.setNapCatConfig({ bypass, o3HookMode: o3HookMode ? 1 : 0 });
toast.success('保存成功,重启后生效');
} catch (error) {
const msg = (error as Error).message;
@@ -156,6 +149,17 @@ const BypassConfigCard = () => {
/>
)}
/>
<Controller
control={control}
name='o3HookMode'
render={({ field }) => (
<SwitchCard
{...field}
label='o3HookMode'
description='O3 Hook 模式'
/>
)}
/>
<SaveButtons
onSubmit={onSubmit}
reset={onReset}

View File

@@ -115,39 +115,42 @@ export default function ExtensionPage () {
</Button>
</div>
{extensionPages.length > 0 && (
<Tabs
aria-label='Extension Pages'
className='max-w-full'
selectedKey={selectedTab}
onSelectionChange={(key) => setSelectedTab(key as string)}
classNames={{
tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md',
cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm',
panel: 'hidden',
}}
>
{tabs.map((tab) => (
<Tab
key={tab.key}
title={
<div className='flex items-center gap-2'>
{tab.icon && <span>{tab.icon}</span>}
<span
className='cursor-pointer hover:underline truncate max-w-[6rem] md:max-w-none'
title={`插件:${tab.pluginName}\n点击在新窗口打开`}
onClick={(e) => {
e.stopPropagation();
openInNewWindow(tab.pluginId, tab.path);
}}
>
{tab.title}
</span>
<span className='text-xs text-default-400 hidden md:inline'>({tab.pluginName})</span>
</div>
}
/>
))}
</Tabs>
<div className='max-w-full overflow-x-auto overflow-y-hidden pb-1 -mb-1'>
<Tabs
aria-label='Extension Pages'
className='min-w-max'
selectedKey={selectedTab}
onSelectionChange={(key) => setSelectedTab(key as string)}
classNames={{
tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md flex-nowrap',
cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm',
panel: 'hidden',
}}
>
{tabs.map((tab) => (
<Tab
key={tab.key}
className='shrink-0'
title={
<div className='flex items-center gap-2'>
{tab.icon && <span>{tab.icon}</span>}
<span
className='cursor-pointer hover:underline truncate max-w-[6rem] md:max-w-none shrink-0'
title={`插件:${tab.pluginName}\n点击在新窗口打开`}
onClick={(e) => {
e.stopPropagation();
openInNewWindow(tab.pluginId, tab.path);
}}
>
{tab.title}
</span>
<span className='text-xs text-default-400 hidden md:inline shrink-0'>({tab.pluginName})</span>
</div>
}
/>
))}
</Tabs>
</div>
)}
</div>

View File

@@ -61,54 +61,43 @@ export default function PluginPage () {
const handleUninstall = async (plugin: PluginItem) => {
return new Promise<void>((resolve, reject) => {
let cleanData = false;
dialog.confirm({
title: '卸载插件',
content: (
<div className="flex flex-col gap-2">
<p>{plugin.name}? </p>
<p className="text-small text-default-500"></p>
<p className="text-base text-default-800"><span className="font-semibold text-danger">{plugin.name}</span>? </p>
<div className="mt-2 bg-default-100 dark:bg-default-50/10 p-3 rounded-lg flex flex-col gap-1">
<label className="flex items-center gap-2 cursor-pointer w-fit">
<input
type="checkbox"
onChange={(e) => { cleanData = e.target.checked; }}
className="w-4 h-4 cursor-pointer accent-danger"
/>
<span className="text-small font-medium text-default-700"></span>
</label>
<p className="text-xs text-default-500 pl-6 break-all w-full">配置目录: config/plugins/{plugin.id}</p>
</div>
</div>
),
confirmText: '确定卸载',
cancelText: '取消',
onConfirm: async () => {
// Ask for data cleanup
dialog.confirm({
title: '删除配置',
content: (
<div className="flex flex-col gap-2">
<p>{plugin.name}</p>
<div className="text-small text-default-500">
<p>配置目录: config/plugins/{plugin.id}</p>
<p>"确定""取消"</p>
</div>
</div>
),
confirmText: '清理并卸载',
cancelText: '仅卸载',
onConfirm: async () => {
await performUninstall(true);
},
onCancel: async () => {
await performUninstall(false);
}
});
const loadingToast = toast.loading('卸载中...');
try {
await PluginManager.uninstallPlugin(plugin.id, cleanData);
toast.success('卸载成功', { id: loadingToast });
loadPlugins();
resolve();
} catch (e: any) {
toast.error(e.message, { id: loadingToast });
reject(e);
}
},
onCancel: () => {
resolve();
}
});
const performUninstall = async (cleanData: boolean) => {
const loadingToast = toast.loading('卸载中...');
try {
await PluginManager.uninstallPlugin(plugin.id, cleanData);
toast.success('卸载成功', { id: loadingToast });
loadPlugins();
resolve();
} catch (e: any) {
toast.error(e.message, { id: loadingToast });
reject(e);
}
};
});
};