feat: 语言国际化重构
This commit is contained in:
@@ -14,6 +14,8 @@ export interface ChannelMeta {
|
||||
instructions: string[];
|
||||
}
|
||||
|
||||
export type ChannelTextResolver = (path: string, fallback: string) => string;
|
||||
|
||||
function createField(
|
||||
key: string,
|
||||
label: string,
|
||||
@@ -94,6 +96,93 @@ function createUrlField(
|
||||
return createField(key, label, 'url', placeholder, description, required, options);
|
||||
}
|
||||
|
||||
function buildChannelMetaPath(channelType: string, suffix: string): string {
|
||||
return `channels.meta.${channelType}.${suffix}`;
|
||||
}
|
||||
|
||||
function resolveChannelText(
|
||||
channelType: string,
|
||||
suffix: string,
|
||||
fallback: string,
|
||||
resolveText?: ChannelTextResolver,
|
||||
): string {
|
||||
return resolveText ? resolveText(buildChannelMetaPath(channelType, suffix), fallback) : fallback;
|
||||
}
|
||||
|
||||
export function formatChannelTypeLabel(channelType: string): string {
|
||||
const normalized = String(channelType ?? '')
|
||||
.split(/[-_]/)
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (normalized.length === 0) return String(channelType ?? '');
|
||||
|
||||
return normalized
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function localizeChannelField(
|
||||
channelType: string,
|
||||
field: ChannelConfigFieldMeta,
|
||||
resolveText?: ChannelTextResolver,
|
||||
): ChannelConfigFieldMeta {
|
||||
if (!resolveText) {
|
||||
return field;
|
||||
}
|
||||
|
||||
return {
|
||||
...field,
|
||||
label: resolveChannelText(channelType, `fields.${field.key}.label`, field.label, resolveText),
|
||||
placeholder: field.placeholder
|
||||
? resolveChannelText(channelType, `fields.${field.key}.placeholder`, field.placeholder, resolveText)
|
||||
: field.placeholder,
|
||||
description: field.description
|
||||
? resolveChannelText(channelType, `fields.${field.key}.description`, field.description, resolveText)
|
||||
: field.description,
|
||||
docsUrl: field.docsUrl
|
||||
? resolveChannelText(channelType, `fields.${field.key}.docsUrl`, field.docsUrl, resolveText)
|
||||
: field.docsUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function localizeChannelMeta(meta: ChannelMeta, resolveText?: ChannelTextResolver): ChannelMeta {
|
||||
if (!resolveText) {
|
||||
return meta;
|
||||
}
|
||||
|
||||
return {
|
||||
...meta,
|
||||
name: resolveChannelText(meta.type, 'name', meta.name, resolveText),
|
||||
description: resolveChannelText(meta.type, 'description', meta.description, resolveText),
|
||||
docsUrl: meta.docsUrl
|
||||
? resolveChannelText(meta.type, 'docsUrl', meta.docsUrl, resolveText)
|
||||
: meta.docsUrl,
|
||||
instructions: meta.instructions.map((instruction, index) => (
|
||||
resolveChannelText(meta.type, `instructions.${index}`, instruction, resolveText)
|
||||
)),
|
||||
configFields: meta.configFields.map((field) => localizeChannelField(meta.type, field, resolveText)),
|
||||
};
|
||||
}
|
||||
|
||||
export function getChannelDisplayName(
|
||||
channelType: string | null | undefined,
|
||||
resolveText?: ChannelTextResolver,
|
||||
fallbackName?: string,
|
||||
): string {
|
||||
const meta = getChannelMeta(channelType);
|
||||
return resolveChannelText(meta.type, 'name', fallbackName ?? meta.name, resolveText);
|
||||
}
|
||||
|
||||
export function getChannelDisplayDescription(
|
||||
channelType: string | null | undefined,
|
||||
resolveText?: ChannelTextResolver,
|
||||
fallbackDescription?: string,
|
||||
): string {
|
||||
const meta = getChannelMeta(channelType);
|
||||
return resolveChannelText(meta.type, 'description', fallbackDescription ?? meta.description, resolveText);
|
||||
}
|
||||
|
||||
export const PRIMARY_CHANNEL_TYPES = [
|
||||
'telegram',
|
||||
'discord',
|
||||
@@ -109,7 +198,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
{
|
||||
type: 'telegram',
|
||||
name: 'Telegram',
|
||||
description: '使用 @BotFather 提供的机器人令牌连接 Telegram。',
|
||||
description: 'Connect Telegram using the bot token provided by @BotFather.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://core.telegram.org/bots',
|
||||
configFields: [
|
||||
@@ -117,7 +206,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
'botToken',
|
||||
'Bot Token',
|
||||
'123456:telegram-bot-token',
|
||||
'从 @BotFather 获取的机器人令牌。',
|
||||
'Bot token issued by @BotFather.',
|
||||
true,
|
||||
{
|
||||
docsUrl: 'https://core.telegram.org/bots#botfather',
|
||||
@@ -128,7 +217,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
'allowedUsers',
|
||||
'Allowed Users',
|
||||
'12345678,98765432',
|
||||
'可选。限制允许与机器人对话的 Telegram 用户 ID。',
|
||||
'Optional. Comma-separated Telegram user IDs allowed to chat with this bot.',
|
||||
false,
|
||||
{
|
||||
envVar: 'TELEGRAM_ALLOWED_USERS',
|
||||
@@ -137,16 +226,16 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'在 Telegram 中使用 @BotFather 创建机器人并复制 Bot Token。',
|
||||
'如需限制访问范围,可把用户 ID 通过环境变量或配置项预先准备好。',
|
||||
'如果需要限制测试范围,可填写允许访问的用户 ID 列表。',
|
||||
'保存后在消息频道页确认默认账号、归属 Agent 和运行态状态,完成 save and connect。',
|
||||
'Create a bot with @BotFather in Telegram and copy the Bot Token.',
|
||||
'If you want to restrict access, collect the Telegram user IDs that are allowed to talk to this bot.',
|
||||
'Paste the token and, if needed, the allowed user list into the fields below.',
|
||||
'After saving, confirm the default account and Agent binding from the Channels flow.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'discord',
|
||||
name: 'Discord',
|
||||
description: '使用开发者门户提供的机器人令牌连接 Discord。',
|
||||
description: 'Connect Discord using the bot token provided in the developer portal.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://discord.com/developers/docs/intro',
|
||||
configFields: [
|
||||
@@ -154,7 +243,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
'token',
|
||||
'Bot Token',
|
||||
'discord-bot-token',
|
||||
'Discord Bot Token。',
|
||||
'Discord bot token.',
|
||||
true,
|
||||
{
|
||||
docsUrl: 'https://discord.com/developers/applications',
|
||||
@@ -165,7 +254,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
'guildId',
|
||||
'Guild ID',
|
||||
'123456789012345678',
|
||||
'推荐填写,便于定位绑定的服务器。',
|
||||
'Recommended. Limits the bot to a specific server.',
|
||||
true,
|
||||
{
|
||||
envVar: 'DISCORD_GUILD_ID',
|
||||
@@ -175,7 +264,7 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
'channelId',
|
||||
'Channel ID',
|
||||
'123456789012345678',
|
||||
'可选。指定默认投递频道。',
|
||||
'Optional. Sends messages to a default channel.',
|
||||
false,
|
||||
{
|
||||
envVar: 'DISCORD_CHANNEL_ID',
|
||||
@@ -184,240 +273,389 @@ export const CHANNEL_META_LIST: ChannelMeta[] = [
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'在 Discord Developer Portal 创建应用并启用 Bot。',
|
||||
'复制 Token,并在目标服务器中邀请该机器人。',
|
||||
'把 Token 和可选的 Guild / Channel 标识整理成环境变量后,便于本地与部署环境复用。',
|
||||
'如果需要固定默认频道,可补充填写 Guild ID / Channel ID。',
|
||||
'Create an application in the Discord Developer Portal and enable the Bot feature.',
|
||||
'Copy the bot token and invite the bot to the target server.',
|
||||
'Prepare the token and optional Guild or Channel identifiers as environment variables if you deploy them elsewhere.',
|
||||
'Fill in the fields, save, and verify the target server/channel mapping if you pin one.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'whatsapp',
|
||||
name: 'WhatsApp',
|
||||
description: '通过扫描二维码连接 WhatsApp(无需手机号)。',
|
||||
description: 'Connect WhatsApp by scanning a QR code, without requiring a phone number.',
|
||||
connectionType: 'qr',
|
||||
docsUrl: 'https://developers.facebook.com/docs/whatsapp',
|
||||
configFields: [],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'保存后等待 runtime 侧二维码能力接入。',
|
||||
'如果后续切换到服务化接入,可把账号凭据改为环境变量管理。',
|
||||
'当前 `zn-ai` 已预留配置和状态位,二维码拉起仍是后续波次。',
|
||||
'在完全对齐 ClawX 前,建议先使用默认账号完成路由与绑定链路验证。',
|
||||
'Save the channel first so the runtime can request a QR session when support is ready.',
|
||||
'If you later move to a hosted integration, keep the account credentials in environment variables.',
|
||||
'The current zn-ai flow keeps the configuration and state placeholders aligned with ClawX.',
|
||||
'Until the QR bridge is fully wired, use the default account path for routing and ownership validation.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'wechat',
|
||||
name: 'WeChat',
|
||||
description: '通过腾讯官方 OpenClaw 插件扫码连接个人微信。',
|
||||
description: 'Connect personal WeChat by scanning through the official OpenClaw plugin.',
|
||||
connectionType: 'qr',
|
||||
docsUrl: 'https://developers.weixin.qq.com/doc/',
|
||||
configFields: [],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'该频道依赖插件与二维码链路,当前页面已保留入口。',
|
||||
'扫码接入通常不需要额外字段,但后续如果插件暴露 envVar,可直接在 modal 中复用。',
|
||||
'后续需要补齐扫码事件、成功回调和账号自动发现。',
|
||||
'Agent 绑定、默认账号和删除流程已可先行对齐。',
|
||||
'This channel depends on the plugin and QR flow, and the current page already reserves the entry point.',
|
||||
'QR-based onboarding usually does not need extra fields, but environment-backed options can be added later if the plugin exposes them.',
|
||||
'The remaining QR events, success callbacks, and account discovery will be filled in later.',
|
||||
'Agent ownership, default account handling, and cleanup flows can already stay aligned here.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'dingtalk',
|
||||
name: 'DingTalk',
|
||||
description: '通过 OpenClaw 渠道插件连接钉钉(Stream 模式)。',
|
||||
description: 'Connect DingTalk through the OpenClaw channel plugin in Stream mode.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://open.dingtalk.com/document',
|
||||
configFields: [
|
||||
createTextField('clientId', 'Client ID', 'ding-app-key', '钉钉应用的 AppKey。', true, {
|
||||
envVar: 'DINGTALK_CLIENT_ID',
|
||||
}),
|
||||
createPasswordField('clientSecret', 'Client Secret', 'ding-app-secret', '钉钉应用的 AppSecret。', true, {
|
||||
envVar: 'DINGTALK_CLIENT_SECRET',
|
||||
}),
|
||||
createTextField('robotCode', 'Robot Code', 'dingxxxx', '可选。机器人编码。', false, {
|
||||
envVar: 'DINGTALK_ROBOT_CODE',
|
||||
}),
|
||||
createTextField('corpId', 'Corp ID', 'dingcorp123', '可选。企业 corpId。', false, {
|
||||
envVar: 'DINGTALK_CORP_ID',
|
||||
}),
|
||||
createTextField('agentId', 'Agent ID', '123456789', '可选。机器人 AgentId。', false, {
|
||||
envVar: 'DINGTALK_AGENT_ID',
|
||||
}),
|
||||
createTextField(
|
||||
'clientId',
|
||||
'Client ID',
|
||||
'ding-app-key',
|
||||
'DingTalk app key.',
|
||||
true,
|
||||
{
|
||||
envVar: 'DINGTALK_CLIENT_ID',
|
||||
},
|
||||
),
|
||||
createPasswordField(
|
||||
'clientSecret',
|
||||
'Client Secret',
|
||||
'ding-app-secret',
|
||||
'DingTalk app secret.',
|
||||
true,
|
||||
{
|
||||
envVar: 'DINGTALK_CLIENT_SECRET',
|
||||
},
|
||||
),
|
||||
createTextField(
|
||||
'robotCode',
|
||||
'Robot Code',
|
||||
'dingxxxx',
|
||||
'Optional. DingTalk robot code.',
|
||||
false,
|
||||
{
|
||||
envVar: 'DINGTALK_ROBOT_CODE',
|
||||
},
|
||||
),
|
||||
createTextField(
|
||||
'corpId',
|
||||
'Corp ID',
|
||||
'dingcorp123',
|
||||
'Optional. Enterprise corpId.',
|
||||
false,
|
||||
{
|
||||
envVar: 'DINGTALK_CORP_ID',
|
||||
},
|
||||
),
|
||||
createTextField(
|
||||
'agentId',
|
||||
'Agent ID',
|
||||
'123456789',
|
||||
'Optional. DingTalk AgentId.',
|
||||
false,
|
||||
{
|
||||
envVar: 'DINGTALK_AGENT_ID',
|
||||
},
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在钉钉开发者后台创建机器人并获取 AppKey / AppSecret。',
|
||||
'如果你更习惯部署时注入配置,可以把这些值放到环境变量里再填入 modal。',
|
||||
'按需补充 Robot Code、Corp ID 或 Agent ID 以兼容不同部署方式。',
|
||||
'保存后优先验证默认账号、频道归属和 gateway 重载是否收敛。',
|
||||
'Create a bot app in DingTalk and collect the AppKey and AppSecret.',
|
||||
'If you prefer injecting configuration at deploy time, map these values through environment variables first.',
|
||||
'Add Robot Code, Corp ID, or Agent ID as needed to match your deployment mode.',
|
||||
'After saving, verify the default account, channel ownership, and runtime reload path.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'feishu',
|
||||
name: 'Feishu / Lark',
|
||||
description: '通过飞书官方推出的 OpenClaw 插件连接飞书/Lark 机器人。',
|
||||
description: 'Connect a Feishu/Lark bot through Feishu\'s official OpenClaw plugin.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://open.feishu.cn/document',
|
||||
configFields: [
|
||||
createTextField('appId', 'App ID', 'cli_xxxxxxxxx', '飞书应用的 App ID。', true, {
|
||||
envVar: 'FEISHU_APP_ID',
|
||||
}),
|
||||
createPasswordField('appSecret', 'App Secret', 'app-secret', '飞书应用的 App Secret。', true, {
|
||||
envVar: 'FEISHU_APP_SECRET',
|
||||
}),
|
||||
createTextField(
|
||||
'appId',
|
||||
'App ID',
|
||||
'cli_xxxxxxxxx',
|
||||
'Feishu app ID.',
|
||||
true,
|
||||
{
|
||||
envVar: 'FEISHU_APP_ID',
|
||||
},
|
||||
),
|
||||
createPasswordField(
|
||||
'appSecret',
|
||||
'App Secret',
|
||||
'app-secret',
|
||||
'Feishu app secret.',
|
||||
true,
|
||||
{
|
||||
envVar: 'FEISHU_APP_SECRET',
|
||||
},
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在飞书开放平台创建机器人应用并开启事件订阅。',
|
||||
'App ID 与 App Secret 可以直接从环境变量注入,方便本地、测试与生产环境统一。',
|
||||
'填入 App ID 与 App Secret,保存后完成默认账号与 Agent 绑定。',
|
||||
'这是 ClawX 中验证最完整的频道之一,建议作为首个联调样板。',
|
||||
'Create a bot application in the Feishu or Lark developer console and enable the needed events.',
|
||||
'App ID and App Secret can come directly from environment variables so local, staging, and production stay consistent.',
|
||||
'Fill in the credentials and save to finish the default account and Agent binding flow.',
|
||||
'This is one of the most complete ClawX-aligned channel flows and a good first integration to validate.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'wecom',
|
||||
name: 'WeCom',
|
||||
description: '通过插件连接企业微信机器人。',
|
||||
description: 'Connect a WeCom bot through the plugin.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://developer.work.weixin.qq.com/document',
|
||||
configFields: [
|
||||
createTextField('botId', 'Bot ID', 'wecom-bot-id', '企业微信机器人或应用标识。', true, {
|
||||
envVar: 'WECOM_BOT_ID',
|
||||
}),
|
||||
createPasswordField('secret', 'Secret', 'wecom-secret', '企业微信机器人密钥。', true, {
|
||||
envVar: 'WECOM_BOT_SECRET',
|
||||
}),
|
||||
createTextField(
|
||||
'botId',
|
||||
'Bot ID',
|
||||
'wecom-bot-id',
|
||||
'WeCom bot or app identifier.',
|
||||
true,
|
||||
{
|
||||
envVar: 'WECOM_BOT_ID',
|
||||
},
|
||||
),
|
||||
createPasswordField(
|
||||
'secret',
|
||||
'Secret',
|
||||
'wecom-secret',
|
||||
'WeCom bot secret.',
|
||||
true,
|
||||
{
|
||||
envVar: 'WECOM_BOT_SECRET',
|
||||
},
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在企业微信管理后台创建机器人并复制关键信息。',
|
||||
'如果你已经在部署层维护 env vars,可直接用环境变量名作为对照来填充字段。',
|
||||
'建议先用默认账号联通消息,再扩展多账号分工。',
|
||||
'保存后使用消息频道页统一管理默认账号和 Agent 归属。',
|
||||
'Create the bot in the WeCom admin console and copy the required credentials.',
|
||||
'If your deployment already manages env vars, keep the same variable names here for easier mapping.',
|
||||
'Start with the main account to validate message delivery before expanding to multiple accounts.',
|
||||
'After saving, keep account ownership and Agent binding managed from the Channels page.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'qqbot',
|
||||
name: 'QQ Bot',
|
||||
description: '连接 QQ 机器人频道(OpenClaw 3.31 起内置)。',
|
||||
description: 'Connect a QQ bot channel, built in since OpenClaw 3.31.',
|
||||
connectionType: 'token',
|
||||
docsUrl: 'https://bot.q.qq.com/wiki',
|
||||
configFields: [
|
||||
createTextField('appId', 'App ID', 'qq-app-id', 'QQ Bot App ID。', true, {
|
||||
envVar: 'QQBOT_APP_ID',
|
||||
}),
|
||||
createPasswordField('clientSecret', 'Client Secret', 'qq-client-secret', 'QQ Bot Client Secret。', true, {
|
||||
envVar: 'QQBOT_CLIENT_SECRET',
|
||||
}),
|
||||
createTextField(
|
||||
'appId',
|
||||
'App ID',
|
||||
'qq-app-id',
|
||||
'QQ Bot app ID.',
|
||||
true,
|
||||
{
|
||||
envVar: 'QQBOT_APP_ID',
|
||||
},
|
||||
),
|
||||
createPasswordField(
|
||||
'clientSecret',
|
||||
'Client Secret',
|
||||
'qq-client-secret',
|
||||
'QQ Bot client secret.',
|
||||
true,
|
||||
{
|
||||
envVar: 'QQBOT_CLIENT_SECRET',
|
||||
},
|
||||
),
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'在 QQ Bot 平台创建机器人并获取 App ID 与密钥。',
|
||||
'App ID 和 Client Secret 都可以通过环境变量形式提前约定,方便和 runtime 对齐。',
|
||||
'如需多账号场景,新增账号时建议使用清晰的 accountId 规则。',
|
||||
'对齐完成后,Cron、目标选择和路由会直接复用这里的配置。',
|
||||
'Create the bot in the QQ Bot platform and obtain the App ID and secret.',
|
||||
'Both App ID and Client Secret can be supplied ahead of time through environment variables to match the runtime.',
|
||||
'If you need multiple accounts, adopt a clear accountId naming pattern when you add them later.',
|
||||
'Once aligned, downstream routing and target selection can reuse the configuration stored here.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'signal',
|
||||
name: 'Signal',
|
||||
description: '通过 Signal 服务号码接入消息路由。',
|
||||
description: 'Connect Signal through a registered service number.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createTextField('phoneNumber', 'Phone Number', '+8613800138000', 'Signal 注册号码。', true),
|
||||
createTextField(
|
||||
'phoneNumber',
|
||||
'Phone Number',
|
||||
'+8613800138000',
|
||||
'Signal phone number registered for this bridge.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'准备已注册 Signal 的号码。',
|
||||
'补充 runtime 侧连接能力后即可复用当前配置模型。',
|
||||
'Prepare a registered Signal number for the bridge service.',
|
||||
'Once runtime support lands, this channel can reuse the same configuration structure.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'imessage',
|
||||
name: 'iMessage',
|
||||
description: '通过桥接服务接入 iMessage。',
|
||||
description: 'Connect iMessage through a bridge service.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createUrlField('serverUrl', 'Server URL', 'https://imessage-bridge.example.com', '桥接服务地址。', true),
|
||||
createPasswordField('password', 'Password', 'bridge-password', '桥接服务认证密码。', true),
|
||||
createUrlField(
|
||||
'serverUrl',
|
||||
'Server URL',
|
||||
'https://imessage-bridge.example.com',
|
||||
'Bridge service URL.',
|
||||
true,
|
||||
),
|
||||
createPasswordField(
|
||||
'password',
|
||||
'Password',
|
||||
'bridge-password',
|
||||
'Bridge service password.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'先部署 iMessage bridge 服务。',
|
||||
'保存后验证服务地址和认证信息是否可用。',
|
||||
'Deploy the iMessage bridge service first.',
|
||||
'After saving, verify the service URL and credentials before routing traffic here.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'matrix',
|
||||
name: 'Matrix',
|
||||
description: '通过 homeserver 和 access token 接入 Matrix。',
|
||||
description: 'Connect Matrix using a homeserver and access token.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createUrlField('homeserver', 'Homeserver', 'https://matrix.example.com', 'Matrix homeserver 地址。', true),
|
||||
createPasswordField('accessToken', 'Access Token', 'matrix-access-token', 'Matrix access token。', true),
|
||||
createUrlField(
|
||||
'homeserver',
|
||||
'Homeserver',
|
||||
'https://matrix.example.com',
|
||||
'Matrix homeserver URL.',
|
||||
true,
|
||||
),
|
||||
createPasswordField(
|
||||
'accessToken',
|
||||
'Access Token',
|
||||
'matrix-access-token',
|
||||
'Matrix access token.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'准备 Matrix homeserver 与 access token。',
|
||||
'建议后续将 channel target 自动发现扩展到 roomId 级别。',
|
||||
'Prepare the Matrix homeserver URL and an access token.',
|
||||
'A later enhancement can promote channel target discovery down to the room level.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
name: 'LINE',
|
||||
description: '通过 LINE Messaging API 接入机器人。',
|
||||
description: 'Connect a LINE bot using the Messaging API.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createPasswordField('channelAccessToken', 'Channel Access Token', 'line-channel-access-token', 'LINE Channel Access Token。', true),
|
||||
createPasswordField('channelSecret', 'Channel Secret', 'line-channel-secret', 'LINE Channel Secret。', true),
|
||||
createPasswordField(
|
||||
'channelAccessToken',
|
||||
'Channel Access Token',
|
||||
'line-channel-access-token',
|
||||
'LINE channel access token.',
|
||||
true,
|
||||
),
|
||||
createPasswordField(
|
||||
'channelSecret',
|
||||
'Channel Secret',
|
||||
'line-channel-secret',
|
||||
'LINE channel secret.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在 LINE Developers 中创建 Messaging API channel。',
|
||||
'保存后验证 token、secret 与默认投递账号。',
|
||||
'Create a Messaging API channel in LINE Developers.',
|
||||
'After saving, verify the token, secret, and the default delivery account.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'msteams',
|
||||
name: 'Microsoft Teams',
|
||||
description: '通过 Bot Framework 凭证接入 Microsoft Teams。',
|
||||
description: 'Connect Microsoft Teams through Bot Framework credentials.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createTextField('appId', 'App ID', 'teams-app-id', 'Bot Framework App ID。', true),
|
||||
createPasswordField('appPassword', 'App Password', 'teams-app-password', 'Bot Framework App Password。', true),
|
||||
createTextField(
|
||||
'appId',
|
||||
'App ID',
|
||||
'teams-app-id',
|
||||
'Bot Framework App ID.',
|
||||
true,
|
||||
),
|
||||
createPasswordField(
|
||||
'appPassword',
|
||||
'App Password',
|
||||
'teams-app-password',
|
||||
'Bot Framework App Password.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在 Azure / Bot Framework 中创建 Teams Bot。',
|
||||
'保存后与默认账号、Agent 绑定一并验证。',
|
||||
'Create a Teams bot in Azure or Bot Framework.',
|
||||
'After saving, verify the default account and Agent ownership together.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'googlechat',
|
||||
name: 'Google Chat',
|
||||
description: '通过 Google Chat 服务账号接入消息投递。',
|
||||
description: 'Connect Google Chat using a service account.',
|
||||
connectionType: 'webhook',
|
||||
configFields: [
|
||||
createTextareaField('serviceAccountKey', 'Service Account Key', '{ ...json... }', 'Google 服务账号 JSON 内容。', true),
|
||||
createTextareaField(
|
||||
'serviceAccountKey',
|
||||
'Service Account Key',
|
||||
'{ ...json... }',
|
||||
'Google service account JSON payload.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: false,
|
||||
instructions: [
|
||||
'在 Google Cloud 中创建服务账号并开启 Chat API。',
|
||||
'将服务账号 JSON 粘贴到配置中,后续可扩展 webhook / space 目标发现。',
|
||||
'Create a service account in Google Cloud and enable the Chat API.',
|
||||
'Paste the service account JSON here. Webhook and space discovery can be added later.',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'mattermost',
|
||||
name: 'Mattermost',
|
||||
description: '通过服务地址与 Bot Token 接入 Mattermost。',
|
||||
description: 'Connect Mattermost using a server URL and bot token.',
|
||||
connectionType: 'token',
|
||||
configFields: [
|
||||
createUrlField('serverUrl', 'Server URL', 'https://mattermost.example.com', 'Mattermost 服务地址。', true),
|
||||
createPasswordField('botToken', 'Bot Token', 'mattermost-bot-token', 'Mattermost Bot Token。', true),
|
||||
createUrlField(
|
||||
'serverUrl',
|
||||
'Server URL',
|
||||
'https://mattermost.example.com',
|
||||
'Mattermost server URL.',
|
||||
true,
|
||||
),
|
||||
createPasswordField(
|
||||
'botToken',
|
||||
'Bot Token',
|
||||
'mattermost-bot-token',
|
||||
'Mattermost bot token.',
|
||||
true,
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'在 Mattermost 中创建 Bot 并生成 Token。',
|
||||
'后续建议把 channel target 扩展到 team/channel 自动发现。',
|
||||
'Create a bot in Mattermost and generate a token.',
|
||||
'A future iteration can extend channel target discovery down to team or channel scopes.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -430,16 +668,21 @@ export const CHANNEL_META_MAP = CHANNEL_META_LIST.reduce<Record<string, ChannelM
|
||||
export const DEFAULT_CHANNEL_META: ChannelMeta = {
|
||||
type: 'custom',
|
||||
name: 'Custom Channel',
|
||||
description: '通用频道模板,用于承接尚未内建 schema 的自定义渠道或插件。',
|
||||
description: 'Generic channel template for integrations or plugins without a dedicated schema yet.',
|
||||
connectionType: 'plugin',
|
||||
docsUrl: undefined,
|
||||
configFields: [
|
||||
createTokenField('token', 'Token', 'token-or-secret', '粘贴该渠道要求的 token、secret 或访问凭据。'),
|
||||
createTokenField(
|
||||
'token',
|
||||
'Token',
|
||||
'token-or-secret',
|
||||
'Paste the token, secret, or access credential required by this channel.',
|
||||
),
|
||||
],
|
||||
isPlugin: true,
|
||||
instructions: [
|
||||
'当渠道 schema 仍在演进,或由外部插件提供时,可先使用该模板。',
|
||||
'保存后仍可通过消息频道页管理默认账号、Agent 绑定与运行态状态。',
|
||||
'Use this template when the channel schema is still evolving or when an external plugin provides the integration.',
|
||||
'After saving, you can still manage the default account, Agent binding, and runtime state from Channels.',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -453,10 +696,10 @@ export function getChannelMeta(channelType: string | null | undefined): ChannelM
|
||||
...DEFAULT_CHANNEL_META,
|
||||
type: trimmed,
|
||||
name: trimmed,
|
||||
description: `${trimmed} 的通用频道模板。`,
|
||||
description: `${trimmed} generic channel template.`,
|
||||
instructions: [
|
||||
...DEFAULT_CHANNEL_META.instructions,
|
||||
`当前 runtime 尚未为 ${trimmed} 提供专用 schema,先以数据驱动方式接入。`,
|
||||
`${trimmed} does not have a dedicated schema yet, so configure it with the generic template for now.`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user