2025-08-19 14:33:59 +08:00
|
|
|
|
import axios from 'axios'
|
|
|
|
|
|
import { MJBasic } from './mjBasic'
|
|
|
|
|
|
import { ImageGenerateMode, MJRobotType, MJSpeed } from '@/define/data/mjData'
|
|
|
|
|
|
import { MJRespoonseType } from '@/define/enum/mjEnum'
|
|
|
|
|
|
import { GetApiDefineDataById } from '@/define/data/apiData'
|
|
|
|
|
|
import { isEmpty } from 'lodash'
|
|
|
|
|
|
import { BookBackTaskStatus } from '@/define/enum/bookEnum'
|
|
|
|
|
|
import { MJ } from '@/define/model/mj'
|
2025-09-04 16:58:42 +08:00
|
|
|
|
import { define } from '@/define/define'
|
2025-09-12 14:52:28 +08:00
|
|
|
|
import { t } from '@/i18n'
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MidJourney API 账户过滤器接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该接口定义了与MidJourney API请求中账户过滤相关的配置选项。
|
|
|
|
|
|
* 用于在API请求中指定任务处理速度、备注信息和实例ID等账户级别的设置。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @interface AccountFilter
|
|
|
|
|
|
* @property {['FAST' | 'RELAX']?} modes - 处理模式数组,可选值为'FAST'(快速)或'RELAX'(普通)
|
|
|
|
|
|
* @property {string?} remark - 可选的备注信息,通常用于标识请求来源
|
|
|
|
|
|
* @property {string?} instanceId - 可选的实例ID,用于特定场景下的实例标识
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* // 创建一个账户过滤器对象
|
|
|
|
|
|
* const filter: AccountFilter = {
|
|
|
|
|
|
* modes: ['FAST'],
|
|
|
|
|
|
* remark: '请求来源标识',
|
|
|
|
|
|
* instanceId: ''
|
|
|
|
|
|
* };
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface AccountFilter {
|
|
|
|
|
|
modes?: ['FAST' | 'RELAX']
|
|
|
|
|
|
remark?: string // 添加问号使其成为可选属性
|
|
|
|
|
|
instanceId?: string // 添加问号使其成为可选属性
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MidJourney API 图像生成请求体接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该接口定义了向MidJourney API提交图像生成请求时所需的请求体结构。
|
|
|
|
|
|
* 包含指定的机器人类型、提示词文本和可选的账户过滤器设置。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @interface MJAPIImagineRequestBody
|
|
|
|
|
|
* @property {'MID_JOURNEY' | 'NIJI_JOURNEY'} botType - 使用的机器人类型,MidJourney或NijiJourney
|
|
|
|
|
|
* @property {string} prompt - 用于生成图像的提示词文本
|
|
|
|
|
|
* @property {AccountFilter} [accountFilter] - 可选的账户过滤设置,用于指定处理速度等参数
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* // 创建一个图像生成请求体
|
|
|
|
|
|
* const requestBody: MJAPIImagineRequestBody = {
|
|
|
|
|
|
* botType: 'MID_JOURNEY',
|
|
|
|
|
|
* prompt: 'a beautiful sunset over mountains, photorealistic style',
|
|
|
|
|
|
* accountFilter: {
|
|
|
|
|
|
* modes: ['FAST']
|
|
|
|
|
|
* }
|
|
|
|
|
|
* };
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface MJAPIImagineRequestBody {
|
|
|
|
|
|
botType: 'MID_JOURNEY' | 'NIJI_JOURNEY'
|
|
|
|
|
|
prompt: string
|
|
|
|
|
|
accountFilter?: AccountFilter
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MidJourney API 图像描述请求体接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该接口定义了向MidJourney API提交图像描述(反推)请求时所需的请求体结构。
|
|
|
|
|
|
* 包含指定的机器人类型、base64编码的图像数据和可选的账户过滤器设置。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @interface MJAPIDescribeRequestBody
|
|
|
|
|
|
* @property {'MID_JOURNEY' | 'NIJI_JOURNEY'} botType - 使用的机器人类型,MidJourney或NijiJourney
|
|
|
|
|
|
* @property {string} base64 - base64编码的图像数据字符串,用于图像描述/反推
|
|
|
|
|
|
* @property {AccountFilter} [accountFilter] - 可选的账户过滤设置,用于指定处理速度等参数
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* // 创建一个图像描述请求体
|
|
|
|
|
|
* const requestBody: MJAPIDescribeRequestBody = {
|
|
|
|
|
|
* botType: 'MID_JOURNEY',
|
|
|
|
|
|
* base64: 'data:image/png;base64,iVBORw0KGgo...',
|
|
|
|
|
|
* accountFilter: {
|
|
|
|
|
|
* modes: ['FAST']
|
|
|
|
|
|
* }
|
|
|
|
|
|
* };
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface MJAPIDescribeRequestBody {
|
|
|
|
|
|
botType: 'MID_JOURNEY' | 'NIJI_JOURNEY'
|
|
|
|
|
|
base64: string
|
|
|
|
|
|
accountFilter?: AccountFilter
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MidJourney API 服务类
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该类负责处理与MidJourney API的所有交互,包括图像生成和图像描述(反推提示词)功能。
|
|
|
|
|
|
* 扩展自MJBasic基类,继承了基本设置和配置管理功能。类提供了对MJ API的完整封装,
|
|
|
|
|
|
* 包括初始化配置、构建请求、发送API请求、处理响应和错误处理等。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 主要功能:
|
|
|
|
|
|
* - 初始化MidJourney API设置和配置
|
|
|
|
|
|
* - 提交图像生成(imagine)请求
|
|
|
|
|
|
* - 提交图像描述(describe/反推)请求
|
|
|
|
|
|
* - 查询任务状态和获取结果
|
|
|
|
|
|
* - 构建符合API要求的请求体
|
|
|
|
|
|
* - 处理API响应和错误
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 小说分镜的AI图像生成
|
|
|
|
|
|
* - 根据已有图像获取描述文本(反推提示词)
|
|
|
|
|
|
* - 监控和获取长时间运行任务的状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* @class MJApiService
|
|
|
|
|
|
* @extends MJBasic
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* // 初始化服务
|
|
|
|
|
|
* const mjApiService = new MJApiService();
|
|
|
|
|
|
*
|
|
|
|
|
|
* // 提交图像生成请求
|
|
|
|
|
|
* const taskId = await mjApiService.SubmitMJImagine(
|
|
|
|
|
|
* "local-task-id",
|
|
|
|
|
|
* "a futuristic cityscape at sunset, hyperrealistic style"
|
|
|
|
|
|
* );
|
|
|
|
|
|
*
|
|
|
|
|
|
* // 查询任务状态
|
|
|
|
|
|
* const taskStatus = await mjApiService.GetMJAPITaskById(taskId, "local-task-id");
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MJApiService extends MJBasic {
|
|
|
|
|
|
bootType: 'NIJI_JOURNEY' | 'MID_JOURNEY'
|
|
|
|
|
|
|
|
|
|
|
|
imagineUrl!: string
|
|
|
|
|
|
fetchTaskUrl!: string
|
|
|
|
|
|
describeUrl!: string
|
2025-09-12 14:52:28 +08:00
|
|
|
|
videoUrl!: string
|
2025-09-04 16:58:42 +08:00
|
|
|
|
token!: string
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
super()
|
|
|
|
|
|
this.bootType = 'MID_JOURNEY'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
//#region InitMJSetting
|
2025-08-19 14:33:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化MJ设置
|
|
|
|
|
|
*/
|
2025-09-12 14:52:28 +08:00
|
|
|
|
async InitMJSetting(outputMode?: ImageGenerateMode): Promise<void> {
|
2025-08-19 14:33:59 +08:00
|
|
|
|
await this.GetMJGeneralSetting()
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
// 获取当前机器人类型
|
2025-08-19 14:33:59 +08:00
|
|
|
|
this.bootType =
|
|
|
|
|
|
this.mjGeneralSetting?.robot == MJRobotType.NIJI ? 'NIJI_JOURNEY' : 'MID_JOURNEY'
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-09-12 14:52:28 +08:00
|
|
|
|
if (outputMode) {
|
|
|
|
|
|
this.mjGeneralSetting!.outputMode = outputMode
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
// 再 MJ API 模式下 获取对应的数据
|
2025-08-19 14:33:59 +08:00
|
|
|
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) {
|
2025-09-04 16:58:42 +08:00
|
|
|
|
await this.GetApiSetting()
|
|
|
|
|
|
if (
|
|
|
|
|
|
!this.mjApiSetting ||
|
|
|
|
|
|
isEmpty(this.mjApiSetting.apiUrl) ||
|
|
|
|
|
|
isEmpty(this.mjApiSetting.apiKey)
|
|
|
|
|
|
) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("没有找到对应的API的配置,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置')
|
|
|
|
|
|
}))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
let apiProvider = GetApiDefineDataById(this.mjApiSetting.apiUrl as string)
|
|
|
|
|
|
if (apiProvider.mj_url == null) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("当前API不支持MJ出图,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置')
|
|
|
|
|
|
}))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
this.imagineUrl = apiProvider.mj_url.imagine
|
|
|
|
|
|
this.describeUrl = apiProvider.mj_url.describe
|
2025-09-12 14:52:28 +08:00
|
|
|
|
this.videoUrl = apiProvider.mj_url.video ?? ''
|
2025-08-19 14:33:59 +08:00
|
|
|
|
this.fetchTaskUrl = apiProvider.mj_url.once_get_task
|
2025-09-04 16:58:42 +08:00
|
|
|
|
this.token = this.mjApiSetting.apiKey
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_PACKAGE) {
|
|
|
|
|
|
await this.GetMJPackageSetting()
|
|
|
|
|
|
if (
|
|
|
|
|
|
!this.mjPackageSetting ||
|
|
|
|
|
|
isEmpty(this.mjPackageSetting.selectPackage) ||
|
|
|
|
|
|
isEmpty(this.mjPackageSetting.packageToken)
|
|
|
|
|
|
) {
|
|
|
|
|
|
throw new Error(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("没有找到对应的生图包的配置或配置有误,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
|
|
|
|
|
})
|
2025-09-04 16:58:42 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mjProvider = GetApiDefineDataById(this.mjPackageSetting.selectPackage)
|
|
|
|
|
|
if (!mjProvider.isPackage) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("当前选择的包不支持,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
|
|
|
|
|
}))
|
2025-09-04 16:58:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mjProvider.mj_url == null) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("当前选择的包不支持,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
|
|
|
|
|
}))
|
2025-09-04 16:58:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
this.imagineUrl = mjProvider.mj_url.imagine
|
|
|
|
|
|
this.describeUrl = mjProvider.mj_url.describe
|
2025-09-12 14:52:28 +08:00
|
|
|
|
this.videoUrl = mjProvider.mj_url.video ?? ''
|
2025-09-04 16:58:42 +08:00
|
|
|
|
this.fetchTaskUrl = mjProvider.mj_url.once_get_task
|
|
|
|
|
|
this.token = this.mjPackageSetting.packageToken
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.LOCAL_MJ) {
|
|
|
|
|
|
await this.GetMjLocalSetting()
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.mjLocalSetting == null ||
|
|
|
|
|
|
isEmpty(this.mjLocalSetting.requestUrl) ||
|
|
|
|
|
|
isEmpty(this.mjLocalSetting.token)
|
|
|
|
|
|
) {
|
|
|
|
|
|
throw new Error(
|
2025-09-12 14:52:28 +08:00
|
|
|
|
t("本地代理模式的设置不完善或配置错误,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置 -> 本地代理模式')
|
|
|
|
|
|
})
|
2025-09-04 16:58:42 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
this.mjLocalSetting.requestUrl.endsWith('/')
|
|
|
|
|
|
? this.mjLocalSetting.requestUrl.slice(0, -1)
|
|
|
|
|
|
: this.mjLocalSetting.requestUrl
|
|
|
|
|
|
|
|
|
|
|
|
this.imagineUrl = this.mjLocalSetting.requestUrl + '/mj/submit/imagine'
|
|
|
|
|
|
this.describeUrl = this.mjLocalSetting.requestUrl + '/mj/submit/describe'
|
2025-09-12 14:52:28 +08:00
|
|
|
|
this.videoUrl = this.mjLocalSetting.requestUrl + '/mj/submit/video'
|
2025-09-04 16:58:42 +08:00
|
|
|
|
this.fetchTaskUrl = this.mjLocalSetting.requestUrl + '/mj/task/${id}/fetch'
|
|
|
|
|
|
this.token = this.mjLocalSetting.token
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.REMOTE_MJ) {
|
|
|
|
|
|
await this.GetMjRemoteSetting()
|
|
|
|
|
|
|
|
|
|
|
|
this.imagineUrl = define.remotemj_api + 'mj/submit/imagine'
|
|
|
|
|
|
this.describeUrl = define.remotemj_api + 'mj/submit/describe'
|
2025-09-12 14:52:28 +08:00
|
|
|
|
this.videoUrl = ""
|
2025-09-04 16:58:42 +08:00
|
|
|
|
this.fetchTaskUrl = define.remotemj_api + 'mj/task/${id}/fetch'
|
|
|
|
|
|
this.token = define.remote_token
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} else {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("当前的MJ出图模式不支持,请检查 ‘{data}’ 配置!", {
|
|
|
|
|
|
data: t('设置 -> MJ设置')
|
|
|
|
|
|
}))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
//#endregion
|
|
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
//#region 获取对应的任务,通过ID
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通过ID获取MidJourney API任务的状态和结果
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法向MidJourney API发送请求,获取指定任务ID的状态信息,包括进度、图像URL和错误信息等。
|
|
|
|
|
|
* 在获取成功后,会将结果格式化为标准响应对象。如果任务失败,则会相应地更新内部任务状态记录。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} taskId - MidJourney API的任务ID,用于查询API任务状态
|
|
|
|
|
|
* @param {string} backTaskId - 内部系统的任务ID,用于更新本地任务状态记录
|
|
|
|
|
|
* @returns {Promise<MJ.MJResponseToFront>} 标准化的任务状态响应对象,包含进度、状态、图像URL等信息
|
|
|
|
|
|
* @throws {Error} 如果API请求失败或返回不可解析的数据
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const taskStatus = await mjApiService.GetMJAPITaskById("task-123", "local-456");
|
|
|
|
|
|
* if (taskStatus.code === 1 && taskStatus.progress === 100) {
|
|
|
|
|
|
* console.log("任务完成,图像URL:", taskStatus.imageShow);
|
|
|
|
|
|
* } else {
|
|
|
|
|
|
* console.log("任务进度:", taskStatus.progress, "%");
|
|
|
|
|
|
* }
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error("获取任务状态失败:", error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
2025-09-04 16:58:42 +08:00
|
|
|
|
async GetMJAPITaskById(taskId: string, backTaskId: string): Promise<MJ.MJResponseToFront> {
|
2025-08-19 14:33:59 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await this.InitMJSetting()
|
|
|
|
|
|
let APIDescribeUrl = this.fetchTaskUrl.replace('${id}', taskId)
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接headers
|
|
|
|
|
|
let headers = {
|
2025-09-04 16:58:42 +08:00
|
|
|
|
Authorization: this.token
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
2025-09-21 13:05:02 +08:00
|
|
|
|
|
|
|
|
|
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.LOCAL_MJ) {
|
|
|
|
|
|
headers['mj-api-secret'] = this.token
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.REMOTE_MJ) {
|
|
|
|
|
|
headers['mj-api-secret'] = this.token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 开始请求
|
|
|
|
|
|
let res = await axios.get(APIDescribeUrl, {
|
|
|
|
|
|
headers: headers
|
|
|
|
|
|
})
|
|
|
|
|
|
let resData = res.data
|
|
|
|
|
|
|
|
|
|
|
|
let progress =
|
|
|
|
|
|
resData.progress && resData.progress.length > 0
|
|
|
|
|
|
? parseInt(resData.progress.slice(0, -1))
|
|
|
|
|
|
: 0
|
|
|
|
|
|
|
|
|
|
|
|
let status = resData.status.toLowerCase()
|
|
|
|
|
|
let code = status == 'failure' || status == 'cancel' ? 0 : 1
|
|
|
|
|
|
// 失败
|
|
|
|
|
|
if (code == 0) {
|
|
|
|
|
|
if (!isEmpty(backTaskId)) {
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: backTaskId,
|
|
|
|
|
|
status: BookBackTaskStatus.FAIL,
|
|
|
|
|
|
errorMessage: resData.message
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let resObj = {
|
|
|
|
|
|
type: MJRespoonseType.UPDATED,
|
|
|
|
|
|
progress: isNaN(progress) ? 0 : progress,
|
|
|
|
|
|
category: this.mjGeneralSetting?.outputMode,
|
|
|
|
|
|
imageClick: resData.imageUrl,
|
|
|
|
|
|
imageShow: resData.imageUrl,
|
|
|
|
|
|
imagePath: resData.imageUrl,
|
2025-09-04 16:58:42 +08:00
|
|
|
|
imageUrls: resData.imageUrls
|
|
|
|
|
|
? resData.imageUrls
|
2025-09-12 14:52:28 +08:00
|
|
|
|
.filter((item) => item.url != null && !isEmpty(item.url))
|
|
|
|
|
|
.map((item) => item.url)
|
2025-09-04 16:58:42 +08:00
|
|
|
|
: [],
|
2025-08-19 14:33:59 +08:00
|
|
|
|
messageId: taskId,
|
|
|
|
|
|
status: status,
|
|
|
|
|
|
code: code,
|
|
|
|
|
|
prompt: resData.prompt == '' ? resData.promptEn : resData.prompt,
|
|
|
|
|
|
message: resData.failReason,
|
|
|
|
|
|
mjApiUrl: this.fetchTaskUrl
|
|
|
|
|
|
} as MJ.MJResponseToFront
|
|
|
|
|
|
return resObj
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
|
|
|
|
//#region MJ反推相关操作
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 提交MidJourney图像描述(反推)任务
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法根据当前设置的输出模式,将图像发送到MidJourney进行描述分析(反推提示词)。
|
|
|
|
|
|
* 目前仅支持API模式,其他模式将抛出错误。在提交请求前会先初始化MJ设置。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {MJ.APIDescribeParams} param - 包含任务ID和base64编码图像的参数对象
|
|
|
|
|
|
* @returns {Promise<string>} 成功时返回API任务结果ID,队列已满时返回"23"
|
|
|
|
|
|
* @throws {Error} 如果当前输出模式不支持或API调用失败时抛出错误
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const params = {
|
|
|
|
|
|
* taskId: "task-123",
|
|
|
|
|
|
* image: "data:image/png;base64,iVBORw0KGgo..."
|
|
|
|
|
|
* };
|
|
|
|
|
|
* const resultId = await mjApiService.SubmitMJDescribe(params);
|
|
|
|
|
|
* console.log("提交成功,任务ID:", resultId);
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error("提交反推任务失败:", error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
|
|
|
|
|
async SubmitMJDescribe(param: MJ.APIDescribeParams): Promise<string> {
|
|
|
|
|
|
await this.InitMJSetting()
|
|
|
|
|
|
let res: string
|
|
|
|
|
|
switch (this.mjGeneralSetting?.outputMode) {
|
|
|
|
|
|
case ImageGenerateMode.MJ_API:
|
|
|
|
|
|
res = await this.SubmitMJDescribeAPI(param)
|
|
|
|
|
|
break
|
|
|
|
|
|
default:
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('MJ反推的类型不支持,反推只支持API和代理模式'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成MidJourney描述请求的主体和配置
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法根据提供的base64图像和当前MJ设置,生成用于调用MidJourney描述API的请求主体和HTTP配置。
|
|
|
|
|
|
* 配置会根据当前输出模式自动调整,并包含必要的认证信息和内容类型。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} imageBase64 - base64编码的图像字符串,用于图像描述/反推
|
|
|
|
|
|
* @returns {{body: MJAPIDescribeRequestBody, config: Object}} 返回包含请求体和配置的对象
|
|
|
|
|
|
* @throws {Error} 如果当前MJ输出模式不支持,将抛出错误
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* const imageBase64 = "data:image/png;base64,iVBORw0KGgo...";
|
|
|
|
|
|
* const { body, config } = mjApiService.GenerateDescribeRequestBody(imageBase64);
|
|
|
|
|
|
* const response = await axios.post(describeUrl, body, config);
|
|
|
|
|
|
*/
|
|
|
|
|
|
GenerateDescribeRequestBody(imageBase64: string): {
|
|
|
|
|
|
body: MJAPIDescribeRequestBody
|
|
|
|
|
|
config: any
|
|
|
|
|
|
} {
|
|
|
|
|
|
// 提交API的反推
|
|
|
|
|
|
let data = {
|
|
|
|
|
|
botType: this.bootType,
|
|
|
|
|
|
base64: imageBase64,
|
|
|
|
|
|
accountFilter: {
|
|
|
|
|
|
modes: [this.mjApiSetting?.apiSpeed == MJSpeed.FAST ? 'FAST' : 'RELAX'],
|
|
|
|
|
|
remark: global.machineId,
|
|
|
|
|
|
instanceId: ''
|
|
|
|
|
|
} as AccountFilter
|
|
|
|
|
|
}
|
|
|
|
|
|
let config = {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) {
|
|
|
|
|
|
delete data.accountFilter.remark
|
|
|
|
|
|
delete data.accountFilter.instanceId
|
2025-09-04 16:58:42 +08:00
|
|
|
|
config.headers['Authorization'] = this.token
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('MJ出图的类型不支持')
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
body: data,
|
|
|
|
|
|
config: config
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通过API提交MidJourney图像描述(反推)请求
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法发送图像到MidJourney API进行描述(反推)分析,将base64编码的图像数据发送到API,
|
|
|
|
|
|
* 并处理返回结果。根据API响应状态码,会相应地更新任务状态记录并返回结果。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 支持以下特殊响应处理:
|
|
|
|
|
|
* - 队列已满(code=23): 更新任务状态为RECONNECT,返回"23"
|
|
|
|
|
|
* - 请求失败: 更新任务状态为FAIL,抛出错误
|
|
|
|
|
|
* - 请求成功: 更新任务状态为RUNNING,返回结果ID
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {MJ.APIDescribeParams} param - 包含任务ID和base64编码图像数据的参数对象
|
|
|
|
|
|
* @returns {Promise<string>} 成功时返回API任务结果ID,队列已满时返回"23"
|
|
|
|
|
|
* @throws {Error} 如果API返回失败状态码或错误描述
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const params = {
|
|
|
|
|
|
* taskId: "task-123",
|
|
|
|
|
|
* image: "data:image/png;base64,iVBORw0KGgo..."
|
|
|
|
|
|
* };
|
|
|
|
|
|
* const taskResultId = await mjApiService.SubmitMJDescribeAPI(params);
|
|
|
|
|
|
* if (taskResultId === "23") {
|
|
|
|
|
|
* // 队列已满,需要重试
|
|
|
|
|
|
* } else {
|
|
|
|
|
|
* // 任务提交成功,可以用taskResultId查询结果
|
|
|
|
|
|
* }
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error("图像描述请求失败:", error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
|
|
|
|
|
async SubmitMJDescribeAPI(param: MJ.APIDescribeParams): Promise<string> {
|
|
|
|
|
|
// 获取body和config
|
|
|
|
|
|
let { body, config } = this.GenerateDescribeRequestBody(param.image)
|
|
|
|
|
|
// 开始请求
|
|
|
|
|
|
let res = await axios.post(this.describeUrl, body, config)
|
|
|
|
|
|
|
|
|
|
|
|
// 某些API的返回的code为23,表示队列已满,需要重新请求
|
|
|
|
|
|
if (res.data.code == 23) {
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: param.taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.RECONNECT
|
|
|
|
|
|
})
|
|
|
|
|
|
return '23'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (res.data.code != 1 && res.data.code != 22) {
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: param.taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.FAIL,
|
|
|
|
|
|
errorMessage: res.data.description
|
|
|
|
|
|
})
|
|
|
|
|
|
throw new Error(res.data.description)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: param.taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.RUNNING
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return res.data.result as string
|
|
|
|
|
|
}
|
|
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
|
|
|
|
//#region 提交MJ生图任务
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 提交MidJourney图像生成任务
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法根据当前设置的输出模式,发送提示词到MidJourney进行图像生成。
|
|
|
|
|
|
* 目前仅支持API模式,其他模式将抛出错误。在提交请求前会先初始化MJ设置。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} taskId - 任务ID,用于追踪和更新任务状态
|
|
|
|
|
|
* @param {string} prompt - 用于生成图像的提示词文本
|
|
|
|
|
|
* @returns {Promise<string>} 成功时返回API任务结果ID,队列已满时返回"23"
|
|
|
|
|
|
* @throws {Error} 如果当前输出模式不支持或API调用失败时抛出错误
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const resultId = await mjApiService.SubmitMJImagine("task-123", "a beautiful sunset in watercolor style");
|
|
|
|
|
|
* console.log("提交成功,任务ID:", resultId);
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error("提交生图任务失败:", error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
|
|
|
|
|
async SubmitMJImagine(taskId: string, prompt: string): Promise<string> {
|
|
|
|
|
|
await this.InitMJSetting()
|
|
|
|
|
|
let res: string
|
|
|
|
|
|
switch (this.mjGeneralSetting?.outputMode) {
|
|
|
|
|
|
case ImageGenerateMode.MJ_API:
|
2025-09-04 16:58:42 +08:00
|
|
|
|
case ImageGenerateMode.MJ_PACKAGE:
|
|
|
|
|
|
case ImageGenerateMode.REMOTE_MJ:
|
|
|
|
|
|
case ImageGenerateMode.LOCAL_MJ:
|
2025-08-19 14:33:59 +08:00
|
|
|
|
res = await this.SubmitMJImagineAPI(taskId, prompt)
|
|
|
|
|
|
break
|
|
|
|
|
|
default:
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('MJ出图的类型不支持'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成MidJourney API的imagine请求体和配置
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法根据当前MJ设置生成用于调用imagine API的请求主体和HTTP配置。
|
|
|
|
|
|
* 配置包含必要的认证信息和内容类型,根据输出模式自动调整请求参数。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} prompt - 用于生成图像的提示词文本
|
|
|
|
|
|
* @returns {{body: MJAPIImagineRequestBody, config: Object}} 返回包含请求体和配置的对象
|
|
|
|
|
|
* @throws {Error} 如果当前MJ输出模式不支持,将抛出错误
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* const { body, config } = mjApiService.GenerateImagineRequestBody("a beautiful sunset");
|
|
|
|
|
|
* const response = await axios.post(imagineUrl, body, config);
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note 当前实现有问题,缺少prompt参数,需要修改方法签名为GenerateImagineRequestBody(prompt: string)
|
|
|
|
|
|
*/
|
|
|
|
|
|
GenerateImagineRequestBody(prompt: string): {
|
|
|
|
|
|
body: MJAPIImagineRequestBody
|
|
|
|
|
|
config: any
|
|
|
|
|
|
} {
|
|
|
|
|
|
// 提交API的出图任务
|
|
|
|
|
|
let data = {
|
|
|
|
|
|
botType: this.bootType,
|
|
|
|
|
|
prompt: prompt,
|
|
|
|
|
|
accountFilter: {
|
|
|
|
|
|
modes: [this.mjApiSetting?.apiSpeed == MJSpeed.FAST ? 'FAST' : 'RELAX'],
|
|
|
|
|
|
remark: global.machineId ?? '',
|
|
|
|
|
|
instanceId: ''
|
|
|
|
|
|
} as AccountFilter
|
|
|
|
|
|
}
|
|
|
|
|
|
let config = {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
let useTransfer = false
|
|
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) {
|
|
|
|
|
|
delete data.accountFilter.remark
|
|
|
|
|
|
delete data.accountFilter.instanceId
|
2025-09-04 16:58:42 +08:00
|
|
|
|
config.headers['Authorization'] = this.token
|
|
|
|
|
|
useTransfer = false
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_PACKAGE) {
|
|
|
|
|
|
delete data.accountFilter.remark
|
|
|
|
|
|
delete data.accountFilter.instanceId
|
|
|
|
|
|
delete data.accountFilter.modes
|
|
|
|
|
|
config.headers['Authorization'] = this.token
|
|
|
|
|
|
useTransfer = false
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.LOCAL_MJ) {
|
|
|
|
|
|
delete data.accountFilter.remark
|
|
|
|
|
|
delete data.accountFilter.modes
|
|
|
|
|
|
delete data.accountFilter.instanceId
|
|
|
|
|
|
config.headers['mj-api-secret'] = this.token
|
|
|
|
|
|
useTransfer = false
|
|
|
|
|
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.REMOTE_MJ) {
|
|
|
|
|
|
config.headers['mj-api-secret'] = this.token
|
|
|
|
|
|
delete data.accountFilter.modes
|
|
|
|
|
|
delete data.accountFilter.instanceId
|
|
|
|
|
|
useTransfer = this.mjRemoteSetting?.isForward ?? false
|
2025-08-19 14:33:59 +08:00
|
|
|
|
} else {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('不支持的MJ出图类型'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
2025-09-04 16:58:42 +08:00
|
|
|
|
console.log('useTransfer', useTransfer)
|
2025-08-19 14:33:59 +08:00
|
|
|
|
return {
|
|
|
|
|
|
body: data,
|
|
|
|
|
|
config: config
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通过API提交MidJourney图像生成任务
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法向MidJourney API提交图像生成请求,并处理返回结果。在提交前会验证提示词是否包含非法链接,
|
|
|
|
|
|
* 然后构造API请求体并发送。根据API响应,会相应地更新任务状态并返回结果。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 支持以下特殊响应处理:
|
|
|
|
|
|
* - 队列已满(code=23): 更新任务状态为RECONNECT,返回"23"
|
|
|
|
|
|
* - 请求失败: 更新任务状态为FAIL,抛出错误
|
|
|
|
|
|
* - 请求成功: 更新任务状态为RUNNING,返回结果ID
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} taskId - 任务ID,用于更新任务状态记录
|
|
|
|
|
|
* @param {string} prompt - 发送给MidJourney的图像生成提示词
|
|
|
|
|
|
* @returns {Promise<string>} 成功时返回API任务结果ID,队列已满时返回"23"
|
|
|
|
|
|
* @throws {Error} 如果提示词中包含非法链接、API返回错误或返回数据为空
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const taskResultId = await mjApiService.SubmitMJImagineAPI("task-123", "a beautiful sunset");
|
|
|
|
|
|
* if (taskResultId === "23") {
|
|
|
|
|
|
* // 队列已满,需要重试
|
|
|
|
|
|
* } else {
|
|
|
|
|
|
* // 任务提交成功,可以用taskResultId查询结果
|
|
|
|
|
|
* }
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error("提交任务失败:", error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
|
|
|
|
|
async SubmitMJImagineAPI(taskId: string, prompt: string): Promise<string> {
|
|
|
|
|
|
// 这边校验是不是在提示词包含不正确的链接
|
|
|
|
|
|
if (prompt.includes('feishu.cn')) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t("提示词里面出现了 feishu.cn 飞书的链接,请检查并复制正确的链接"))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let { body, config } = this.GenerateImagineRequestBody(prompt)
|
|
|
|
|
|
|
|
|
|
|
|
// 开始请求
|
|
|
|
|
|
let res = await axios.post(this.imagineUrl, body, config)
|
|
|
|
|
|
let resData = res.data
|
|
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_PACKAGE) {
|
|
|
|
|
|
if (resData.code == -1 || resData.success == false) {
|
|
|
|
|
|
throw new Error(resData.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
if (resData == null) {
|
2025-09-12 14:52:28 +08:00
|
|
|
|
throw new Error(t('返回的数据为空'))
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 某些API的返回的code为23,表示队列已满,需要重新请求
|
|
|
|
|
|
if (resData.code == 23) {
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.RECONNECT
|
|
|
|
|
|
})
|
|
|
|
|
|
return '23'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (resData.code != 1 && resData.code != 22) {
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.FAIL,
|
|
|
|
|
|
errorMessage: resData.description
|
|
|
|
|
|
})
|
|
|
|
|
|
throw new Error(resData.description)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.taskListService.UpdateTaskStatus({
|
|
|
|
|
|
id: taskId,
|
|
|
|
|
|
status: BookBackTaskStatus.RUNNING
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return resData.result as string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//#endregion
|
|
|
|
|
|
}
|