ai-chatbot/图像拓展功能-API调用文档.md
Your Name 7c253e04a0
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: 添加图像拓展功能
- 新增 /api/extend 路由,集成阿里云Dashscope图像拓展API
- 在系统设置'本地AI设置'中添加图像拓展API配置(API密钥、X/Y轴放大倍数)
- 在图片标注组件中添加'图像拓展'按钮(橙色,Expand图标)
- 在聊天对话图片上添加'图像拓展'按钮,支持动态占位卡片
- 更新ImageProcessingLoading组件支持extend类型(橙色主题)
- 处理后的图片上传到S3,保存到数据库(modelId: extend-1.0)
- 同步到我的相册和出图记录
- 添加完整的API调用文档
2025-12-29 10:54:32 +08:00

14 KiB
Raw Blame History

🎨 图像拓展功能 - API调用文档

📋 功能概述

图像拓展(Image Out-painting)功能使用阿里云 Dashscope API,可以将图片智能扩展,自动填充边缘区域,实现图片尺寸的放大。


🔧 API配置

环境变量

# 阿里云 Dashscope API Key(用于图像拓展)
GENERATE_BG_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

说明: 此配置与背景生成功能共用同一个 API Key。


🌐 API端点

1. 提交拓展任务

端点: https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting

方法: POST

请求头:

Content-Type: application/json
Authorization: Bearer {GENERATE_BG_API_KEY}
X-DashScope-Async: enable

说明: X-DashScope-Async: enable 是必需的,用于启用异步模式。


2. 查询任务状态

端点: https://dashscope.aliyuncs.com/api/v1/tasks/{taskId}

方法: GET

请求头:

Authorization: Bearer {GENERATE_BG_API_KEY}

📤 请求参数

请求体结构

{
  "model": "image-out-painting",
  "input": {
    "image_url": "https://example.com/image.jpg"  // 方式1:公网URL
    // 或
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAA..."  // 方式2:Base64编码(推荐)
  },
  "parameters": {
    "x_scale": 1.5,           // X轴放大倍数(1.0-2.0)
    "y_scale": 1.5,           // Y轴放大倍数(1.0-2.0)
    "best_quality": false,    // 是否使用最佳质量(true/false)
    "limit_image_size": true, // 是否限制图片大小
    "add_watermark": false    // 是否添加水印(建议false)
  }
}

参数说明

参数 类型 必填 说明 默认值
model string 是 固定值 "image-out-painting" -
input.image_url string 否* 图片的公网URL(需可访问) -
input.image_base64 string 否* 图片的Base64编码 -
parameters.x_scale number 否 X轴放大倍数(1.0-2.0) 1.5
parameters.y_scale number 否 Y轴放大倍数(1.0-2.0) 1.5
parameters.best_quality boolean 否 是否使用最佳质量 false
parameters.limit_image_size boolean 否 是否限制图片大小 true
parameters.add_watermark boolean 否 是否添加水印 false

注意: input.image_url 和 input.image_base64 二选一,推荐使用 image_base64。


📥 响应格式

提交任务响应

成功响应:

{
  "output": {
    "task_id": "task-abc123def456",
    "task_status": "PENDING"
  }
}

失败响应:

{
  "code": "InvalidParameter",
  "message": "image_base64 is required"
}

查询任务状态响应

处理中:

{
  "output": {
    "task_id": "task-abc123def456",
    "task_status": "RUNNING"
  }
}

成功:

{
  "output": {
    "task_id": "task-abc123def456",
    "task_status": "SUCCEEDED",
    "output_image_url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/..."
  }
}

失败:

{
  "output": {
    "task_id": "task-abc123def456",
    "task_status": "FAILED",
    "message": "任务处理失败",
    "code": "InternalError"
  }
}

🔄 任务状态

状态 说明 处理方式
PENDING 等待处理 继续轮询
RUNNING 处理中 继续轮询
SUCCEEDED 成功 获取结果URL
FAILED 失败 抛出错误

💻 代码实现示例

TypeScript/Node.js 实现

import { NextRequest, NextResponse } from "next/server";
import sharp from "sharp";

/**
 * 图像拓展核心实现
 * @param imageBase64 - Base64编码的图片数据
 * @param apiKey - Dashscope API Key
 * @returns 拓展后的图片URL
 */
