refactor: 整体重构 (#1381)

* feat: pnpm new

* Refactor build and release workflows, update dependencies

Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
This commit is contained in:
手瓜一十雪
2025-11-13 15:39:42 +08:00
committed by GitHub
parent c3d1892545
commit ed19c52f25
778 changed files with 2356 additions and 26391 deletions

View File

@@ -0,0 +1,180 @@
import { Button } from '@heroui/button';
import { Input } from '@heroui/input';
import { ModalBody, ModalFooter } from '@heroui/modal';
import { Select, SelectItem } from '@heroui/select';
import { ReactElement, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import type {
DefaultValues,
Path,
PathValue,
SubmitHandler,
} from 'react-hook-form';
import toast from 'react-hot-toast';
import SwitchCard from '../switch_card';
export type FieldTypes = 'input' | 'select' | 'switch';
type NetworkConfigType = OneBotConfig['network'];
export interface Field<T extends keyof OneBotConfig['network']> {
name: keyof NetworkConfigType[T][0];
label: string;
type: FieldTypes;
options?: Array<{ key: string; value: string; }>;
placeholder?: string;
isRequired?: boolean;
isDisabled?: boolean;
description?: string;
colSpan?: 1 | 2;
}
export interface GenericFormProps<T extends keyof NetworkConfigType> {
data?: NetworkConfigType[T][0];
defaultValues: DefaultValues<NetworkConfigType[T][0]>;
onClose: () => void;
onSubmit: (data: NetworkConfigType[T][0]) => Promise<void>;
fields: Array<Field<T>>;
}
const GenericForm = <T extends keyof NetworkConfigType> ({
data,
defaultValues,
onClose,
onSubmit,
fields,
}: GenericFormProps<T>): ReactElement => {
const { control, handleSubmit, formState, setValue, reset } = useForm<
NetworkConfigType[T][0]
>({
defaultValues,
});
const submitAction: SubmitHandler<NetworkConfigType[T][0]> = async (data) => {
await onSubmit(data);
onClose();
};
const _onSubmit = handleSubmit(submitAction, (e) => {
const errors = Object.values(e);
if (errors.length > 0) {
toast.error(errors[0]?.message as string);
}
});
useEffect(() => {
if (data) {
const keys = Object.keys(data) as Path<NetworkConfig[T][0]>[];
for (const key of keys) {
const value = data[key] as PathValue<
NetworkConfig[T][0],
Path<NetworkConfig[T][0]>
>;
setValue(key, value);
}
} else {
reset();
}
}, [data, reset, setValue]);
return (
<>
<ModalBody>
<div className='grid grid-cols-2 gap-y-4 gap-x-2 w-full'>
{fields.map((field) => (
<div
key={field.name as string}
className={field.colSpan === 1 ? 'col-span-1' : 'col-span-2'}
>
<Controller
control={control}
name={field.name as Path<NetworkConfig[T][0]>}
rules={
field.isRequired
? {
required: `请填写${field.label}`,
}
: undefined
}
render={({ field: controllerField }) => {
switch (field.type) {
case 'input':
return (
<Input
value={controllerField.value as string}
onValueChange={(value) => controllerField.onChange(value)}
ref={controllerField.ref}
isRequired={field.isRequired}
isDisabled={field.isDisabled}
label={field.label}
placeholder={field.placeholder}
/>
);
case 'select':
return (
<Select
{...controllerField}
ref={controllerField.ref}
isRequired={field.isRequired}
label={field.label}
placeholder={field.placeholder}
selectedKeys={[controllerField.value as string]}
value={controllerField.value.toString()}
>
{field.options?.map((option) => (
<SelectItem key={option.key} value={option.value}>
{option.value}
</SelectItem>
)) || <></>}
</Select>
);
case 'switch':
return (
<SwitchCard
{...controllerField}
value={controllerField.value as boolean}
description={field.description}
label={field.label}
/>
);
default:
return <></>;
}
}}
/>
</div>
))}
</div>
</ModalBody>
<ModalFooter>
<Button
color='primary'
isDisabled={formState.isSubmitting}
variant='light'
onPress={onClose}
>
</Button>
<Button
color='primary'
isLoading={formState.isSubmitting}
onPress={() => _onSubmit()}
>
</Button>
</ModalFooter>
</>
);
};
export default GenericForm;
export function random_token (length: number) {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()-_=+[]{}|;:,.<>?';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}

View File

@@ -0,0 +1,95 @@
import GenericForm, { random_token } from './generic_form';
import type { Field } from './generic_form';
export interface HTTPClientFormProps {
data?: OneBotConfig['network']['httpClients'][0]
onClose: () => void
onSubmit: (data: OneBotConfig['network']['httpClients'][0]) => Promise<void>
}
type HTTPClientFormType = OneBotConfig['network']['httpClients'];
const HTTPClientForm: React.FC<HTTPClientFormProps> = ({
data,
onClose,
onSubmit,
}) => {
const defaultValues: HTTPClientFormType[0] = {
enable: false,
name: '',
url: 'http://localhost:8080',
reportSelfMessage: false,
messagePostFormat: 'array',
token: random_token(16),
debug: false,
};
const fields: Field<'httpClients'>[] = [
{
name: 'enable',
label: '启用',
type: 'switch',
description: '保存后启用此配置',
colSpan: 1,
},
{
name: 'debug',
label: '开启Debug',
type: 'switch',
description: '是否开启调试模式',
colSpan: 1,
},
{
name: 'name',
label: '名称',
type: 'input',
placeholder: '请输入名称',
isRequired: true,
isDisabled: !!data,
},
{
name: 'url',
label: 'URL',
type: 'input',
placeholder: '请输入URL',
isRequired: true,
},
{
name: 'reportSelfMessage',
label: '上报自身消息',
type: 'switch',
description: '是否上报自身消息',
colSpan: 1,
},
{
name: 'messagePostFormat',
label: '消息格式',
type: 'select',
placeholder: '请选择消息格式',
isRequired: true,
options: [
{ key: 'array', value: 'Array' },
{ key: 'string', value: 'String' },
],
colSpan: 1,
},
{
name: 'token',
label: 'Token',
type: 'input',
placeholder: '请输入Token',
},
];
return (
<GenericForm
data={data}
defaultValues={defaultValues}
onClose={onClose}
onSubmit={onSubmit}
fields={fields}
/>
);
};
export default HTTPClientForm;

View File

@@ -0,0 +1,110 @@
import GenericForm, { random_token } from './generic_form';
import type { Field } from './generic_form';
export interface HTTPServerFormProps {
data?: OneBotConfig['network']['httpServers'][0]
onClose: () => void
onSubmit: (data: OneBotConfig['network']['httpServers'][0]) => Promise<void>
}
type HTTPServerFormType = OneBotConfig['network']['httpServers'];
const HTTPServerForm: React.FC<HTTPServerFormProps> = ({
data,
onClose,
onSubmit,
}) => {
const defaultValues: HTTPServerFormType[0] = {
enable: false,
name: '',
host: '127.0.0.1',
port: 3000,
enableCors: true,
enableWebsocket: true,
messagePostFormat: 'array',
token: random_token(16),
debug: false,
};
const fields: Field<'httpServers'>[] = [
{
name: 'enable',
label: '启用',
type: 'switch',
description: '保存后启用此配置',
colSpan: 1,
},
{
name: 'debug',
label: '开启Debug',
type: 'switch',
description: '是否开启调试模式',
colSpan: 1,
},
{
name: 'name',
label: '名称',
type: 'input',
placeholder: '请输入名称',
isRequired: true,
isDisabled: !!data,
},
{
name: 'host',
label: 'Host',
type: 'input',
placeholder: '请输入主机地址',
isRequired: true,
},
{
name: 'port',
label: 'Port',
type: 'input',
placeholder: '请输入端口',
isRequired: true,
},
{
name: 'enableCors',
label: '启用CORS',
type: 'switch',
description: '是否启用CORS跨域',
colSpan: 1,
},
{
name: 'enableWebsocket',
label: '启用Websocket',
type: 'switch',
description: '是否启用Websocket',
colSpan: 1,
},
{
name: 'messagePostFormat',
label: '消息格式',
type: 'select',
placeholder: '请选择消息格式',
isRequired: true,
options: [
{ key: 'array', value: 'Array' },
{ key: 'string', value: 'String' },
],
},
{
name: 'token',
label: 'Token',
type: 'input',
placeholder: '请输入Token',
},
];
return (
<GenericForm
data={data}
defaultValues={defaultValues}
onClose={onClose}
onSubmit={onSubmit}
fields={fields}
/>
);
};
export default HTTPServerForm;

View File

@@ -0,0 +1,120 @@
import GenericForm, { random_token } from './generic_form';
import type { Field } from './generic_form';
export interface HTTPServerSSEFormProps {
data?: OneBotConfig['network']['httpSseServers'][0]
onClose: () => void
onSubmit: (
data: OneBotConfig['network']['httpSseServers'][0]
) => Promise<void>
}
type HTTPServerSSEFormType = OneBotConfig['network']['httpSseServers'];
const HTTPServerSSEForm: React.FC<HTTPServerSSEFormProps> = ({
data,
onClose,
onSubmit,
}) => {
const defaultValues: HTTPServerSSEFormType[0] = {
enable: false,
name: '',
host: '127.0.0.1',
port: 3000,
enableCors: true,
enableWebsocket: true,
messagePostFormat: 'array',
token: random_token(16),
debug: false,
reportSelfMessage: false,
};
const fields: Field<'httpSseServers'>[] = [
{
name: 'enable',
label: '启用',
type: 'switch',
description: '保存后启用此配置',
colSpan: 1,
},
{
name: 'debug',
label: '开启Debug',
type: 'switch',
description: '是否开启调试模式',
colSpan: 1,
},
{
name: 'name',
label: '名称',
type: 'input',
placeholder: '请输入名称',
isRequired: true,
isDisabled: !!data,
},
{
name: 'host',
label: 'Host',
type: 'input',
placeholder: '请输入主机地址',
isRequired: true,
},
{
name: 'port',
label: 'Port',
type: 'input',
placeholder: '请输入端口',
isRequired: true,
},
{
name: 'enableCors',
label: '启用CORS',
type: 'switch',
description: '是否启用CORS跨域',
colSpan: 1,
},
{
name: 'enableWebsocket',
label: '启用Websocket',
type: 'switch',
description: '是否启用Websocket',
colSpan: 1,
},
{
name: 'messagePostFormat',
label: '消息格式',
type: 'select',
placeholder: '请选择消息格式',
isRequired: true,
options: [
{ key: 'array', value: 'Array' },
{ key: 'string', value: 'String' },
],
},
{
name: 'token',
label: 'Token',
type: 'input',
placeholder: '请输入Token',
},
{
name: 'reportSelfMessage',
label: '上报自身消息',
type: 'switch',
description: '是否上报自身消息',
colSpan: 1,
},
];
return (
<GenericForm
data={data}
defaultValues={defaultValues}
onClose={onClose}
onSubmit={onSubmit}
fields={fields}
/>
);
};
export default HTTPServerSSEForm;

View File

@@ -0,0 +1,123 @@
import { Modal, ModalContent, ModalHeader } from '@heroui/modal';
import toast from 'react-hot-toast';
import useConfig from '@/hooks/use-config';
import HTTPClientForm from './http_client';
import HTTPServerForm from './http_server';
import HTTPServerSSEForm from './http_sse';
import WebsocketClientForm from './ws_client';
import WebsocketServerForm from './ws_server';
const modalTitle = {
httpServers: 'HTTP Server',
httpClients: 'HTTP Client',
httpSseServers: 'HTTP SSE Server',
websocketServers: 'Websocket Server',
websocketClients: 'Websocket Client',
};
export interface NetworkFormModalProps<
T extends keyof OneBotConfig['network']
> {
isOpen: boolean;
field: T;
data?: OneBotConfig['network'][T][0];
onOpenChange: (isOpen: boolean) => void;
}
const NetworkFormModal = <T extends keyof OneBotConfig['network']> (
props: NetworkFormModalProps<T>
) => {
const { isOpen, onOpenChange, field, data } = props;
const { createNetworkConfig, updateNetworkConfig } = useConfig();
const isCreate = !data;
const onSubmit = async (data: OneBotConfig['network'][typeof field][0]) => {
try {
if (isCreate) {
await createNetworkConfig(field, data);
} else {
await updateNetworkConfig(field, data);
}
toast.success('保存配置成功');
} catch (error) {
const msg = (error as Error).message;
toast.error(`保存配置失败: ${msg}`);
throw error;
}
};
const renderFormComponent = (onClose: () => void) => {
switch (field) {
case 'httpServers':
return (
<HTTPServerForm
data={data as OneBotConfig['network']['httpServers'][0]}
onClose={onClose}
onSubmit={onSubmit}
/>
);
case 'httpClients':
return (
<HTTPClientForm
data={data as OneBotConfig['network']['httpClients'][0]}
onClose={onClose}
onSubmit={onSubmit}
/>
);
case 'websocketServers':
return (
<WebsocketServerForm
data={data as OneBotConfig['network']['websocketServers'][0]}
onClose={onClose}
onSubmit={onSubmit}
/>
);
case 'websocketClients':
return (
<WebsocketClientForm
data={data as OneBotConfig['network']['websocketClients'][0]}
onClose={onClose}
onSubmit={onSubmit}
/>
);
case 'httpSseServers':
return (
<HTTPServerSSEForm
data={data as OneBotConfig['network']['httpSseServers'][0]}
onClose={onClose}
onSubmit={onSubmit}
/>
);
default:
return null;
}
};
return (
<Modal
backdrop='blur'
isDismissable={false}
isOpen={isOpen}
size='lg'
scrollBehavior='outside'
onOpenChange={onOpenChange}
>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className='flex flex-col gap-1'>
{modalTitle[field]}
</ModalHeader>
{renderFormComponent(onClose)}
</>
)}
</ModalContent>
</Modal>
);
};
export default NetworkFormModal;

View File

@@ -0,0 +1,115 @@
import GenericForm, { random_token } from './generic_form';
import type { Field } from './generic_form';
export interface WebsocketClientFormProps {
data?: OneBotConfig['network']['websocketClients'][0]
onClose: () => void
onSubmit: (
data: OneBotConfig['network']['websocketClients'][0]
) => Promise<void>
}
type WebsocketClientFormType = OneBotConfig['network']['websocketClients'];
const WebsocketClientForm: React.FC<WebsocketClientFormProps> = ({
data,
onClose,
onSubmit,
}) => {
const defaultValues: WebsocketClientFormType[0] = {
enable: false,
name: '',
url: 'ws://localhost:8082',
reportSelfMessage: false,
messagePostFormat: 'array',
token: random_token(16),
debug: false,
heartInterval: 30000,
reconnectInterval: 30000,
};
const fields: Field<'websocketClients'>[] = [
{
name: 'enable',
label: '启用',
type: 'switch',
description: '保存后启用此配置',
colSpan: 1,
},
{
name: 'debug',
label: '开启Debug',
type: 'switch',
description: '是否开启调试模式',
colSpan: 1,
},
{
name: 'name',
label: '名称',
type: 'input',
placeholder: '请输入名称',
isRequired: true,
isDisabled: !!data,
},
{
name: 'url',
label: 'URL',
type: 'input',
placeholder: '请输入URL',
isRequired: true,
},
{
name: 'reportSelfMessage',
label: '上报自身消息',
type: 'switch',
description: '是否上报自身消息',
colSpan: 1,
},
{
name: 'messagePostFormat',
label: '消息格式',
type: 'select',
placeholder: '请选择消息格式',
isRequired: true,
options: [
{ key: 'array', value: 'Array' },
{ key: 'string', value: 'String' },
],
colSpan: 1,
},
{
name: 'token',
label: 'Token',
type: 'input',
placeholder: '请输入Token',
},
{
name: 'heartInterval',
label: '心跳间隔',
type: 'input',
placeholder: '请输入心跳间隔',
isRequired: true,
colSpan: 1,
},
{
name: 'reconnectInterval',
label: '重连间隔',
type: 'input',
placeholder: '请输入重连间隔',
isRequired: true,
colSpan: 1,
},
];
return (
<GenericForm
data={data}
defaultValues={defaultValues}
onClose={onClose}
onSubmit={onSubmit}
fields={fields}
/>
);
};
export default WebsocketClientForm;

View File

@@ -0,0 +1,122 @@
import GenericForm, { random_token } from './generic_form';
import type { Field } from './generic_form';
export interface WebsocketServerFormProps {
data?: OneBotConfig['network']['websocketServers'][0]
onClose: () => void
onSubmit: (
data: OneBotConfig['network']['websocketServers'][0]
) => Promise<void>
}
type WebsocketServerFormType = OneBotConfig['network']['websocketServers'];
const WebsocketServerForm: React.FC<WebsocketServerFormProps> = ({
data,
onClose,
onSubmit,
}) => {
const defaultValues: WebsocketServerFormType[0] = {
enable: false,
name: '',
host: '127.0.0.1',
port: 3001,
reportSelfMessage: false,
enableForcePushEvent: true,
messagePostFormat: 'array',
token: random_token(16),
debug: false,
heartInterval: 30000,
};
const fields: Field<'websocketServers'>[] = [
{
name: 'enable',
label: '启用',
type: 'switch',
description: '保存后启用此配置',
colSpan: 1,
},
{
name: 'debug',
label: '开启Debug',
type: 'switch',
description: '是否开启调试模式',
colSpan: 1,
},
{
name: 'name',
label: '名称',
type: 'input',
placeholder: '请输入名称',
isRequired: true,
isDisabled: !!data,
},
{
name: 'host',
label: 'Host',
type: 'input',
placeholder: '请输入主机地址',
isRequired: true,
},
{
name: 'port',
label: 'Port',
type: 'input',
placeholder: '请输入端口',
isRequired: true,
colSpan: 1,
},
{
name: 'messagePostFormat',
label: '消息格式',
type: 'select',
placeholder: '请选择消息格式',
isRequired: true,
options: [
{ key: 'array', value: 'Array' },
{ key: 'string', value: 'String' },
],
colSpan: 1,
},
{
name: 'reportSelfMessage',
label: '上报自身消息',
type: 'switch',
description: '是否上报自身消息',
colSpan: 1,
},
{
name: 'enableForcePushEvent',
label: '强制推送事件',
type: 'switch',
description: '是否强制推送事件',
colSpan: 1,
},
{
name: 'token',
label: 'Token',
type: 'input',
placeholder: '请输入Token',
},
{
name: 'heartInterval',
label: '心跳间隔',
type: 'input',
placeholder: '请输入心跳间隔',
isRequired: true,
},
];
return (
<GenericForm
data={data}
defaultValues={defaultValues}
onClose={onClose}
onSubmit={onSubmit}
fields={fields}
/>
);
};
export default WebsocketServerForm;