From 53917268d32c27084dbe94b082b8669a6bf5664e Mon Sep 17 00:00:00 2001 From: GouJ Date: Mon, 31 Aug 2026 19:14:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8C=E6=AD=A5=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E9=80=89=E8=A7=92=E4=B8=8E=E9=A6=96=E5=B8=A7=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 +++- src/features/production/ProductionPage.vue | 16 ++ .../components/ShotProductionAssets.vue | 10 + src/features/production/model.ts | 3 + src/features/production/production.test.ts | 2 + src/features/production/types.ts | 20 +- .../storyboard/components/ShotTools.vue | 8 +- .../storyboard/generation-spec.types.ts | 2 + .../subject-identity/SubjectIdentityPage.vue | 258 ++++++++++++++---- src/features/subject-identity/api.ts | 30 ++ .../components/CastingCandidateDialog.vue | 84 ++++++ .../components/IdentityEditor.vue | 11 +- .../components/IdentityGallery.vue | 28 +- .../components/IdentityImageDialog.vue | 18 +- .../subject-identity/identity.test.ts | 118 +++++++- src/features/subject-identity/model.ts | 46 +++- src/features/subject-identity/types.ts | 89 +++++- .../subject-identity/useSubjectIdentity.ts | 130 ++++++++- src/features/workflows/pages.test.ts | 75 ++++- src/styles.css | 55 +++- 20 files changed, 940 insertions(+), 112 deletions(-) create mode 100644 src/features/subject-identity/components/CastingCandidateDialog.vue diff --git a/README.md b/README.md index 1d6b3cb..88475c5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Short Drama Agent Front -短剧 Agent 的前端工作台。支持剧本创作、拆解、视觉风格、主体身份、形态图片与分镜设计,使用真实后端 API,不包含演示数据或浏览器端模型调用。 +短剧 Agent 的前端工作台。支持剧本创作、拆解、视觉风格、角色选角、主体形态、分镜设计、镜头首帧与视频生产,使用真实后端 API,不包含演示数据或浏览器端模型调用。 ## 启动 @@ -32,14 +32,16 @@ API_PROXY_TARGET=http://localhost:3412 | 拆解结果 | 主体、别名、视觉形态、Episode → Beat → Shot、主体绑定、抽取任务状态、校验问题、JSON 导出 | | 拆解恢复 | 失败抽取重试、缺失剧集镜头补齐、分镜引用与 Form 绑定修复 | | 视觉风格 | AI 生成、人工编辑、分类 Prompt、JSON 硬约束、锁定;风格参考图地址登记、启停和删除 | -| 主体身份 | 按正式主体归并形态,单个/批量生成身份文本,人工编辑及锁定,部分失败诊断 | -| 身份母版 | Seedream 生图、四种参考视角、指定身份参考图、母版候选与切换、实际 Prompt 和来源追溯 | +| 主体身份 | 正式主体目录、单个/批量身份文本、人工编辑与锁定;无形态 Character 也可先完成身份 | +| 角色选角 | 选角就绪检查、角色身份专用批量生成、Seedream 候选小批量生成、确认演员并原子锁定 Identity | +| 身份母版 | 普通身份图与辅助视角、实际 Prompt 和来源追溯;Character 母版通过确认选角确定 | | 分镜检查 | 按剧集、Beat、正式 Shot ID 浏览;导演设计、环境与主体即时状态;单集覆盖率与 JSON 导出 | | 分镜生成 | Direction / VisualState 单集生成、项目批量补齐、覆盖重生成;并发和自动修复次数;部分失败诊断 | | 镜头工具 | 主体参考图与缺失原因、确定性 GenerationSpec 查询与导出、单镜头/批量视频提示词 | | 形态图片 | 正式形态图库、主图/候选图预览、图片历史与失败记录、Seedream 单图/批量生图、切换主参考图 | +| 镜头生产 | Prompt/首帧/视频就绪诊断、Seedream 首帧候选、Seedance 任务、成片播放、主资产切换与失败重试 | -尚未实现:手工编辑剧本或分镜设计(后端暂无对应写接口)、实际视频生成、用户登录。模型输出按纯文本显示,避免执行不可信 HTML。参考图只接受 HTTP(S) 或后端 `/storage/` 路径。 +尚未实现:手工编辑剧本或分镜设计(后端暂无对应写接口)、用户登录。模型输出按纯文本显示,避免执行不可信 HTML。图片和视频只接受 HTTP(S) 或后端 `/storage/` 路径。 ## 技术与规范 @@ -72,6 +74,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S | `src/features/subject-images` | 正式形态图库、图片生成参数、历史图片、主图选择与批量回执 | | `src/features/visual-style` | 项目视觉风格 API、类型、编辑器、锁定和风格参考图管理 | | `src/features/subject-identity` | 稳定身份 API、正式主体目录、文本编辑、生图配置、身份图库和母版切换 | +| `src/features/production` | 视频提示词、镜头首帧、视频任务、就绪诊断、资产候选与批量回执 | | `src/features/workflows` | checkpoint 选择、执行记录、长请求互斥与操作确认 | | `src/components/ui` | 跨业务使用的 Reka UI 封装与公共展示组件 | | `src/composables` | 具备取消和竞态保护的轮询 | @@ -83,7 +86,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S ## 接口基线 对齐后端 `qianlanse/short-drama-agent` 的 `dev`: -`059e59709359dccd08b08cb35c6aff95c5e03f2b`(2026-08-28),包含 VisualStyle、SubjectIdentity、身份母版、人物/场景形态自动引用母版及道具稳定特征提取。本轮只修改前端,请确保后端依照其开发流程同步了新 Prisma 结构并重启服务。 +`c50d3f536fbca40ea07d36e00bace7bb886fabfa`(2026-08-31),包含 Character Casting、身份/形态双参考首帧、首帧与视频生产就绪检查。本轮只修改前端,请确保后端依照其开发流程同步 Prisma migration 并重启服务。 | HTTP | 路径(以 /api 为前缀) | 用途 | | --- | --- | --- | @@ -120,8 +123,22 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S | GET / PUT | `/subjects/:subjectId/identity` | 查询/人工编辑稳定身份;使用正式 Subject ID | | POST | `/subjects/:subjectId/identity/generate` | AI 生成身份文本,需要项目视觉风格 | | POST | `/projects/:id/subject-identities/generate` | 项目批量身份文本;`force`、`concurrency`,已锁定始终跳过 | +| POST | `/projects/:id/character-identities/generate` | 只批量生成 Character Identity 文本 | +| GET | `/projects/:id/character-casting/readiness` | 角色身份、候选、Anchor 与锁定状态汇总 | +| POST | `/projects/:id/character-casting/candidates/generate` | 为缺少 Anchor 的角色小批量生成停用候选 | | GET / POST | `/subjects/:subjectId/identity/images` | 查询带 `isAnchor` 的身份图片/Seedream 生图 | +| POST | `/subjects/:subjectId/identity/casting-candidates` | 单角色生成 primary 选角候选,不自动成为 Anchor | | PUT | `/subjects/:subjectId/identity/images/:imageId/anchor` | 将成功的 primary 候选设为身份母版 | +| PUT | `/subjects/:subjectId/identity/images/:imageId/casting` | 确认 Character 选角:切换 Anchor 并锁定 Identity | +| GET | `/projects/:id/video-prompts/readiness` | 项目视频提示词前置条件检查 | +| GET / POST | `/projects/:id/keyframes/readiness`、`/keyframes/generate` | 首帧就绪检查与批量 Seedream 生图 | +| GET / POST | `/storyboard-shots/:shotId/keyframes`、`/keyframe` | 单镜头首帧历史与生图 | +| PUT | `/storyboard-shots/:shotId/keyframes/:keyframeId/primary` | 切换主首帧 | +| GET / POST | `/projects/:id/videos/readiness`、`/videos/generate` | 视频前置检查与批量 Seedance 任务 | +| GET | `/projects/:id/videos/status` | 项目最近视频任务状态 | +| POST | `/projects/:id/videos/retry-failed` | 重试最近失败的视频任务 | +| GET / POST | `/storyboard-shots/:shotId/videos` | 单镜头视频历史与任务创建 | +| PUT | `/storyboard-shots/:shotId/videos/:videoId/primary` | 切换主视频 | API 层也提供 `state` 与 `breakdown/latest` 方法。页面通过共享 checkpoint 查询取得阶段状态,避免重复拉取同样内容。 @@ -147,7 +164,7 @@ API 层也提供 `state` 与 `breakdown/latest` 方法。页面通过共享 chec 7. 上游重生成**不会自动使下游旧数据失效**。覆盖 Direction 后请重生成 VisualState,修改设计/状态/参考图后按需覆盖 Prompt。覆盖率只说明字段已保存,不能证明下游与新版本一致。 8. 每 6 秒查询当前剧集的两类正式结果;这不是全项目实时进度。后端没有独立 Storyboard running / executionId 查询。回执只保留当前浏览器会话,切换页面不丢失,刷新浏览器会丢失。 9. 参考图和 GenerationSpec 按需 GET,切换镜头或设计变化时取消旧查询。GenerationSpec 需要有效的 Direction、VisualState 和形态关联,不生成图片或视频。 -10. 后端当前的 Video Prompt 仍使用镜头描述和参考图,**尚未消费 GenerationSpec**。没有独立 GET Prompt 接口,“读取/生成提示词”仍是 POST;不存在 Prompt 时会调用模型,因此必须确认。`force: false` 复用已有正文;批量响应只有计数和失败列表,不能凭计数构造 Prompt 正文。`generated` 是后端处理成功数,可能包含已存在但状态不为 `prompt_ready` 的复用项。 +10. Video Prompt、Keyframe 与 Video 都有项目级 readiness。页面只向 `ready` 镜头批量提交;`force` 会重写 Prompt 或新增首帧/视频候选,但不会静默替换主首帧和主视频。视频任务创建成功不代表成片完成,最终状态以后端轮询和持久化资产为准。 ### 形态图片约定 @@ -163,20 +180,21 @@ API 层也提供 `state` 与 `breakdown/latest` 方法。页面通过共享 chec ### 视觉风格、身份与形态的衔接 -建议操作顺序:**剧本创作 → 剧本拆解 → 视觉风格 → 主体身份 → 形态图片 → 分镜设计**。视觉风格也可以在拆解前准备;这不是前端强制启动的整条生产流程。 +建议操作顺序:**剧本创作 → 剧本拆解 → 视觉风格 → 角色身份与选角 → 形态图片 → 分镜设计 → 镜头生产**。视觉风格也可以在拆解前准备;页面不会自动重生成已存在的下游资产。 | 层级 | 职责 | 本次可操作内容 | | --- | --- | --- | | VisualStyle | 项目整体美术与视觉语言 | 整体/人物/场景/道具 Prompt、JSON 字符串数组硬约束、锁定、风格图片记录 | | SubjectIdentity | 同一主体跨形态不变的特征 | 稳定身份描述、身份 Prompt、锁定、单个及批量 AI 文本生成 | -| Identity Image | 固定主体身份,人物主要固定脸部与体型 | primary、front、three-quarter、full-body 四类生图、身份锚定来源、母版选择 | +| Character Casting | 确认具体演员身份 | 候选图、人工选择 Anchor、Identity 原子锁定、项目就绪检查 | +| Identity Image | 固定主体身份或补充辅助视角 | Scene / Prop 母版及 Character front、three-quarter、full-body 辅助图 | | SubjectForm Image | 某个具体造型或状态的图片 | 原有单图/批量生图与主图选择;人物/场景图自动引用当前身份母版 | -1. 风格与身份的 `isLocked` 只禁止 AI 自动覆盖文本。已锁定记录仍可人工编辑并保存;身份生图和母版切换也不受文字锁定限制。需要 AI 重生成时,先取消锁定并保存。 +1. 风格锁定仍用于阻止 AI 覆盖。Character 的 `isLocked` 同时是正式选角前置条件:必须具有 Identity、已确认的 primary Anchor 并锁定,Keyframe 才会就绪;确认选角接口会原子完成后两项。 2. 身份 AI 生成需要已创建 VisualStyle;身份图生成还需要已保存的非空 `generationPrompt`。不存在身份时不调用会报错的图片 GET。404 或网络失败不会被当作“尚未创建”。 -3. 后端暂无全项目主体/身份的 GET。前端从正式 `subject-forms` 目录按 `subjectId` 去重,选中主体后按需读取身份和专用图库;无形态主体暂不在目录展示。批量仍由后端处理项目全部主体,可能多于目录可见数,不受当前筛选影响。未使用 checkpoint 或 `DramaCharacter.id` 拼造 Subject ID。 -4. 身份文本批量回执显示 `total`、`targetCount`、`generated`、`skipped`、`skippedLocked`、`failed` 和逐主体失败原因。它不代表图片已生成,也不是实时任务进度。后端没有批量身份生图接口,前端不自行循环触发有费用的图片请求。 -5. 身份图片的 `isAnchor` 来自专用 GET。`enabled: true` 的正面、侧面或全身图不是母版;只有成功且有地址的 `primary` 图可经 PUT 设为母版。第一张 primary 图自动启用;已有母版后,新 primary 图作为停用候选保留,不静默替换。辅助视图不能直接选为母版,但可以作为新 primary 图的参考来源。 +3. 前端以正式 `subject-forms` 目录为基础,并合并 Character Casting readiness,因此尚无形态的角色也可先完成 Identity 和选角。Scene / Prop 若还没有 Form,仍不会凭 checkpoint 伪造正式 Subject ID。 +4. 身份文本批量分为 Character 专用入口和全部主体高级入口。选角候选批量只处理 `missing_anchor`,受本批上限控制;`candidate_pending` 必须人工确认,不会由批量任务静默决定演员。 +5. 身份图片的 `isAnchor` 来自专用 GET。Character primary 通过 Casting Candidate 接口生成且默认停用;点击确认演员后才成为 Anchor 并锁定 Identity。辅助视图不能直接成为母版。Scene / Prop 保留普通母版切换接口。 6. 身份生图不传 `referenceImageId` 时,由后端自动选择当前母版;无母版时按文本生成。指定时只能选择当前身份已完成的图片;候选图即使停用也可作为显式参考。后端没有“有母版但忽略它”的参数。尺寸成对留空或提供正整数。自定义 Prompt 会替换后端编译的完整文本,建议保留默认。 7. 目前**人物与场景形态生图**会自动注入当前身份母版,分别固定人物身份和空间骨架;道具暂不自动引用。没有身份或母版时,原形态生图流程仍可使用。已有母版但缺少 Provider 可访问远程地址时,后端会报错;本地 `/storage/` 能显示并不证明原始 Provider URL 仍可访问。前端显示后端错误,不自动重试消耗额度。 8. 修改风格、身份文本或母版,不会自动使旧图失效或重生成。已有形态主图仍会被分镜使用;需要保持新身份时,主动生成候选图并设为形态主图,再刷新分镜参考图,按需覆盖视频提示词。图库详情展示当次 `identityAnchorImageId` 或 `referenceImageId`;旧记录没有该字段时明确显示未知,不推断它使用了当前母版。 @@ -237,13 +255,14 @@ server { 5. 在可控测试项目中触发故障,确认 retry / resume-shots / resume-storyboard 各自对应正确失败阶段。 6. 进入分镜设计,确认正式镜头 ID 与 Beat 对应;先生成单集 Direction,再生成 VisualState,检查覆盖率和主体状态。 7. 用小型测试项目验证批量补齐、跳过完整集、失败诊断和明确覆盖;确认关闭覆盖后不会重生成完整设计。 -8. 给主体形态准备参考图,读取镜头参考图和 GenerationSpec;确认 Prompt 无正文时经确认生成、有正文且不覆盖时复用。不提交实际视频任务。 +8. 给主体形态准备参考图,读取镜头参考图和 GenerationSpec;在镜头生产页核对 Prompt、首帧和视频三类 readiness。 9. 打开“形态图片”,检查已有主图自动显示;单个形态新增候选图,查看历史、失败原因和实际 Prompt,再设为主图。 10. 在小型测试项目中验证批量补齐与“已有主图也新增候选图”;回到分镜刷新参考图,确认使用新选择的主图。 11. 进入“视觉风格”,保存或生成风格,检查分类 Prompt 和硬约束;锁定后 AI 入口禁用,人工编辑仍可保存。登记一张已有风格图,检查启停和移除确认。 -12. 进入“主体身份”,检查同一主体的多形态归并。生成/保存身份文本并锁定,再生成第一张 primary 图;生成第二张后,确认旧母版未被替换。 -13. 为同一主体选择参考来源并生成 front / three-quarter / full-body 图;确认辅助图不能直接设为母版。将第二张 primary 候选设为母版后,检查旧母版停用且保留。 +12. 进入“主体身份”,先补齐 Character Identity,再小批量生成选角候选。确认候选不会自动成为 Anchor,人工确认演员后 Identity 同时锁定且 readiness 变为 ready。 +13. 为已确认演员生成 front / three-quarter / full-body 辅助图;确认辅助图不能直接设为母版。角色需要更多演员候选时使用单角色候选入口。 14. 对人物和场景形态分别新增图片,在图片详情检查当次母版 ID;旧主图应保持不变,手动切换后再刷新分镜参考图。道具不应显示自动继承母版已生效。 +15. 在镜头生产页生成单镜头首帧,确认规格同时列出 `identity-anchor` 与 `form-primary`。切换主首帧后再创建 Seedance 任务,等待完成后播放并选择主视频。 本轮新增测试覆盖风格 JSON/锁定/图片 DELETE、身份归并/前置条件/尺寸/显式参考图/母版切换、部分失败、旧响应、草稿保护与费用确认。模型调用均使用模拟 API。当前执行环境未能通过浏览器访问本地开发服务,**未完成浏览器视觉验收及真实模型联调**,请按上述步骤在本地验收。 diff --git a/src/features/production/ProductionPage.vue b/src/features/production/ProductionPage.vue index 45a4e77..6cecd27 100644 --- a/src/features/production/ProductionPage.vue +++ b/src/features/production/ProductionPage.vue @@ -75,6 +75,12 @@ const videoRunning = computed( (query.data.value?.videoStatus.queued ?? 0) + (query.data.value?.videoStatus.running ?? 0) ) +const identityBlocked = computed( + () => + (query.data.value?.keyframes.missingIdentity ?? 0) + + (query.data.value?.keyframes.missingIdentityAnchor ?? 0) + + (query.data.value?.keyframes.identityUnlocked ?? 0) +) /** 父页查询包含整个项目的就绪统计,单镜头变更后立即刷新。 */ function refreshProject() { @@ -149,6 +155,11 @@ function refreshProject() {

