feat: 支持 huanke-gemini 图片生成 provider

- 将 huanke-gemini 加入 Gemini 图片生成处理流程
- 禁用 Base64 图片传输,统一使用 URL 格式
- gemini-image 和 huanke-gemini 内部会自行处理图片上传到 S3
- 添加 huanke-gemini 模型调试日志
This commit is contained in:
Your Name 2026-02-02 00:19:25 +08:00
parent 4267ac42bf
commit e631cc45f3
2 changed files with 32 additions and 36 deletions

View File

@ -2553,9 +2553,9 @@ export function AdminSettingsClient() {
</div>
{siteSettings.bannerEnabled && (
<>
<div className="space-y-2">
<div className="space-y-2">
<Label htmlFor="bannerText">横幅文字</Label>
<Input
<Input
id="bannerText"
value={siteSettings.bannerText}
onChange={(e) => setSiteSettings({ ...siteSettings, bannerText: e.target.value })}
@ -2963,11 +2963,11 @@ export function AdminSettingsClient() {
</div>
</label>
</div>
</div>
</div>
<div className="space-y-2">
<div className="space-y-2">
<Label>视频链接 *</Label>
<Input
<Input
value={tutorialVideoForm.videoUrl}
onChange={(e) => setTutorialVideoForm({ ...tutorialVideoForm, videoUrl: e.target.value })}
placeholder="视频URL(上传后自动填充或手动输入)"
@ -3020,7 +3020,7 @@ export function AdminSettingsClient() {
</Button>
</label>
</div>
</div>
</div>
<div className="space-y-2">
<Label>视频描述</Label>
@ -3067,7 +3067,7 @@ export function AdminSettingsClient() {
<Button variant="outline" onClick={() => setShowTutorialVideoDialog(false)}>取消</Button>
<Button onClick={handleSaveTutorialVideo} disabled={!tutorialVideoForm.title || !tutorialVideoForm.videoUrl}>
保存
</Button>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@ -3130,8 +3130,8 @@ export function AdminSettingsClient() {
</div>
)}
</div>
</CardContent>
</Card>
</CardContent>
</Card>
)}
{/* 导航链接列表 */}

View File

@ -105,11 +105,14 @@ function hasAnnotatedImage(messages: ChatMessage[]): boolean {
const ANNOTATION_SYSTEM_PROMPT = "【重要】用户提供的图片上有标注线条(如箭头、圆圈、画笔痕迹或文字说明),这些标注是用户用来指示需要修改的区域或方向。请仔细理解标注的含义,按照标注指示进行编辑,但在生成的新图片中不要保留这些标注线条,输出干净的编辑结果。";
/**
* 自定义消息转换函数,支持将图片 URL 转换为 base64(仅用于 Gemini)
* 自定义消息转换函数
* 注意:gemini-image 和 huanke-gemini 都需要 URL 格式(不是 Base64)
* 因为这些 provider 内部会自行处理图片上传到 S3
*/
async function convertMessagesWithImages(messages: ChatMessage[], providerType: string) {
// 千问视频需要真实的 HTTP URL,不能用 base64
// 只有 Gemini 需要 base64
// 所有 provider 都使用 URL 格式传输图片
// gemini-image 和 huanke-gemini 内部会将 Base64 转换为 S3 URL
// 这里不再进行 Base64 转换,直接返回标准格式
if (providerType !== "gemini") {
return convertToModelMessages(messages);
}
@ -146,26 +149,15 @@ async function convertMessagesWithImages(messages: ChatMessage[], providerType:
text: textContent,
});
} else if (part.type === "file" && part.url) {
// 将图片 URL 转换为 base64
try {
const base64Data = await convertImageUrlToBase64(part.url);
content.push({
type: "image_url",
image_url: {
url: base64Data,
},
});
console.log("[Message Convert] Image converted to base64 and formatted as image_url");
} catch (error) {
console.error("[Message Convert] Failed to convert image, using URL:", error);
// 失败时仍然尝试使用 URL
content.push({
type: "image_url",
image_url: {
url: part.url,
},
});
}
// 直接使用 URL 格式(不再转换为 Base64)
// gemini-image 和 huanke-gemini provider 内部会处理图片上传
content.push({
type: "image_url",
image_url: {
url: part.url,
},
});
console.log("[Message Convert] Image kept as URL format:", part.url.substring(0, 80) + "...");
}
}
@ -744,8 +736,10 @@ export async function POST(request: Request) {
}
// ========== Gemini 图片生成独立处理 ==========
if ((languageModel as any).provider === "gemini-image" && typeof (languageModel as any).doGenerate === "function") {
console.log("[Chat API] Using Gemini image generation");
// 支持 gemini-image 和 huanke-gemini 两种图片生成 provider
const isGeminiImageProvider = ["gemini-image", "huanke-gemini"].includes((languageModel as any).provider);
if (isGeminiImageProvider && typeof (languageModel as any).doGenerate === "function") {
console.log("[Chat API] Using Gemini image generation, provider:", (languageModel as any).provider);
console.log("[Chat API] Image params:", requestBody.imageParams);
// 🆕 获取备用模型配置
@ -767,8 +761,10 @@ export async function POST(request: Request) {
const messagesForGemini = lastUserMessage ? [lastUserMessage] : uiMessages;
console.log("[Chat API] Gemini: Using only last user message (no history context)");
// 转换消息,Gemini 需要 base64 格式的图片
const modelMessages = await convertMessagesWithImages(messagesForGemini, "gemini");
// 转换消息:gemini-image 需要 base64 格式,huanke-gemini 需要 URL 格式
const providerForConvert = (languageModel as any).provider === "huanke-gemini" ? "huanke-gemini" : "gemini";
const modelMessages = await convertMessagesWithImages(messagesForGemini, providerForConvert);
console.log("[Chat API] Message conversion provider:", providerForConvert);
// 调用 doGenerate 获取图片(如果是备用模型,传入 overrideModelId)
const result = await (languageModel as any).doGenerate({