async function extendImage(imageBase64: string, apiKey: string): Promise<string> {
  // 1. 提交拓展任务
  const requestBody = {
    model: "image-out-painting",
    input: {
      image_base64: imageBase64,
    },
    parameters: {
      x_scale: 1.5,
      y_scale: 1.5,
      best_quality: false,
      limit_image_size: true,
      add_watermark: false,
    },
  };

  const response = await fetch(
    "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
        "X-DashScope-Async": "enable",
      },
      body: JSON.stringify(requestBody),
    }
  );

  const initialResult = await response.json();

  if (initialResult.code) {
    throw new Error(
      `图像拓展API错误: ${initialResult.message || JSON.stringify(initialResult)}`
    );
  }

  // 2. 获取任务ID
  const taskId = initialResult.output.task_id;
  const taskStatusUrl = `https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`;

  // 3. 等待3秒后开始轮询
  await new Promise((resolve) => setTimeout(resolve, 3000));

  // 4. 轮询获取结果
  let attempts = 0;
  const maxAttempts = 60; // 最多轮询60次
  const pollInterval = 2000; // 每2秒轮询一次

  while (attempts < maxAttempts) {
    const statusResponse = await fetch(taskStatusUrl, {
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
    });

    const result = await statusResponse.json();

    if (result.output?.task_status === "SUCCEEDED") {
      return result.output.output_image_url;
    } else if (result.output?.task_status === "FAILED") {
      throw new Error(
        `图像拓展任务失败: ${result.output.message || result.output.code || "未知错误"}`
      );
    }

    attempts++;
    if (attempts < maxAttempts) {
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
  }

  throw new Error(`图像拓展任务超时(等待${maxAttempts * 2 + 3}秒)`);
}

/**
 * 完整的图像拓展流程(包含图片上传到S3)
 */
export async function POST(request: NextRequest) {
  try {
    // 1. 获取用户会话
    const session = await auth();
    if (!session?.user?.id) {
      return NextResponse.json({ error: "未授权" }, { status: 401 });
    }

    // 2. 获取API配置
    const apiKey = process.env.GENERATE_BG_API_KEY;
    if (!apiKey) {
      return NextResponse.json(
        { error: "图像拓展API配置未设置" },
        { status: 400 }
      );
    }

    // 3. 获取请求体(Base64图片)
    const contentType = request.headers.get("content-type") || "";
    let imageBase64: string;

    if (contentType.includes("application/json")) {
      const body = await request.json();
      imageBase64 = body.image_base64;
    } else if (contentType.includes("multipart/form-data")) {
      const formData = await request.formData();
      const file = formData.get("file") as File;
      const arrayBuffer = await file.arrayBuffer();
      const buffer = Buffer.from(arrayBuffer);
      imageBase64 = buffer.toString("base64");
    } else {
      return NextResponse.json(
        { error: "不支持的Content-Type" },
        { status: 400 }
      );
    }

    if (!imageBase64) {
      return NextResponse.json(
        { error: "缺少图片数据" },
        { status: 400 }
      );
    }

    // 4. 调用图像拓展API
    const extendedImageUrl = await extendImage(imageBase64, apiKey);

    // 5. 下载拓展后的图片
    const imageResponse = await fetch(extendedImageUrl);
    if (!imageResponse.ok) {
      throw new Error(`下载拓展图片失败: ${imageResponse.status}`);
    }

    const arrayBuffer = await imageResponse.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);

    // 6. 上传到S3(可选)
    // const { uploadFile } = await import("@/lib/storage/service");
    // const thumbnailBuffer = await createThumbnail(buffer, "image/jpeg");
    // const [thumbnailResult, originalResult] = await Promise.all([
    //   uploadFile(`extend-thumb-${Date.now()}.jpg`, thumbnailBuffer),
    //   uploadFile(`extend-orig-${Date.now()}.jpg`, buffer),
    // ]);

    // 7. 保存到数据库(可选)
    // await db.insert(generatedImage).values({
    //   userId: session.user.id,
    //   thumbnailUrl: thumbnailResult.url,
    //   originalUrl: originalResult.url,
    //   prompt: "图像拓展处理",
    //   modelId: "extend-1.0",
    // });

    // 8. 返回结果
    return NextResponse.json({
      success: true,
      original_url: extendedImageUrl, // 或 originalResult.url
      thumbnail_url: extendedImageUrl, // 或 thumbnailResult.url
      // base64: buffer.toString("base64"), // 可选:返回Base64
    });
  } catch (error) {
    console.error("[Extend API] 错误:", error);
    return NextResponse.json(
      {
        error: "图像拓展失败",
        detail: error instanceof Error ? error.message : String(error),
      },
      { status: 500 }
    );
  }
}

🔄 使用公网URL方式(备选方案)

如果不想使用Base64,可以先将图片上传到S3/MinIO,然后使用公网URL:

// 1. 上传图片到S3/MinIO获取公网URL
const imageUrl = await uploadToS3(imageBuffer);