当前有 {{ videoRunning }} 个视频任务等待或生成中。后端正在轮询 Provider;同一镜头不会重复提交活动任务。

+

+ 当前有 {{ identityBlocked }} 项角色身份前置问题。Character 必须生成 + Identity、确认演员母版并锁定后,首帧才能就绪。 + 前往角色选角 → +

@@ -191,6 +202,11 @@ function refreshProject() {
{{ stage.data?.blocked ?? 0 }}
+

+ 身份缺失 {{ query.data.value?.keyframes.missingIdentity ?? 0 }} · 母版缺失 + {{ query.data.value?.keyframes.missingIdentityAnchor ?? 0 }} · 未锁定 + {{ query.data.value?.keyframes.identityUnlocked ?? 0 }} +

首帧 Prompt · {{ keyframeSpec.references.length }} 张参考图

{{ keyframeSpec.prompt }}

+
    +
  • + {{ reference.subjectRef }} · + {{ reference.role === 'identity-anchor' ? '演员身份母版' : '形态主图' }} · + {{ reference.imageId }} +
  • +
视频 Prompt · {{ videoSpec.durationSeconds }}s
diff --git a/src/features/production/model.ts b/src/features/production/model.ts index 584ade4..16d34b7 100644 --- a/src/features/production/model.ts +++ b/src/features/production/model.ts @@ -43,6 +43,9 @@ export function issueLabel(code: ProductionIssueCode): string { invalid_generation_spec: '生成规格不完整', missing_visual_style: '缺少视觉风格', missing_reference: '缺少主体参考图', + missing_identity: '缺少角色身份', + missing_identity_anchor: '缺少演员母版', + identity_unlocked: '演员身份未锁定', missing_prompt: '缺少视频提示词', missing_keyframe: '缺少主首帧' } diff --git a/src/features/production/production.test.ts b/src/features/production/production.test.ts index 01b848f..e697c55 100644 --- a/src/features/production/production.test.ts +++ b/src/features/production/production.test.ts @@ -51,6 +51,8 @@ describe('镜头生产数据契约', () => { it('就绪问题和异步任务状态提供中文标签', () => { expect(issueLabel('missing_keyframe')).toBe('缺少主首帧') expect(issueLabel('invalid_generation_spec')).toBe('生成规格不完整') + expect(issueLabel('missing_identity_anchor')).toBe('缺少演员母版') + expect(issueLabel('identity_unlocked')).toBe('演员身份未锁定') expect(productionStatusLabel('in_progress')).toBe('任务进行中') expect(productionStatusLabel('unknown')).toBe('unknown') }) diff --git a/src/features/production/types.ts b/src/features/production/types.ts index e06c5a9..b1e6e52 100644 --- a/src/features/production/types.ts +++ b/src/features/production/types.ts @@ -6,6 +6,9 @@ export type ProductionIssueCode = | 'invalid_generation_spec' | 'missing_visual_style' | 'missing_reference' + | 'missing_identity' + | 'missing_identity_anchor' + | 'identity_unlocked' | 'missing_prompt' | 'missing_keyframe' @@ -37,12 +40,17 @@ export interface PromptReadiness { /** 镜头首帧就绪检查的逐镜结果。 */ export interface KeyframeReadinessItem extends PromptReadinessItem { + episodeNo: number + beatNo: number primaryKeyframeId?: string | null } /** 镜头首帧项目级就绪汇总。 */ export interface KeyframeReadiness extends PromptReadiness { missingVisualStyle: number + missingIdentity: number + missingIdentityAnchor: number + identityUnlocked: number items: KeyframeReadinessItem[] } @@ -111,7 +119,8 @@ export interface KeyframeReference { subjectId: string subjectRef: string module: string - subjectFormId: string + role: 'identity-anchor' | 'form-primary' + subjectFormId?: string imageId: string imageUrl: string providerImageUrl?: string @@ -129,9 +138,16 @@ export interface KeyframeSpec { } /** 视频生成规格中的主体参考图。 */ -export interface VideoReference extends KeyframeReference { +export interface VideoReference { + subjectId: string + subjectRef: string subjectName: string + module: string + subjectFormId: string subjectFormName: string + imageId: string + imageUrl: string + providerImageUrl?: string } /** 后端实际提交 Seedance 前编译的完整视频生成规格。 */ diff --git a/src/features/storyboard/components/ShotTools.vue b/src/features/storyboard/components/ShotTools.vue index ba22d67..0bf249a 100644 --- a/src/features/storyboard/components/ShotTools.vue +++ b/src/features/storyboard/components/ShotTools.vue @@ -174,8 +174,8 @@ function exportResult(kind: 'spec' | 'prompt') {

- 后端合并主体、形态、摄影设计与即时状态,编译为 GenerationSpec,不调用模型。需要本镜头已有 Direction 和 - VisualState。 + 后端合并主体稳定身份、具体形态、摄影设计与即时状态,编译为 GenerationSpec,不调用模型。需要本镜头已有 + Direction 和 VisualState。

diff --git a/src/features/subject-identity/api.ts b/src/features/subject-identity/api.ts index 0da3a18..0d76b0c 100644 --- a/src/features/subject-identity/api.ts +++ b/src/features/subject-identity/api.ts @@ -1,5 +1,10 @@ import { request } from '../../lib/http' import type { + CastingBatchResult, + CharacterCastingReadiness, + ConfirmCastingResult, + GenerateCastingCandidateInput, + GenerateCastingCandidatesInput, GenerateIdentityImageInput, IdentityBatchResult, IdentityImage, @@ -30,6 +35,27 @@ export const subjectIdentityApi = { body: input, timeoutMs: 0 }), + generateCharacters: (projectId: string, input: { force: boolean; concurrency: number }) => + request(`/projects/${encodeURIComponent(projectId)}/character-identities/generate`, { + method: 'POST', + body: input, + timeoutMs: 0 + }), + castingReadiness: (projectId: string, signal?: AbortSignal) => + request(`/projects/${encodeURIComponent(projectId)}/character-casting/readiness`, { + signal + }), + generateCastingCandidate: (subjectId: string, input: GenerateCastingCandidateInput) => + request(`${identityPath(subjectId)}/casting-candidates`, { + method: 'POST', + body: input, + timeoutMs: 0 + }), + generateCastingCandidates: (projectId: string, input: GenerateCastingCandidatesInput) => + request( + `/projects/${encodeURIComponent(projectId)}/character-casting/candidates/generate`, + { method: 'POST', body: input, timeoutMs: 0 } + ), listImages: (subjectId: string, signal?: AbortSignal) => request(`${identityPath(subjectId)}/images`, { signal }), generateImage: (subjectId: string, input: GenerateIdentityImageInput) => @@ -37,5 +63,9 @@ export const subjectIdentityApi = { setAnchor: (subjectId: string, imageId: string) => request(`${identityPath(subjectId)}/images/${encodeURIComponent(imageId)}/anchor`, { method: 'PUT' + }), + confirmCasting: (subjectId: string, imageId: string) => + request(`${identityPath(subjectId)}/images/${encodeURIComponent(imageId)}/casting`, { + method: 'PUT' }) } diff --git a/src/features/subject-identity/components/CastingCandidateDialog.vue b/src/features/subject-identity/components/CastingCandidateDialog.vue new file mode 100644 index 0000000..2028a6f --- /dev/null +++ b/src/features/subject-identity/components/CastingCandidateDialog.vue @@ -0,0 +1,84 @@ + + +