mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
chore: extract wechat credentials to custom render form
This commit is contained in:
@@ -13,18 +13,12 @@ import type {
|
|||||||
SerializedPlatformDefinition,
|
SerializedPlatformDefinition,
|
||||||
} from '@/server/services/bot/platforms/types';
|
} from '@/server/services/bot/platforms/types';
|
||||||
|
|
||||||
|
import { platformCredentialBodyMap } from '../platform/registry';
|
||||||
import type { ChannelFormValues } from './index';
|
import type { ChannelFormValues } from './index';
|
||||||
import QrCodeAuth from './QrCodeAuth';
|
|
||||||
|
|
||||||
const prefixCls = 'ant';
|
const prefixCls = 'ant';
|
||||||
|
|
||||||
const styles = createStaticStyles(({ css }) => ({
|
const styles = createStaticStyles(({ css }) => ({
|
||||||
connectedInfoHeader: css`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-block-end: 16px;
|
|
||||||
`,
|
|
||||||
form: css`
|
form: css`
|
||||||
.${prefixCls}-form-item-control:has(.${prefixCls}-input, .${prefixCls}-select, .${prefixCls}-input-number) {
|
.${prefixCls}-form-item-control:has(.${prefixCls}-input, .${prefixCls}-select, .${prefixCls}-input-number) {
|
||||||
flex: none;
|
flex: none;
|
||||||
@@ -165,30 +159,6 @@ const ApplicationIdField = memo<{ field: FieldSchema }>(({ field }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ReadOnlyField = memo<{
|
|
||||||
description?: string;
|
|
||||||
divider?: boolean;
|
|
||||||
label: string;
|
|
||||||
password?: boolean;
|
|
||||||
tag: string;
|
|
||||||
value?: string;
|
|
||||||
}>(({ description, divider, label, password, tag, value }) => {
|
|
||||||
const InputComponent = password ? FormPassword : FormInput;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem
|
|
||||||
desc={description}
|
|
||||||
divider={divider}
|
|
||||||
label={label}
|
|
||||||
minWidth={'max(50%, 400px)'}
|
|
||||||
tag={tag}
|
|
||||||
variant="borderless"
|
|
||||||
>
|
|
||||||
<InputComponent readOnly value={value || ''} />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// --------------- Helper: flatten fields from schema ---------------
|
// --------------- Helper: flatten fields from schema ---------------
|
||||||
|
|
||||||
function getFields(schema: FieldSchema[], sectionKey: string): FieldSchema[] {
|
function getFields(schema: FieldSchema[], sectionKey: string): FieldSchema[] {
|
||||||
@@ -225,115 +195,66 @@ interface BodyProps {
|
|||||||
};
|
};
|
||||||
form: FormInstance<ChannelFormValues>;
|
form: FormInstance<ChannelFormValues>;
|
||||||
hasConfig?: boolean;
|
hasConfig?: boolean;
|
||||||
onQrAuthenticated?: (credentials: { botId: string; botToken: string; userId: string }) => void;
|
onAuthenticated?: (params: {
|
||||||
|
applicationId: string;
|
||||||
|
credentials: Record<string, string>;
|
||||||
|
}) => void;
|
||||||
platformDef: SerializedPlatformDefinition;
|
platformDef: SerializedPlatformDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Body = memo<BodyProps>(
|
const Body = memo<BodyProps>(({ platformDef, form, hasConfig, currentConfig, onAuthenticated }) => {
|
||||||
({ platformDef, form, hasConfig, currentConfig, onQrAuthenticated }) => {
|
const { t: _t } = useTranslation('agent');
|
||||||
const { t: _t } = useTranslation('agent');
|
const t = _t as (key: string) => string;
|
||||||
const t = _t as (key: string) => string;
|
|
||||||
|
|
||||||
const applicationIdField = useMemo(
|
const CustomCredentialBody = platformCredentialBodyMap[platformDef.id];
|
||||||
() => platformDef.schema.find((f) => f.key === 'applicationId'),
|
|
||||||
[platformDef.schema],
|
|
||||||
);
|
|
||||||
|
|
||||||
const credentialFields = useMemo(
|
const applicationIdField = useMemo(
|
||||||
() => getFields(platformDef.schema, 'credentials'),
|
() => platformDef.schema.find((f) => f.key === 'applicationId'),
|
||||||
[platformDef.schema],
|
[platformDef.schema],
|
||||||
);
|
);
|
||||||
|
|
||||||
const settingsFields = useMemo(
|
const credentialFields = useMemo(
|
||||||
() => getFields(platformDef.schema, 'settings'),
|
() => getFields(platformDef.schema, 'credentials'),
|
||||||
[platformDef.schema],
|
[platformDef.schema],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [settingsActive, setSettingsActive] = useState(false);
|
const settingsFields = useMemo(
|
||||||
const shouldShowWechatApplicationId =
|
() => getFields(platformDef.schema, 'settings'),
|
||||||
!!currentConfig?.applicationId &&
|
[platformDef.schema],
|
||||||
currentConfig.applicationId !== currentConfig.credentials.botId;
|
);
|
||||||
|
|
||||||
const handleResetSettings = useCallback(() => {
|
const [settingsActive, setSettingsActive] = useState(false);
|
||||||
const defaults: Record<string, any> = {};
|
|
||||||
for (const field of settingsFields) {
|
const handleResetSettings = useCallback(() => {
|
||||||
if (field.default !== undefined) {
|
const defaults: Record<string, any> = {};
|
||||||
defaults[field.key] = field.default;
|
for (const field of settingsFields) {
|
||||||
}
|
if (field.default !== undefined) {
|
||||||
|
defaults[field.key] = field.default;
|
||||||
}
|
}
|
||||||
form.setFieldsValue({ settings: defaults });
|
}
|
||||||
}, [form, settingsFields]);
|
form.setFieldsValue({ settings: defaults });
|
||||||
|
}, [form, settingsFields]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
className={styles.form}
|
className={styles.form}
|
||||||
form={form}
|
form={form}
|
||||||
gap={0}
|
gap={0}
|
||||||
itemMinWidth={'max(50%, 400px)'}
|
itemMinWidth={'max(50%, 400px)'}
|
||||||
requiredMark={false}
|
requiredMark={false}
|
||||||
style={{ maxWidth: 1024, padding: '16px 0', width: '100%' }}
|
style={{ maxWidth: 1024, padding: '16px 0', width: '100%' }}
|
||||||
variant={'borderless'}
|
variant={'borderless'}
|
||||||
>
|
>
|
||||||
{platformDef.authFlow === 'qrcode' && hasConfig && currentConfig && (
|
{CustomCredentialBody ? (
|
||||||
<>
|
<CustomCredentialBody
|
||||||
<div className={styles.connectedInfoHeader}>
|
currentConfig={currentConfig}
|
||||||
<Flexbox gap={4}>
|
hasConfig={hasConfig}
|
||||||
<div style={{ fontSize: 16, fontWeight: 600 }}>
|
onAuthenticated={onAuthenticated}
|
||||||
{t('channel.wechatConnectedInfo')}
|
/>
|
||||||
</div>
|
) : (
|
||||||
<div style={{ color: 'var(--ant-color-text-secondary)', fontSize: 13 }}>
|
<>
|
||||||
{t('channel.wechatManagedCredentials')}
|
{applicationIdField && <ApplicationIdField field={applicationIdField} />}
|
||||||
</div>
|
{credentialFields.map((field, i) => (
|
||||||
</Flexbox>
|
|
||||||
{onQrAuthenticated && (
|
|
||||||
<QrCodeAuth
|
|
||||||
buttonLabel={t('channel.wechatRebind')}
|
|
||||||
buttonType="default"
|
|
||||||
showTips={false}
|
|
||||||
onAuthenticated={onQrAuthenticated}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{shouldShowWechatApplicationId && (
|
|
||||||
<ReadOnlyField
|
|
||||||
description={t('channel.applicationIdHint')}
|
|
||||||
label={t('channel.applicationId')}
|
|
||||||
tag="applicationId"
|
|
||||||
value={currentConfig.applicationId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ReadOnlyField
|
|
||||||
description={t('channel.wechatBotIdHint')}
|
|
||||||
divider={shouldShowWechatApplicationId}
|
|
||||||
label={t('channel.wechatBotId')}
|
|
||||||
tag="botId"
|
|
||||||
value={currentConfig.credentials.botId}
|
|
||||||
/>
|
|
||||||
<ReadOnlyField
|
|
||||||
divider
|
|
||||||
password
|
|
||||||
description={t('channel.botTokenEncryptedHint')}
|
|
||||||
label={t('channel.botToken')}
|
|
||||||
tag="botToken"
|
|
||||||
value={currentConfig.credentials.botToken}
|
|
||||||
/>
|
|
||||||
<ReadOnlyField
|
|
||||||
divider
|
|
||||||
description={t('channel.wechatUserIdHint')}
|
|
||||||
label={t('channel.wechatUserId')}
|
|
||||||
tag="userId"
|
|
||||||
value={currentConfig.credentials.userId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{platformDef.authFlow === 'qrcode' && onQrAuthenticated && !hasConfig && (
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '16px 0' }}>
|
|
||||||
<QrCodeAuth onAuthenticated={onQrAuthenticated} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{applicationIdField && <ApplicationIdField field={applicationIdField} />}
|
|
||||||
{!platformDef.authFlow &&
|
|
||||||
credentialFields.map((field, i) => (
|
|
||||||
<SchemaField
|
<SchemaField
|
||||||
divider={applicationIdField ? true : i !== 0}
|
divider={applicationIdField ? true : i !== 0}
|
||||||
field={field}
|
field={field}
|
||||||
@@ -341,36 +262,34 @@ const Body = memo<BodyProps>(
|
|||||||
parentKey="credentials"
|
parentKey="credentials"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{settingsFields.length > 0 && (
|
</>
|
||||||
<FormGroup
|
)}
|
||||||
collapsible
|
{settingsFields.length > 0 && (
|
||||||
defaultActive={false}
|
<FormGroup
|
||||||
keyValue={`settings-${platformDef.id}`}
|
collapsible
|
||||||
style={{ marginBlockStart: 16 }}
|
defaultActive={false}
|
||||||
title={<SettingsTitle schema={platformDef.schema} />}
|
keyValue={`settings-${platformDef.id}`}
|
||||||
variant="borderless"
|
style={{ marginBlockStart: 16 }}
|
||||||
extra={
|
title={<SettingsTitle schema={platformDef.schema} />}
|
||||||
settingsActive ? (
|
variant="borderless"
|
||||||
<Popconfirm
|
extra={
|
||||||
title={t('channel.settingsResetConfirm')}
|
settingsActive ? (
|
||||||
onConfirm={handleResetSettings}
|
<Popconfirm title={t('channel.settingsResetConfirm')} onConfirm={handleResetSettings}>
|
||||||
>
|
<Button icon={<RotateCcw size={14} />} size="small" type="default">
|
||||||
<Button icon={<RotateCcw size={14} />} size="small" type="default">
|
{t('channel.settingsResetDefault')}
|
||||||
{t('channel.settingsResetDefault')}
|
</Button>
|
||||||
</Button>
|
</Popconfirm>
|
||||||
</Popconfirm>
|
) : undefined
|
||||||
) : undefined
|
}
|
||||||
}
|
onCollapse={setSettingsActive}
|
||||||
onCollapse={setSettingsActive}
|
>
|
||||||
>
|
{settingsFields.map((field, i) => (
|
||||||
{settingsFields.map((field, i) => (
|
<SchemaField divider={i !== 0} field={field} key={field.key} parentKey="settings" />
|
||||||
<SchemaField divider={i !== 0} field={field} key={field.key} parentKey="settings" />
|
))}
|
||||||
))}
|
</FormGroup>
|
||||||
</FormGroup>
|
)}
|
||||||
)}
|
</Form>
|
||||||
</Form>
|
);
|
||||||
);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Body;
|
export default Body;
|
||||||
|
|||||||
@@ -291,25 +291,14 @@ const PlatformDetail = memo<PlatformDetailProps>(({ platformDef, agentId, curren
|
|||||||
connectCurrentBot,
|
connectCurrentBot,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleQrAuthenticated = useCallback(
|
const handleExternalAuth = useCallback(
|
||||||
async (creds: { botId: string; botToken: string; userId: string }) => {
|
async (params: { applicationId: string; credentials: Record<string, string> }) => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
setSaveResult(undefined);
|
setSaveResult(undefined);
|
||||||
setConnectResult(undefined);
|
setConnectResult(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const botToken = creds.botToken?.trim();
|
const { applicationId, credentials } = params;
|
||||||
|
|
||||||
if (!creds.botId && !botToken) {
|
|
||||||
throw new Error('Bot Token is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials = {
|
|
||||||
botId: creds.botId,
|
|
||||||
botToken: creds.botToken,
|
|
||||||
userId: creds.userId,
|
|
||||||
};
|
|
||||||
const applicationId = creds.botId || botToken?.slice(0, 16) || '';
|
|
||||||
const settings = form.getFieldValue('settings') || {};
|
const settings = form.getFieldValue('settings') || {};
|
||||||
|
|
||||||
if (currentConfig) {
|
if (currentConfig) {
|
||||||
@@ -429,7 +418,7 @@ const PlatformDetail = memo<PlatformDetailProps>(({ platformDef, agentId, curren
|
|||||||
form={form}
|
form={form}
|
||||||
hasConfig={!!currentConfig}
|
hasConfig={!!currentConfig}
|
||||||
platformDef={platformDef}
|
platformDef={platformDef}
|
||||||
onQrAuthenticated={platformDef.authFlow === 'qrcode' ? handleQrAuthenticated : undefined}
|
onAuthenticated={handleExternalAuth}
|
||||||
/>
|
/>
|
||||||
<Footer
|
<Footer
|
||||||
connectResult={connectResult}
|
connectResult={connectResult}
|
||||||
|
|||||||
11
src/routes/(main)/agent/channel/platform/registry.ts
Normal file
11
src/routes/(main)/agent/channel/platform/registry.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ComponentType } from 'react';
|
||||||
|
|
||||||
|
import type { PlatformCredentialBodyProps } from './types';
|
||||||
|
import WechatCredentialBody from './wechat/CredentialBody';
|
||||||
|
|
||||||
|
export const platformCredentialBodyMap: Record<
|
||||||
|
string,
|
||||||
|
ComponentType<PlatformCredentialBodyProps>
|
||||||
|
> = {
|
||||||
|
wechat: WechatCredentialBody,
|
||||||
|
};
|
||||||
11
src/routes/(main)/agent/channel/platform/types.ts
Normal file
11
src/routes/(main)/agent/channel/platform/types.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export interface PlatformCredentialBodyProps {
|
||||||
|
currentConfig?: {
|
||||||
|
applicationId: string;
|
||||||
|
credentials: Record<string, string>;
|
||||||
|
};
|
||||||
|
hasConfig?: boolean;
|
||||||
|
onAuthenticated?: (params: {
|
||||||
|
applicationId: string;
|
||||||
|
credentials: Record<string, string>;
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Flexbox, FormItem } from '@lobehub/ui';
|
||||||
|
import { createStaticStyles } from 'antd-style';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { FormInput, FormPassword } from '@/components/FormInput';
|
||||||
|
|
||||||
|
import QrCodeAuth from './QrCodeAuth';
|
||||||
|
|
||||||
|
const styles = createStaticStyles(({ css }) => ({
|
||||||
|
header: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-block-end: 16px;
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ReadOnlyField = memo<{
|
||||||
|
description?: string;
|
||||||
|
divider?: boolean;
|
||||||
|
label: string;
|
||||||
|
password?: boolean;
|
||||||
|
tag: string;
|
||||||
|
value?: string;
|
||||||
|
}>(({ description, divider, label, password, tag, value }) => {
|
||||||
|
const InputComponent = password ? FormPassword : FormInput;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
desc={description}
|
||||||
|
divider={divider}
|
||||||
|
label={label}
|
||||||
|
minWidth={'max(50%, 400px)'}
|
||||||
|
tag={tag}
|
||||||
|
variant="borderless"
|
||||||
|
>
|
||||||
|
<InputComponent readOnly value={value || ''} />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface WechatConnectedInfoProps {
|
||||||
|
currentConfig: {
|
||||||
|
applicationId: string;
|
||||||
|
credentials: Record<string, string>;
|
||||||
|
};
|
||||||
|
onQrAuthenticated?: (credentials: { botId: string; botToken: string; userId: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WechatConnectedInfo = memo<WechatConnectedInfoProps>(
|
||||||
|
({ currentConfig, onQrAuthenticated }) => {
|
||||||
|
const { t: _t } = useTranslation('agent');
|
||||||
|
const t = _t as (key: string) => string;
|
||||||
|
|
||||||
|
const shouldShowApplicationId =
|
||||||
|
!!currentConfig.applicationId &&
|
||||||
|
currentConfig.applicationId !== currentConfig.credentials.botId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<Flexbox gap={4}>
|
||||||
|
<div style={{ fontSize: 16, fontWeight: 600 }}>{t('channel.wechatConnectedInfo')}</div>
|
||||||
|
<div style={{ color: 'var(--ant-color-text-secondary)', fontSize: 13 }}>
|
||||||
|
{t('channel.wechatManagedCredentials')}
|
||||||
|
</div>
|
||||||
|
</Flexbox>
|
||||||
|
{onQrAuthenticated && (
|
||||||
|
<QrCodeAuth
|
||||||
|
buttonLabel={t('channel.wechatRebind')}
|
||||||
|
buttonType="default"
|
||||||
|
showTips={false}
|
||||||
|
onAuthenticated={onQrAuthenticated}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{shouldShowApplicationId && (
|
||||||
|
<ReadOnlyField
|
||||||
|
description={t('channel.applicationIdHint')}
|
||||||
|
label={t('channel.applicationId')}
|
||||||
|
tag="applicationId"
|
||||||
|
value={currentConfig.applicationId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{process.env.NODE_ENV === 'development' && (
|
||||||
|
<>
|
||||||
|
<ReadOnlyField
|
||||||
|
description={t('channel.wechatBotIdHint')}
|
||||||
|
divider={shouldShowApplicationId}
|
||||||
|
label={t('channel.wechatBotId')}
|
||||||
|
tag="botId"
|
||||||
|
value={currentConfig.credentials.botId}
|
||||||
|
/>
|
||||||
|
<ReadOnlyField
|
||||||
|
divider
|
||||||
|
password
|
||||||
|
description={t('channel.botTokenEncryptedHint')}
|
||||||
|
label={t('channel.botToken')}
|
||||||
|
tag="botToken"
|
||||||
|
value={currentConfig.credentials.botToken}
|
||||||
|
/>
|
||||||
|
<ReadOnlyField
|
||||||
|
divider
|
||||||
|
description={t('channel.wechatUserIdHint')}
|
||||||
|
label={t('channel.wechatUserId')}
|
||||||
|
tag="userId"
|
||||||
|
value={currentConfig.credentials.userId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WechatConnectedInfo;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
|
import type { PlatformCredentialBodyProps } from '../types';
|
||||||
|
import ConnectedInfo from './ConnectedInfo';
|
||||||
|
import QrCodeAuth from './QrCodeAuth';
|
||||||
|
|
||||||
|
const CredentialBody = memo<PlatformCredentialBodyProps>(
|
||||||
|
({ currentConfig, hasConfig, onAuthenticated }) => {
|
||||||
|
const handleQrAuthenticated = useCallback(
|
||||||
|
(creds: { botId: string; botToken: string; userId: string }) => {
|
||||||
|
const botToken = creds.botToken?.trim();
|
||||||
|
if (!creds.botId && !botToken) return;
|
||||||
|
|
||||||
|
const applicationId = creds.botId || botToken?.slice(0, 16) || '';
|
||||||
|
onAuthenticated?.({
|
||||||
|
applicationId,
|
||||||
|
credentials: {
|
||||||
|
botId: creds.botId,
|
||||||
|
botToken: creds.botToken,
|
||||||
|
userId: creds.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onAuthenticated],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasConfig && currentConfig) {
|
||||||
|
return (
|
||||||
|
<ConnectedInfo currentConfig={currentConfig} onQrAuthenticated={handleQrAuthenticated} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onAuthenticated) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', padding: '16px 0' }}>
|
||||||
|
<QrCodeAuth onAuthenticated={handleQrAuthenticated} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CredentialBody;
|
||||||
@@ -241,13 +241,6 @@ export abstract class ClientFactory {
|
|||||||
* Contains metadata, factory, and validation. All runtime operations go through PlatformClient.
|
* Contains metadata, factory, and validation. All runtime operations go through PlatformClient.
|
||||||
*/
|
*/
|
||||||
export interface PlatformDefinition {
|
export interface PlatformDefinition {
|
||||||
/**
|
|
||||||
* Authentication flow for obtaining credentials.
|
|
||||||
* - 'qrcode': QR code scan flow (e.g. WeChat iLink)
|
|
||||||
* When set, the frontend renders a QR code auth UI instead of manual credential inputs.
|
|
||||||
*/
|
|
||||||
authFlow?: 'qrcode';
|
|
||||||
|
|
||||||
/** Factory for creating PlatformClient instances and validating credentials/settings. */
|
/** Factory for creating PlatformClient instances and validating credentials/settings. */
|
||||||
clientFactory: ClientFactory;
|
clientFactory: ClientFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { WechatClientFactory } from './client';
|
|||||||
import { schema } from './schema';
|
import { schema } from './schema';
|
||||||
|
|
||||||
export const wechat: PlatformDefinition = {
|
export const wechat: PlatformDefinition = {
|
||||||
authFlow: 'qrcode',
|
|
||||||
id: 'wechat',
|
id: 'wechat',
|
||||||
name: 'WeChat',
|
name: 'WeChat',
|
||||||
connectionMode: 'persistent',
|
connectionMode: 'persistent',
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { DEFAULT_DEBOUNCE_MS, MAX_DEBOUNCE_MS } from '../const';
|
|||||||
import type { FieldSchema } from '../types';
|
import type { FieldSchema } from '../types';
|
||||||
|
|
||||||
export const schema: FieldSchema[] = [
|
export const schema: FieldSchema[] = [
|
||||||
// No credentials fields — WeChat uses QR code auth flow (authFlow: 'qrcode').
|
// No credentials fields — credentials are populated automatically after QR scan.
|
||||||
// botToken, botId, and userId are populated automatically after QR scan.
|
|
||||||
{
|
{
|
||||||
key: 'settings',
|
key: 'settings',
|
||||||
label: 'channel.settings',
|
label: 'channel.settings',
|
||||||
|
|||||||
Reference in New Issue
Block a user