// 2. 使用URL调用API
const requestBody = {
  model: "image-out-painting",
  input: {
    image_url: imageUrl, // 使用URL而不是Base64
  },
  parameters: {
    x_scale: 1.5,
    y_scale: 1.5,
    best_quality: false,
    limit_image_size: true,
    add_watermark: false,
  },
};

注意: 公网URL必须可访问,且不能是localhost或内网地址。


⚙️ 配置参数说明

放大倍数

  • x_scale / y_scale: 1.0 - 2.0
    • 1.0: 不放大
    • 1.5: 放大1.5倍(推荐)
    • 2.0: 放大2倍(最大)

质量设置

  • best_quality:
    • true: 最佳质量,处理时间更长
    • false: 标准质量,处理时间较短(推荐)

水印设置

  • add_watermark:
    • true: 添加水印(不推荐)
    • false: 不添加水印(推荐)

⏱️ 性能指标

指标 值 说明
单张处理时间 5-15秒 取决于图片大小和质量设置
最大图片大小 4MB 原始图片大小限制
支持格式 JPG, PNG, WebP 常见图片格式
并发限制 5个/秒 根据账户类型
轮询间隔 2秒 建议值
最大轮询次数 60次 总计约120秒超时

🛡️ 错误处理

常见错误

错误码 说明 解决方案
InvalidParameter 参数错误 检查图片格式和参数值
Arrearage 配额不足 充值或升级账户
Throttling.RateQuota 请求过快 降低请求频率
InternalError 内部错误 稍后重试
submit algo service error API服务问题 稍后重试

重试机制

建议实现3次重试,每次重试间隔递增:

async function extendImageWithRetry(
  imageBase64: string,
  apiKey: string,
  maxRetries = 3
): Promise<string> {
  let lastError: Error;

  for (let retry = 0; retry < maxRetries; retry++) {
    try {
      return await extendImage(imageBase64, apiKey);
    } catch (error) {
      lastError = error as Error;
      console.error(`第${retry + 1}次拓展尝试失败:`, error);

      if (retry < maxRetries - 1) {
        const waitTime = (retry + 1) * 5; // 5秒、10秒
        await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
      }
    }
  }

  throw new Error(`图像拓展失败(已重试${maxRetries}次): ${lastError.message}`);
}

📊 完整流程示例

用户上传图片
    ↓
转换为Base64(或上传到S3获取URL)
    ↓
提交拓展任务到Dashscope API
    ↓
获取task_id
    ↓
等待3秒(API初始化)
    ↓
轮询任务状态(每2秒一次,最多60次)
    ↓
任务完成,获取output_image_url
    ↓
下载拓展后的图片
    ↓
上传到S3(可选)
    ↓
保存到数据库(可选)
    ↓
返回结果给前端

🔍 测试方法

1. 使用curl测试

# 准备Base64图片
IMAGE_BASE64=$(base64 -i test.jpg)

# 提交任务
curl -X POST \
  https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $GENERATE_BG_API_KEY" \
  -H "X-DashScope-Async: enable" \
  -d "{
    \"model\": \"image-out-painting\",
    \"input\": {
      \"image_base64\": \"$IMAGE_BASE64\"
    },
    \"parameters\": {
      \"x_scale\": 1.5,
      \"y_scale\": 1.5,
      \"best_quality\": false,
      \"limit_image_size\": true,
      \"add_watermark\": false
    }
  }"

# 查询任务状态(替换task_id)
curl -X GET \
  https://dashscope.aliyuncs.com/api/v1/tasks/{task_id} \
  -H "Authorization: Bearer $GENERATE_BG_API_KEY"

2. 使用Node.js测试脚本

参考 第三方源码参考/test-image-extend-final.js


📝 注意事项

  1. API Key安全: 不要在客户端代码中暴露API Key,应在服务端调用
  2. 图片大小: 建议先压缩图片到4MB以下再调用API
  3. 异步处理: 必须使用异步模式(X-DashScope-Async: enable)
  4. 轮询超时: 建议设置合理的超时时间(120秒)
  5. 错误处理: 实现重试机制以提高成功率
  6. 配额限制: 注意API调用配额,避免超出限制

🔗 相关资源


✅ 总结

图像拓展功能使用阿里云Dashscope API,支持Base64和URL两种输入方式,采用异步模式处理,需要轮询获取结果。建议使用Base64方式,简化流程,提高可靠性。

关键点:

  • ✅ 使用 GENERATE_BG_API_KEY 环境变量
  • ✅ 必须设置 X-DashScope-Async: enable
  • ✅ 推荐使用Base64输入方式
  • ✅ 实现轮询和重试机制
  • ✅ 处理时间约5-15秒/张