feat: 支持可选配置图床服务器地址
This commit is contained in:
@@ -63,6 +63,7 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
clientSecretFile: input.tokenFile,
|
clientSecretFile: input.tokenFile,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
|
imageServerBaseUrl: input.imageServerBaseUrl,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function resolveQQBotAccount(
|
|||||||
dmPolicy: qqbot?.dmPolicy,
|
dmPolicy: qqbot?.dmPolicy,
|
||||||
allowFrom: qqbot?.allowFrom,
|
allowFrom: qqbot?.allowFrom,
|
||||||
systemPrompt: qqbot?.systemPrompt,
|
systemPrompt: qqbot?.systemPrompt,
|
||||||
|
imageServerBaseUrl: qqbot?.imageServerBaseUrl,
|
||||||
};
|
};
|
||||||
appId = qqbot?.appId ?? "";
|
appId = qqbot?.appId ?? "";
|
||||||
} else {
|
} else {
|
||||||
@@ -97,6 +98,7 @@ export function resolveQQBotAccount(
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
secretSource,
|
secretSource,
|
||||||
systemPrompt: accountConfig.systemPrompt,
|
systemPrompt: accountConfig.systemPrompt,
|
||||||
|
imageServerBaseUrl: accountConfig.imageServerBaseUrl || process.env.QQBOT_IMAGE_SERVER_BASE_URL,
|
||||||
config: accountConfig,
|
config: accountConfig,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -107,7 +109,7 @@ export function resolveQQBotAccount(
|
|||||||
export function applyQQBotAccountConfig(
|
export function applyQQBotAccountConfig(
|
||||||
cfg: MoltbotConfig,
|
cfg: MoltbotConfig,
|
||||||
accountId: string,
|
accountId: string,
|
||||||
input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string }
|
input: { appId?: string; clientSecret?: string; clientSecretFile?: string; name?: string; imageServerBaseUrl?: string }
|
||||||
): MoltbotConfig {
|
): MoltbotConfig {
|
||||||
const next = { ...cfg };
|
const next = { ...cfg };
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ export function applyQQBotAccountConfig(
|
|||||||
? { clientSecretFile: input.clientSecretFile }
|
? { clientSecretFile: input.clientSecretFile }
|
||||||
: {}),
|
: {}),
|
||||||
...(input.name ? { name: input.name } : {}),
|
...(input.name ? { name: input.name } : {}),
|
||||||
|
...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -144,6 +147,7 @@ export function applyQQBotAccountConfig(
|
|||||||
? { clientSecretFile: input.clientSecretFile }
|
? { clientSecretFile: input.clientSecretFile }
|
||||||
: {}),
|
: {}),
|
||||||
...(input.name ? { name: input.name } : {}),
|
...(input.name ? { name: input.name } : {}),
|
||||||
|
...(input.imageServerBaseUrl ? { imageServerBaseUrl: input.imageServerBaseUrl } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -96,8 +96,15 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|||||||
throw new Error("QQBot not configured (missing appId or clientSecret)");
|
throw new Error("QQBot not configured (missing appId or clientSecret)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试启动图床服务器
|
// 如果配置了公网 URL,启动图床服务器
|
||||||
const imageServerBaseUrl = await ensureImageServer(log);
|
let imageServerBaseUrl: string | null = null;
|
||||||
|
if (account.imageServerBaseUrl) {
|
||||||
|
await ensureImageServer(log);
|
||||||
|
imageServerBaseUrl = account.imageServerBaseUrl;
|
||||||
|
log?.info(`[qqbot:${account.accountId}] Image server enabled with URL: ${imageServerBaseUrl}`);
|
||||||
|
} else {
|
||||||
|
log?.info(`[qqbot:${account.accountId}] Image server disabled (no imageServerBaseUrl configured)`);
|
||||||
|
}
|
||||||
|
|
||||||
let reconnectAttempts = 0;
|
let reconnectAttempts = 0;
|
||||||
let isAborted = false;
|
let isAborted = false;
|
||||||
@@ -232,15 +239,17 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|||||||
const envelopeOptions = pluginRuntime.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
const envelopeOptions = pluginRuntime.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
||||||
|
|
||||||
// 组装消息体,添加系统提示词
|
// 组装消息体,添加系统提示词
|
||||||
const builtinPrompt = `由于平台限制,你的回复中不可以包含任何URL。
|
let builtinPrompt = "由于平台限制,你的回复中不可以包含任何URL。";
|
||||||
|
|
||||||
|
// 只有配置了图床公网地址,才告诉 AI 可以发送图片
|
||||||
|
if (imageServerBaseUrl) {
|
||||||
|
builtinPrompt += `
|
||||||
|
|
||||||
【发送图片】
|
【发送图片】
|
||||||
如果需要发送本地图片文件给用户,请在回复中使用以下格式:
|
你可以发送本地图片文件给用户。只需在回复中直接引用图片的绝对路径即可,系统会自动处理。
|
||||||
MEDIA:/绝对路径/图片文件.png
|
支持 png、jpg、gif、webp 格式。`;
|
||||||
|
}
|
||||||
|
|
||||||
例如:MEDIA:/home/ubuntu/clawd/downloads/image.png
|
|
||||||
|
|
||||||
系统会自动将该路径的图片发送给用户。支持 png、jpg、gif、webp 格式。`;
|
|
||||||
const systemPrompts = [builtinPrompt];
|
const systemPrompts = [builtinPrompt];
|
||||||
if (account.systemPrompt) {
|
if (account.systemPrompt) {
|
||||||
systemPrompts.push(account.systemPrompt);
|
systemPrompts.push(account.systemPrompt);
|
||||||
@@ -486,13 +495,16 @@ MEDIA:/绝对路径/图片文件.png
|
|||||||
textWithoutImages = textWithoutImages.replace(match[0], "").trim();
|
textWithoutImages = textWithoutImages.replace(match[0], "").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理剩余文本中的 URL 点号
|
// 处理剩余文本中的 URL 点号(只有在没有图片的情况下才替换,避免误伤)
|
||||||
const originalText = textWithoutImages;
|
const hasImages = imageUrls.length > 0;
|
||||||
textWithoutImages = textWithoutImages.replace(/([a-zA-Z0-9])\.([a-zA-Z0-9])/g, "$1_$2");
|
let hasReplacement = false;
|
||||||
|
if (!hasImages) {
|
||||||
const hasReplacement = textWithoutImages !== originalText;
|
const originalText = textWithoutImages;
|
||||||
if (hasReplacement && textWithoutImages.trim()) {
|
textWithoutImages = textWithoutImages.replace(/([a-zA-Z0-9])\.([a-zA-Z0-9])/g, "$1_$2");
|
||||||
textWithoutImages += "\n\n(由于平台限制,回复中的部分符号已被替换)";
|
hasReplacement = textWithoutImages !== originalText;
|
||||||
|
if (hasReplacement && textWithoutImages.trim()) {
|
||||||
|
textWithoutImages += "\n\n(由于平台限制,回复中的部分符号已被替换)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -29,12 +29,14 @@ interface QQBotChannelConfig {
|
|||||||
clientSecret?: string;
|
clientSecret?: string;
|
||||||
clientSecretFile?: string;
|
clientSecretFile?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
imageServerBaseUrl?: string;
|
||||||
accounts?: Record<string, {
|
accounts?: Record<string, {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
clientSecret?: string;
|
clientSecret?: string;
|
||||||
clientSecretFile?: string;
|
clientSecretFile?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
imageServerBaseUrl?: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +197,44 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
).trim();
|
).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 询问是否配置图片发送功能
|
||||||
|
let imageServerBaseUrl: string | null = null;
|
||||||
|
const existingImageUrl = (next.channels?.qqbot as QQBotChannelConfig)?.imageServerBaseUrl
|
||||||
|
|| process.env.QQBOT_IMAGE_SERVER_BASE_URL;
|
||||||
|
|
||||||
|
const wantImageSupport = await prompter.confirm({
|
||||||
|
message: "是否启用图片发送功能?(需要服务器有公网 IP)",
|
||||||
|
initialValue: Boolean(existingImageUrl),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wantImageSupport) {
|
||||||
|
imageServerBaseUrl = String(
|
||||||
|
await prompter.text({
|
||||||
|
message: "请输入服务器公网地址(格式: http://公网IP:18765)",
|
||||||
|
placeholder: "例如: http://123.45.67.89:18765",
|
||||||
|
initialValue: existingImageUrl || undefined,
|
||||||
|
validate: (value) => {
|
||||||
|
if (!value?.trim()) return "公网地址不能为空";
|
||||||
|
if (!value.startsWith("http://") && !value.startsWith("https://")) {
|
||||||
|
return "地址必须以 http:// 或 https:// 开头";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
await prompter.note(
|
||||||
|
[
|
||||||
|
"图片发送功能已启用。请确保:",
|
||||||
|
"1. 服务器防火墙已开放 18765 端口",
|
||||||
|
"2. 云服务器安全组已放行 18765 端口(入站)",
|
||||||
|
"",
|
||||||
|
"如果图片发送失败,请检查端口是否可从公网访问。",
|
||||||
|
].join("\n"),
|
||||||
|
"图片功能配置",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 应用配置
|
// 应用配置
|
||||||
if (appId && clientSecret) {
|
if (appId && clientSecret) {
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
@@ -207,6 +247,7 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
appId,
|
appId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
|
...(imageServerBaseUrl ? { imageServerBaseUrl } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -225,6 +266,38 @@ export const qqbotOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
appId,
|
appId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
|
...(imageServerBaseUrl ? { imageServerBaseUrl } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (imageServerBaseUrl) {
|
||||||
|
// 只更新 imageServerBaseUrl
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
next = {
|
||||||
|
...next,
|
||||||
|
channels: {
|
||||||
|
...next.channels,
|
||||||
|
qqbot: {
|
||||||
|
...next.channels?.qqbot,
|
||||||
|
imageServerBaseUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
next = {
|
||||||
|
...next,
|
||||||
|
channels: {
|
||||||
|
...next.channels,
|
||||||
|
qqbot: {
|
||||||
|
...next.channels?.qqbot,
|
||||||
|
accounts: {
|
||||||
|
...(next.channels?.qqbot as QQBotChannelConfig)?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...(next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId],
|
||||||
|
imageServerBaseUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export interface ResolvedQQBotAccount {
|
|||||||
secretSource: "config" | "file" | "env" | "none";
|
secretSource: "config" | "file" | "env" | "none";
|
||||||
/** 系统提示词 */
|
/** 系统提示词 */
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
/** 图床服务器公网地址 */
|
||||||
|
imageServerBaseUrl?: string;
|
||||||
config: QQBotAccountConfig;
|
config: QQBotAccountConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ export interface QQBotAccountConfig {
|
|||||||
allowFrom?: string[];
|
allowFrom?: string[];
|
||||||
/** 系统提示词,会添加在用户消息前面 */
|
/** 系统提示词,会添加在用户消息前面 */
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
/** 图床服务器公网地址,用于发送图片,例如 http://your-ip:18765 */
|
||||||
|
imageServerBaseUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user