{"record":{"id":"43ace357d20629df","repo":"yikart/AiToEarn","slug":"only-one-last-frame-image-is-allowed","errorCode":null,"errorMessage":"Only one last frame image is allowed","messagePattern":"Only one last frame image is allowed","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/video/volcengine/volcengine.service.ts","lineNumber":167,"sourceCode":"    const imageContents = content.filter((item): item is Extract<Content, { type: ContentType.ImageUrl }> => item.type === ContentType.ImageUrl)\n    const videoContents = content.filter((item): item is Extract<Content, { type: ContentType.VideoUrl }> => item.type === ContentType.VideoUrl)\n    const audioContents = content.filter((item): item is Extract<Content, { type: ContentType.AudioUrl }> => item.type === ContentType.AudioUrl)\n\n    const firstFrameImages = imageContents.filter(item => !item.role || item.role === ImageRole.FirstFrame)\n    const lastFrameImages = imageContents.filter(item => item.role === ImageRole.LastFrame)\n    const referenceImages = imageContents.filter(item => item.role === ImageRole.ReferenceImage)\n    const referenceVideos = videoContents.filter(item => item.role === VideoRole.ReferenceVideo)\n    const referenceAudios = audioContents.filter(item => item.role === AudioRole.ReferenceAudio)\n\n    const hasFrameScene = firstFrameImages.length > 0 || lastFrameImages.length > 0\n    const hasReferenceScene = referenceImages.length > 0 || referenceVideos.length > 0 || referenceAudios.length > 0\n\n    if (firstFrameImages.length > 1) {\n      throw new BadRequestException('Only one first frame image is allowed')\n    }\n\n    if (lastFrameImages.length > 1) {\n      throw new BadRequestException('Only one last frame image is allowed')\n    }\n\n    if (lastFrameImages.length > 0 && firstFrameImages.length === 0) {\n      throw new BadRequestException('last_frame requires first_frame')\n    }\n\n    if (hasFrameScene && hasReferenceScene) {\n      throw new BadRequestException('first_frame/last_frame and reference media cannot be mixed')\n    }\n\n    if (referenceAudios.length > 0 && referenceImages.length === 0 && referenceVideos.length === 0) {\n      throw new BadRequestException('reference_audio requires at least one reference image or reference video')\n    }\n  }\n\n  private normalizeRequest(request: UserVolcengineGenerationRequestDto) {\n    let prompt = ''\n    let inlineResolution: string | undefined","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/video/volcengine/volcengine.service.ts#L149-L185","documentation":"Same constraint as first frame: Volcengine accepts at most one last frame image. validateContent throws this BadRequestException when more than one image content item with the last-frame role is present.","triggerScenarios":"Supplying 2+ ImageUrl content items with the last-frame role in a generation request.","commonSituations":"UI allowing multiple end frames; loops appending every uploaded image as last_frame; schema changes where an array of end frames was previously allowed.","solutions":["Keep exactly one last-frame image content item","Drop or re-role the extra last-frame images","Validate last-frame count client-side before calling"],"exampleFix":"// before\ncontent: [lastA, lastB].map(u => ({ type:'image_url', role:'last_frame', url: u }))\n// after\ncontent: [{ type:'image_url', role:'last_frame', url: lastA }]","handlingStrategy":"validation","validationCode":"const lastFrames = content.filter(c => c.type === 'image_url' && c.role === 'last_frame'); if (lastFrames.length > 1) throw new Error('Only one last frame image is allowed')","typeGuard":"const isSingleLastFrame = (c: Content[]) => c.filter(i => i.type === ContentType.ImageUrl && i.role === 'last_frame').length <= 1","tryCatchPattern":"try { await generate(req) } catch (e) { if (e instanceof BadRequestException && e.message.includes('last frame image')) { /* keep only one last-frame image and retry */ } else throw e }","preventionTips":["Enforce single-select for the last-frame picker in UI","Deduplicate content by role before sending","Document the 1-image limit for frame roles in API consumers"],"tags":["validation","volcengine","content-constraints"],"backgroundTag":"too-many-items-validation","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}