feat: 移除图片生成记录中的尺寸字段,调整归档逻辑以支持异步处理

This commit is contained in:
2026-04-26 01:37:01 +08:00
parent 4c58f409e9
commit 818cf317ab
9 changed files with 115 additions and 85 deletions
+14 -6
View File
@@ -12,6 +12,7 @@
- 所有返回给前端的 `msg` 必须使用本地业务表达,不提及“上游”“NewAPI”“Lsky”“供应商”“token”“key”等内部来源;内部异常统一返回“服务器内部错误”。 - 所有返回给前端的 `msg` 必须使用本地业务表达,不提及“上游”“NewAPI”“Lsky”“供应商”“token”“key”等内部来源;内部异常统一返回“服务器内部错误”。
- 注释和日志要解释意图、边界和排查信息,不输出密码、cookie、完整 key、token、图片二进制或完整 prompt。 - 注释和日志要解释意图、边界和排查信息,不输出密码、cookie、完整 key、token、图片二进制或完整 prompt。
- 已存在的说明性注释不要随意删除,尤其是 `server/utils/openai.ts` 里的模型调用、视觉输入、流式解析等上下文注释。 - 已存在的说明性注释不要随意删除,尤其是 `server/utils/openai.ts` 里的模型调用、视觉输入、流式解析等上下文注释。
- 不要写任何可能阻塞整个进程的代码。
## API 路由规范 ## API 路由规范
@@ -59,17 +60,21 @@
## 生图、图床与历史记录 ## 生图、图床与历史记录
- `POST /api/images/generate` 当前同步等待生图完成后返回,不向前端透传流式进度。 - `POST /api/images/generate` 当前同步等待生图完成后立即返回,不向前端透传流式进度,也不等待图床归档完成
- 生图成功后返回: - 生图成功后返回:
- `imageUrl`:生成图片访问地址,前端当前优先展示。 - `imageUrl`:生成图片访问地址,前端当前优先展示。
- `hostedImageUrl`:Lsky 图床归档后的 URL,归档失败时为 `null`
- `revisedPrompt`:流式 chat completions 当前没有等价字段,通常为空。 - `revisedPrompt`:流式 chat completions 当前没有等价字段,通常为空。
- 生图主链路先把记录落库为 `SUCCEEDED`,并清空 `hostedImageUrl``hostedResponse``imageMimeType``errorMessage`;之后再由响应后的后台任务异步上传 Lsky。
- 历史记录中的图床归档状态约定:
- `status === "SUCCEEDED"``hostedImageUrl === null``errorMessage === null`:表示图片已生成、仍在归档中。
- `status === "SUCCEEDED"``hostedImageUrl === null``errorMessage !== null`:表示归档失败。
- `hostedImageUrl !== null`:表示归档成功。
- Lsky 归档逻辑集中在 `server/utils/lsky.ts` - Lsky 归档逻辑集中在 `server/utils/lsky.ts`
- 配置从 `LSKY_BASE_URL``LSKY_TOKEN``LSKY_STORAGE_ID` 读取。 - 配置从 `LSKY_BASE_URL``LSKY_TOKEN``LSKY_STORAGE_ID` 读取。
- 上传时先下载上游图片,再用 `multipart/form-data` 调 Lsky `/upload` - 上传时先下载上游图片,再用 `multipart/form-data` 调 Lsky `/upload`
- 文件名格式为 `userId_username_timestamp_recordId.ext` - 文件名格式为 `userId_username_timestamp_recordId.ext`
- tags 固定包含 `AIArtStudio``user:{userId}``record:{recordId}``model:gpt-image-2` - tags 固定包含 `AIArtStudio``user:{userId}``record:{recordId}``model:gpt-image-2`
- 图床上传失败不影响本次生图成功:接口仍返回 `imageUrl`,数据库记录归档失败提示;前端可见文案只能说“图片归档失败”等本地描述。 - 图床上传失败不影响本次生图成功:接口仍立即返回 `imageUrl`,数据库记录归档失败提示;前端可见文案只能说“图片归档失败”等本地描述。
- 数据库不保存图片 base64;不要恢复 `image_base64` 或类似大文本存图方案。 - 数据库不保存图片 base64;不要恢复 `image_base64` 或类似大文本存图方案。
- 历史列表和详情都不向前端返回完整上游响应、图床响应或内部错误详情;这些内容可以入库供服务端排查,但 API 响应应固定隐藏或置空。 - 历史列表和详情都不向前端返回完整上游响应、图床响应或内部错误详情;这些内容可以入库供服务端排查,但 API 响应应固定隐藏或置空。
@@ -82,10 +87,13 @@
- 登录或 `/api/auth/me` 成功后会 upsert `User` 快照,主键使用 NewAPI 用户 id。 - 登录或 `/api/auth/me` 成功后会 upsert `User` 快照,主键使用 NewAPI 用户 id。
- 生图记录写入 `ImageGeneration` - 生图记录写入 `ImageGeneration`
- 开始时写 `RUNNING` - 开始时写 `RUNNING`
- 成功时写 `SUCCEEDED`、上游 URL、图床 URL、MIME、完整上游响应、完整图床响应、耗时。 - 生成成功时`SUCCEEDED`、上游 URL、完整上游响应、耗时。
- 后台归档成功后再补写图床 URL、MIME、完整图床响应。
- 后台归档失败时只补写本地错误文案,不把记录改回失败。
- 失败时写 `FAILED`、错误信息、耗时。 - 失败时写 `FAILED`、错误信息、耗时。
- 删除历史采用软删除 `deletedAt` - 删除历史采用软删除 `deletedAt`
- `GenerationStats` 使用固定 id `global` 记录全局统计。 - `GenerationStats` 使用固定 id `global` 记录全局统计。
- `runningRequests` 只表示生图进行中,不包含图床归档阶段。
- 统计更新失败只记录日志,不应阻断用户拿到已经生成的图片。 - 统计更新失败只记录日志,不应阻断用户拿到已经生成的图片。
- 需要变更数据库结构时,同时维护 Prisma migration 和 `create_tables.sql` - 需要变更数据库结构时,同时维护 Prisma migration 和 `create_tables.sql`
@@ -100,8 +108,8 @@
- `create_running_record` - `create_running_record`
- `get_api_key` - `get_api_key`
- `call_image_stream_api` - `call_image_stream_api`
- `upload_lsky`
- `finish_success_record` - `finish_success_record`
- 图床归档改为响应后的后台日志阶段,继续打印 `recordId`、归档是否成功、失败摘要等信息,但不影响主请求返回。
- 不记录密码、cookie、完整 API key、Lsky token、完整 prompt、图片二进制、base64。 - 不记录密码、cookie、完整 API key、Lsky token、完整 prompt、图片二进制、base64。
- 全局请求日志中间件只记录 method、path、status、耗时,不记录请求体和响应体。 - 全局请求日志中间件只记录 method、path、status、耗时,不记录请求体和响应体。
@@ -131,7 +139,7 @@
- 用户服务在 `app/services/user_service.ts`,图片服务在 `app/services/image_service.ts` - 用户服务在 `app/services/user_service.ts`,图片服务在 `app/services/image_service.ts`
- 登录成功后可以调用 `UserReady()` 准备环境;`/api/auth/me` 恢复登录状态时不自动调用 ready。 - 登录成功后可以调用 `UserReady()` 准备环境;`/api/auth/me` 恢复登录状态时不自动调用 ready。
- 当前首页生图组件为 `app/components/ImageGenerateCom.vue`,优先展示响应中的 `imageUrl` - 当前首页生图组件为 `app/components/ImageGenerateCom.vue`,优先展示响应中的 `imageUrl`
- `hostedImageUrl` 作为图床归档地址返回,后续需要切换展示来源时再调整前端策略。 - `hostedImageUrl` 不在生成接口中返回,只在历史记录中作为归档结果字段保留;后续需要切换展示来源时再调整前端策略。
## 代码风格 ## 代码风格
-1
View File
@@ -18,7 +18,6 @@ CREATE TABLE IF NOT EXISTS `image_generations` (
`prompt` TEXT NOT NULL COMMENT '用户输入的生图提示词', `prompt` TEXT NOT NULL COMMENT '用户输入的生图提示词',
`status` ENUM('QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED') NOT NULL DEFAULT 'RUNNING' COMMENT '生图状态', `status` ENUM('QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED') NOT NULL DEFAULT 'RUNNING' COMMENT '生图状态',
`model` VARCHAR(64) NOT NULL DEFAULT 'gpt-image-2' COMMENT '生图模型', `model` VARCHAR(64) NOT NULL DEFAULT 'gpt-image-2' COMMENT '生图模型',
`size` VARCHAR(32) NOT NULL DEFAULT '1024x1024' COMMENT '图片尺寸',
`started_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '生图开始时间', `started_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '生图开始时间',
`ended_at` DATETIME(3) NULL COMMENT '生图结束时间', `ended_at` DATETIME(3) NULL COMMENT '生图结束时间',
`duration_ms` INT NULL COMMENT '生图耗时,单位毫秒', `duration_ms` INT NULL COMMENT '生图耗时,单位毫秒',
@@ -0,0 +1,2 @@
ALTER TABLE `image_generations`
DROP COLUMN `size`;
-1
View File
@@ -39,7 +39,6 @@ model ImageGeneration {
prompt String @db.Text prompt String @db.Text
status ImageGenerationStatus @default(RUNNING) status ImageGenerationStatus @default(RUNNING)
model String @default("gpt-image-2") @db.VarChar(64) model String @default("gpt-image-2") @db.VarChar(64)
size String @default("1024x1024") @db.VarChar(32)
startedAt DateTime @default(now()) @map("started_at") startedAt DateTime @default(now()) @map("started_at")
endedAt DateTime? @map("ended_at") endedAt DateTime? @map("ended_at")
durationMs Int? @map("duration_ms") durationMs Int? @map("duration_ms")
+45 -46
View File
@@ -1,8 +1,13 @@
// server/api/images/generate.post.ts - 图片生成接口:创建记录、流式调用上游生图、归档图床并返回图片地址 // server/api/images/generate.post.ts - 图片生成接口:创建记录、流式调用上游生图并在响应后异步归档图床
import { setImmediate } from "node:timers";
import type { import type {
IImageGenerateData, IImageGenerateData,
IImageGenerateRequest IImageGenerateRequest
} from "#shared/types/openai"; } from "#shared/types/openai";
import {
finishImageGenerationArchiveFailed,
finishImageGenerationArchiveSuccess
} from "~~/server/utils";
type ApiLogger = ReturnType<typeof createApiLogger>; type ApiLogger = ReturnType<typeof createApiLogger>;
@@ -15,9 +20,9 @@ type ApiLogger = ReturnType<typeof createApiLogger>;
* 3. 创建 RUNNING 生图记录,并递增全局请求/进行中统计。 * 3. 创建 RUNNING 生图记录,并递增全局请求/进行中统计。
* 4. 在服务端确保并读取 AIArtStudio 完整 key,完整 key 不返回前端。 * 4. 在服务端确保并读取 AIArtStudio 完整 key,完整 key 不返回前端。
* 5. 调用 Chat Completions 流式生图接口,累积 SSE delta content 并提取最终图片 URL。 * 5. 调用 Chat Completions 流式生图接口,累积 SSE delta content 并提取最终图片 URL。
* 6. 尝试把上游图片下载后上传到 Lsky 图床;图床失败不阻断本次生图成功 * 6. 上游返回图片 URL 后立刻把生图结果落库为 SUCCEEDED,并马上返回前端
* 7. 成功时写入上游 URL、图床 URL、完整上游响应、图床响应和耗时 * 7. 响应返回后再用后台异步任务上传 Lsky;归档失败只补写记录和日志,不影响本次响应
* 8. 失败时把记录标记为 FAILED;鉴权失败会清理本地登录态并返回 401。 * 8. 主链路失败时把记录标记为 FAILED;鉴权失败会清理本地登录态并返回 401。
*/ */
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.generate"); const logger = createApiLogger("images.generate");
@@ -85,41 +90,32 @@ export default defineEventHandler(async (event) => {
hasUsage: hasStreamUsage(result.upstreamResponse) hasUsage: hasStreamUsage(result.upstreamResponse)
}); });
stage = "upload_lsky";
// 归档图床用于长期保存;失败时仍继续返回 NewAPI 上游图片 URL。
const archiveResult = await archiveGeneratedImage({
imageUrl: result.imageUrl,
userId,
recordId: record.id,
createdAt: record.startedAt,
logger
});
logger.info("图床归档完成", {
recordId: record.id.toString(),
hosted: Boolean(archiveResult.hostedImageUrl),
mimeType: archiveResult.imageMimeType
});
stage = "finish_success_record"; stage = "finish_success_record";
await finishImageGenerationSuccess(record.id, record.startedAt, { await finishImageGenerationSuccess(record.id, record.startedAt, {
imageUrl: result.imageUrl, imageUrl: result.imageUrl,
hostedImageUrl: archiveResult.hostedImageUrl,
imageMimeType: archiveResult.imageMimeType,
revisedPrompt: result.revisedPrompt, revisedPrompt: result.revisedPrompt,
upstreamResponse: result.upstreamResponse, upstreamResponse: result.upstreamResponse
hostedResponse: archiveResult.hostedResponse,
errorMessage: archiveResult.errorMessage
}); });
logger.done("成功", { logger.done("成功", {
recordId: record.id.toString(), recordId: record.id.toString(),
hosted: Boolean(archiveResult.hostedImageUrl) archiveScheduled: true
});
const finishedRecord = record;
setImmediate(() => {
void archiveGeneratedImageInBackground({
imageUrl: result.imageUrl,
userId,
recordId: finishedRecord.id,
createdAt: finishedRecord.startedAt,
logger
});
}); });
return createSuccessResponse<IImageGenerateData>( return createSuccessResponse<IImageGenerateData>(
{ {
imageUrl: result.imageUrl, imageUrl: result.imageUrl,
hostedImageUrl: archiveResult.hostedImageUrl,
revisedPrompt: result.revisedPrompt revisedPrompt: result.revisedPrompt
}, },
"图片生成成功" "图片生成成功"
@@ -154,8 +150,8 @@ export default defineEventHandler(async (event) => {
} }
}); });
/** 将生成图上传到 Lsky,返回图床地址;归档失败时降级为空结果 */ /** 响应返回后异步归档图片,成功则补写图床字段,失败则补写本地错误文案 */
const archiveGeneratedImage = async ({ const archiveGeneratedImageInBackground = async ({
imageUrl, imageUrl,
userId, userId,
recordId, recordId,
@@ -167,14 +163,12 @@ const archiveGeneratedImage = async ({
recordId: bigint; recordId: bigint;
createdAt: Date; createdAt: Date;
logger: ApiLogger; logger: ApiLogger;
}): Promise<{ }) => {
hostedImageUrl: string | null; logger.info("后台归档开始", {
imageMimeType: string | null; recordId: recordId.toString()
hostedResponse: unknown; });
errorMessage: string | null;
}> => {
try { try {
// 图床归档失败不能阻断本次生成结果,前端仍可使用上游图片 URL。
const identity = await getUserArchiveIdentity(userId); const identity = await getUserArchiveIdentity(userId);
const uploaded = await uploadImageFromUrl({ const uploaded = await uploadImageFromUrl({
imageUrl, imageUrl,
@@ -184,24 +178,29 @@ const archiveGeneratedImage = async ({
createdAt createdAt
}); });
return { await finishImageGenerationArchiveSuccess(recordId, {
hostedImageUrl: uploaded.publicUrl, hostedImageUrl: uploaded.publicUrl,
imageMimeType: uploaded.mimetype, imageMimeType: uploaded.mimetype,
hostedResponse: uploaded.response, hostedResponse: uploaded.response
errorMessage: null });
};
logger.info("后台归档成功", {
recordId: recordId.toString(),
mimeType: uploaded.mimetype,
hosted: true
});
} catch (error) { } catch (error) {
logger.error("图床归档失败", { logger.error("后台归档失败", {
recordId: recordId.toString(), recordId: recordId.toString(),
error: toSafeLogError(error) error: toSafeLogError(error)
}); });
return { await finishImageGenerationArchiveFailed(recordId).catch((recordError) => {
hostedImageUrl: null, logger.error("更新归档失败记录失败", {
imageMimeType: null, recordId: recordId.toString(),
hostedResponse: null, error: toSafeLogError(recordError)
errorMessage: "图片生成成功,但图片归档失败" });
}; });
} }
}; };
+1 -1
View File
@@ -12,7 +12,7 @@ const MAX_PAGE_SIZE = 50;
* 1. 从 httpOnly cookie 读取当前用户 ID。 * 1. 从 httpOnly cookie 读取当前用户 ID。
* 2. 读取 page/size 查询参数,并限制最大 pageSize,避免一次查太多。 * 2. 读取 page/size 查询参数,并限制最大 pageSize,避免一次查太多。
* 3. 只查询当前用户且未软删除的记录,按创建时间倒序返回。 * 3. 只查询当前用户且未软删除的记录,按创建时间倒序返回。
* 4. 列表不返回完整上游响应或图床响应,详情接口再返回这些调试数据。 * 4. 列表只返回页面展示所需字段,不返回内部响应或供应商调试数据。
*/ */
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.list"); const logger = createApiLogger("images.history.list");
+2 -2
View File
@@ -1,4 +1,4 @@
// server/api/images/history/[id].get.ts - 生图历史详情接口:返回当前用户单条记录和完整响应 // server/api/images/history/[id].get.ts - 生图历史详情接口:返回当前用户单条记录的展示字段与归档状态
import type { IImageHistoryDetail } from "#shared/types/openai"; import type { IImageHistoryDetail } from "#shared/types/openai";
import { import {
clearNewApiAuthCookies, clearNewApiAuthCookies,
@@ -19,7 +19,7 @@ import {
* 1. 从 httpOnly cookie 读取当前用户 ID。 * 1. 从 httpOnly cookie 读取当前用户 ID。
* 2. 校验路由参数 id 必须是数字,并转成 BigInt 查询。 * 2. 校验路由参数 id 必须是数字,并转成 BigInt 查询。
* 3. 只允许读取当前用户、未软删除的记录。 * 3. 只允许读取当前用户、未软删除的记录。
* 4. 返回详情时包含完整上游响应和 Lsky 响应,便于排查单次生图问题 * 4. 详情接口继续隐藏内部响应,只返回页面展示和归档状态所需字段
*/ */
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.detail"); const logger = createApiLogger("images.history.detail");
+48 -21
View File
@@ -12,23 +12,23 @@ import { prisma } from "~~/server/utils/prisma";
const GLOBAL_STATS_ID = "global"; const GLOBAL_STATS_ID = "global";
const DEFAULT_IMAGE_MODEL = "gpt-image-2"; const DEFAULT_IMAGE_MODEL = "gpt-image-2";
const DEFAULT_IMAGE_SIZE = "1024x1024";
interface IFinishImageGenerationSuccessInput { interface IFinishImageGenerationSuccessInput {
/** NewAPI 上游返回的生成图片 URL */ /** NewAPI 上游返回的生成图片 URL */
imageUrl: string; imageUrl: string;
/** Lsky 图床归档后的图片 URL,归档失败时为空 */
hostedImageUrl: string | null;
/** 图片 MIME 类型,优先来自图床上传结果 */
imageMimeType: string | null;
/** 上游返回的修订提示词,流式生图通常为空 */ /** 上游返回的修订提示词,流式生图通常为空 */
revisedPrompt?: string | null; revisedPrompt?: string | null;
/** 完整上游响应,当前为 chat completions 流式聚合对象 */ /** 完整上游响应,当前为 chat completions 流式聚合对象 */
upstreamResponse: unknown; upstreamResponse: unknown;
/** 完整 Lsky 上传响应,归档失败时为空 */ }
interface IFinishImageGenerationArchiveSuccessInput {
/** Lsky 图床归档后的图片 URL */
hostedImageUrl: string;
/** 图片 MIME 类型,优先来自图床上传结果 */
imageMimeType: string | null;
/** 完整 Lsky 上传响应 */
hostedResponse: unknown; hostedResponse: unknown;
/** 成功状态下的非阻断提示,例如图床归档失败 */
errorMessage?: string | null;
} }
/** 登录或恢复登录时保存 NewAPI 用户快照 */ /** 登录或恢复登录时保存 NewAPI 用户快照 */
@@ -97,8 +97,7 @@ export const createRunningImageGeneration = async (
userId, userId,
prompt, prompt,
status: ImageGenerationStatus.RUNNING, status: ImageGenerationStatus.RUNNING,
model: DEFAULT_IMAGE_MODEL, model: DEFAULT_IMAGE_MODEL
size: DEFAULT_IMAGE_SIZE
} }
}); });
@@ -114,7 +113,7 @@ export const createRunningImageGeneration = async (
return record; return record;
}; };
/** 将生图记录标记为成功,并保存上游 URL、图床 URL、完整响应和耗时 */ /** 将生图记录标记为成功,并先写入上游结果;图床字段稍后由后台归档补写 */
export const finishImageGenerationSuccess = async ( export const finishImageGenerationSuccess = async (
recordId: bigint, recordId: bigint,
startedAt: Date, startedAt: Date,
@@ -131,15 +130,12 @@ export const finishImageGenerationSuccess = async (
endedAt, endedAt,
durationMs: getDurationMs(startedAt, endedAt), durationMs: getDurationMs(startedAt, endedAt),
imageUrl: input.imageUrl, imageUrl: input.imageUrl,
hostedImageUrl: input.hostedImageUrl, hostedImageUrl: null,
imageMimeType: input.imageMimeType, imageMimeType: null,
revisedPrompt: input.revisedPrompt || null, revisedPrompt: input.revisedPrompt || null,
upstreamResponse: input.upstreamResponse as Prisma.InputJsonValue, upstreamResponse: input.upstreamResponse as Prisma.InputJsonValue,
hostedResponse: hostedResponse: Prisma.DbNull,
input.hostedResponse === null errorMessage: null
? Prisma.DbNull
: (input.hostedResponse as Prisma.InputJsonValue),
errorMessage: input.errorMessage || null
} }
}); });
@@ -186,6 +182,39 @@ export const finishImageGenerationFailed = async (
}); });
}; };
/** 后台归档成功后回写图床地址、MIME 和图床响应 */
export const finishImageGenerationArchiveSuccess = async (
recordId: bigint,
input: IFinishImageGenerationArchiveSuccessInput
) => {
await prisma.imageGeneration.update({
where: {
id: recordId
},
data: {
hostedImageUrl: input.hostedImageUrl,
imageMimeType: input.imageMimeType,
hostedResponse: input.hostedResponse as Prisma.InputJsonValue,
errorMessage: null
}
});
};
/** 后台归档失败时仅补写本地错误文案,不影响已成功的生图状态 */
export const finishImageGenerationArchiveFailed = async (
recordId: bigint,
errorMessage: string = "图片归档失败"
) => {
await prisma.imageGeneration.update({
where: {
id: recordId
},
data: {
errorMessage
}
});
};
/** 查询当前用户未删除的生图历史列表,不返回完整上游/图床响应 */ /** 查询当前用户未删除的生图历史列表,不返回完整上游/图床响应 */
export const listImageGenerationHistory = async ({ export const listImageGenerationHistory = async ({
userId, userId,
@@ -221,7 +250,7 @@ export const listImageGenerationHistory = async ({
}; };
}; };
/** 查询当前用户单条生图历史详情,包含完整上游与图床响应 */ /** 查询当前用户单条生图历史详情,内部响应字段固定隐藏为 null */
export const getImageGenerationDetail = async ( export const getImageGenerationDetail = async (
userId: number, userId: number,
recordId: bigint recordId: bigint
@@ -342,7 +371,6 @@ const mapImageGenerationItem = (record: {
prompt: string; prompt: string;
status: ImageGenerationStatus; status: ImageGenerationStatus;
model: string; model: string;
size: string;
startedAt: Date; startedAt: Date;
endedAt: Date | null; endedAt: Date | null;
durationMs: number | null; durationMs: number | null;
@@ -358,7 +386,6 @@ const mapImageGenerationItem = (record: {
prompt: record.prompt, prompt: record.prompt,
status: record.status, status: record.status,
model: record.model, model: record.model,
size: record.size,
startedAt: record.startedAt.toISOString(), startedAt: record.startedAt.toISOString(),
endedAt: record.endedAt?.toISOString() ?? null, endedAt: record.endedAt?.toISOString() ?? null,
durationMs: record.durationMs, durationMs: record.durationMs,
+3 -7
View File
@@ -19,10 +19,8 @@ export interface IImageGenerateRequest {
* 图片生成成功后返回给前端的数据。 * 图片生成成功后返回给前端的数据。
*/ */
export interface IImageGenerateData { export interface IImageGenerateData {
/** NewAPI 上游返回的图片访问地址,前端当前优先展示这个地址 */ /** 生成图片访问地址,前端当前优先展示这个地址 */
imageUrl: string; imageUrl: string;
/** Lsky 图床归档后的图片访问地址,归档失败时为空 */
hostedImageUrl?: string | null;
/** 上游返回的修订提示词,可能为空 */ /** 上游返回的修订提示词,可能为空 */
revisedPrompt?: string; revisedPrompt?: string;
} }
@@ -50,8 +48,6 @@ export interface IImageHistoryItem {
status: ImageGenerationStatus; status: ImageGenerationStatus;
/** 生图模型 */ /** 生图模型 */
model: string; model: string;
/** 图片尺寸 */
size: string;
/** 生图开始时间,ISO 字符串 */ /** 生图开始时间,ISO 字符串 */
startedAt: string; startedAt: string;
/** 生图结束时间,ISO 字符串,未结束时为 null */ /** 生图结束时间,ISO 字符串,未结束时为 null */
@@ -60,11 +56,11 @@ export interface IImageHistoryItem {
durationMs: number | null; durationMs: number | null;
/** NewAPI 上游返回的图片访问地址 */ /** NewAPI 上游返回的图片访问地址 */
imageUrl: string | null; imageUrl: string | null;
/** Lsky 图床归档后的图片访问地址 */ /** Lsky 图床归档后的图片访问地址;为空且没有错误提示时表示仍在归档中 */
hostedImageUrl: string | null; hostedImageUrl: string | null;
/** 上游返回的修订提示词,可能为空 */ /** 上游返回的修订提示词,可能为空 */
revisedPrompt: string | null; revisedPrompt: string | null;
/** 面向前端的本地泛化错误提示,不包含上游或内部服务细节 */ /** 面向前端的本地泛化错误提示;为空且 hostedImageUrl 也为空时表示仍在归档中 */
errorMessage: string | null; errorMessage: string | null;
/** 记录创建时间,ISO 字符串 */ /** 记录创建时间,ISO 字符串 */
createdAt: string; createdAt: string;