Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yaoai-video
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
姚珂
yaoai-video
Commits
3b6236dd
Commit
3b6236dd
authored
Apr 23, 2026
by
yaoke.yk
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update comic studio project and asset generation flow
parent
16da3a9f
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
526 additions
and
156 deletions
+526
-156
vite.config.ts
doc/html/vite.config.ts
+5
-0
docker-compose.yml
yaoai-comic-studio/docker-compose.yml
+0
-2
CharacterController.java
...in/java/com/yaoai/api/controller/CharacterController.java
+12
-7
ProjectCreateRequest.java
.../java/com/yaoai/api/dto/project/ProjectCreateRequest.java
+6
-0
ProjectDTO.java
...i/src/main/java/com/yaoai/api/dto/project/ProjectDTO.java
+4
-0
ProjectUpdateRequest.java
.../java/com/yaoai/api/dto/project/ProjectUpdateRequest.java
+4
-0
ProjectServiceImpl.java
...n/java/com/yaoai/api/service/impl/ProjectServiceImpl.java
+5
-0
YaoAiApplication.java
...i-bootstrap/src/main/java/com/yaoai/YaoAiApplication.java
+1
-0
V11__character_three_views.sql
...ain/resources/db/migration/V11__character_three_views.sql
+13
-0
V12__project_display_options.sql
...n/resources/db/migration/V12__project_display_options.sql
+3
-0
Character.java
...main/src/main/java/com/yaoai/domain/entity/Character.java
+9
-3
Project.java
...domain/src/main/java/com/yaoai/domain/entity/Project.java
+4
-0
AssetGenPipelineService.java
...a/com/yaoai/pipeline/service/AssetGenPipelineService.java
+3
-7
AssetGenPipelineServiceImpl.java
...ai/pipeline/service/impl/AssetGenPipelineServiceImpl.java
+457
-137
No files found.
doc/html/vite.config.ts
View file @
3b6236dd
...
...
@@ -30,6 +30,11 @@ export default defineConfig({
'@'
:
path
.
resolve
(
__dirname
,
'./src'
),
},
},
server
:
{
host
:
true
,
port
:
5173
,
allowedHosts
:
true
},
// File types to support raw imports. Never add .css, .tsx, or .ts files to this.
assetsInclude
:
[
'**/*.svg'
,
'**/*.csv'
],
...
...
yaoai-comic-studio/docker-compose.yml
View file @
3b6236dd
version
:
"
3.9"
services
:
mysql
:
image
:
mysql:8.0
...
...
yaoai-comic-studio/yaoai-api/src/main/java/com/yaoai/api/controller/CharacterController.java
View file @
3b6236dd
...
...
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.*;
import
org.springframework.web.multipart.MultipartFile
;
import
java.io.IOException
;
import
java.util.List
;
@Tag
(
name
=
"角色管理"
)
...
...
@@ -22,7 +21,7 @@ public class CharacterController {
private
final
AssetGenPipelineService
assetService
;
@Operation
(
summary
=
"AI 提取角色列表
(从大纲+分集)
"
)
@Operation
(
summary
=
"AI 提取角色列表"
)
@PostMapping
(
"/extract"
)
public
ApiResponse
<
List
<
Character
>>
extract
(
@PathVariable
Long
projectId
)
{
return
ApiResponse
.
success
(
assetService
.
extractCharacters
(
projectId
,
TenantContext
.
get
()));
...
...
@@ -34,7 +33,7 @@ public class CharacterController {
return
ApiResponse
.
success
(
assetService
.
listCharacters
(
projectId
,
TenantContext
.
get
()));
}
@Operation
(
summary
=
"新建
/
更新角色"
)
@Operation
(
summary
=
"新建
或
更新角色"
)
@PostMapping
public
ApiResponse
<
Character
>
save
(
@PathVariable
Long
projectId
,
@RequestBody
Character
req
)
{
req
.
setProjectId
(
projectId
);
...
...
@@ -42,23 +41,29 @@ public class CharacterController {
return
ApiResponse
.
success
(
assetService
.
saveCharacter
(
req
));
}
@Operation
(
summary
=
"
为角色生成形象图(Seedream)
"
)
@Operation
(
summary
=
"
AI 生成角色三视图
"
)
@PostMapping
(
"/{characterId}/generate-image"
)
public
ApiResponse
<
Character
>
generateImage
(
@PathVariable
Long
projectId
,
@PathVariable
Long
characterId
)
{
return
ApiResponse
.
success
(
assetService
.
generateCharacterImage
(
characterId
,
TenantContext
.
get
()));
}
@Operation
(
summary
=
"上传角色
图片(手动)
"
)
@Operation
(
summary
=
"上传角色
视图图片
"
)
@PostMapping
(
value
=
"/{characterId}/upload-image"
,
consumes
=
"multipart/form-data"
)
public
ApiResponse
<
Character
>
uploadImage
(
@PathVariable
Long
projectId
,
@PathVariable
Long
characterId
,
@RequestParam
(
defaultValue
=
"front"
)
String
viewType
,
@RequestPart
(
"file"
)
MultipartFile
file
)
throws
IOException
{
String
originalName
=
file
.
getOriginalFilename
()
!=
null
?
file
.
getOriginalFilename
()
:
"image.jpg"
;
String
ext
=
originalName
.
contains
(
"."
)
?
originalName
.
substring
(
originalName
.
lastIndexOf
(
'.'
)
+
1
)
:
"jpg"
;
return
ApiResponse
.
success
(
assetService
.
uploadCharacterImage
(
characterId
,
TenantContext
.
get
(),
file
.
getInputStream
(),
file
.
getSize
(),
file
.
getContentType
(),
ext
));
characterId
,
TenantContext
.
get
(),
viewType
,
file
.
getInputStream
(),
file
.
getSize
(),
file
.
getContentType
(),
ext
));
}
@Operation
(
summary
=
"删除角色"
)
...
...
yaoai-comic-studio/yaoai-api/src/main/java/com/yaoai/api/dto/project/ProjectCreateRequest.java
View file @
3b6236dd
...
...
@@ -13,4 +13,10 @@ public class ProjectCreateRequest {
@Size
(
max
=
2000
,
message
=
"描述最长 2000 字符"
)
private
String
description
;
private
String
style
;
private
String
aspectRatio
;
private
String
resolution
;
}
yaoai-comic-studio/yaoai-api/src/main/java/com/yaoai/api/dto/project/ProjectDTO.java
View file @
3b6236dd
...
...
@@ -13,6 +13,8 @@ public class ProjectDTO {
private
String
description
;
private
String
status
;
private
String
style
;
private
String
aspectRatio
;
private
String
resolution
;
private
String
coverUrl
;
private
long
assetCount
;
private
LocalDateTime
createdAt
;
...
...
@@ -25,6 +27,8 @@ public class ProjectDTO {
dto
.
setDescription
(
p
.
getDescription
());
dto
.
setStatus
(
p
.
getStatus
());
dto
.
setStyle
(
p
.
getStyle
());
dto
.
setAspectRatio
(
p
.
getAspectRatio
());
dto
.
setResolution
(
p
.
getResolution
());
dto
.
setCoverUrl
(
coverUrl
);
dto
.
setAssetCount
(
assetCount
);
dto
.
setCreatedAt
(
p
.
getCreatedAt
());
...
...
yaoai-comic-studio/yaoai-api/src/main/java/com/yaoai/api/dto/project/ProjectUpdateRequest.java
View file @
3b6236dd
...
...
@@ -13,4 +13,8 @@ public class ProjectUpdateRequest {
private
String
description
;
private
String
style
;
private
String
aspectRatio
;
private
String
resolution
;
}
yaoai-comic-studio/yaoai-api/src/main/java/com/yaoai/api/service/impl/ProjectServiceImpl.java
View file @
3b6236dd
...
...
@@ -40,6 +40,9 @@ public class ProjectServiceImpl implements ProjectService {
project
.
setName
(
req
.
getName
());
project
.
setDescription
(
req
.
getDescription
());
project
.
setStatus
(
"draft"
);
project
.
setStyle
(
req
.
getStyle
());
project
.
setAspectRatio
(
req
.
getAspectRatio
());
project
.
setResolution
(
req
.
getResolution
());
projectMapper
.
insert
(
project
);
return
toDTO
(
project
);
...
...
@@ -75,6 +78,8 @@ public class ProjectServiceImpl implements ProjectService {
if
(
req
.
getName
()
!=
null
)
project
.
setName
(
req
.
getName
());
if
(
req
.
getDescription
()
!=
null
)
project
.
setDescription
(
req
.
getDescription
());
if
(
req
.
getStyle
()
!=
null
)
project
.
setStyle
(
req
.
getStyle
());
if
(
req
.
getAspectRatio
()
!=
null
)
project
.
setAspectRatio
(
req
.
getAspectRatio
());
if
(
req
.
getResolution
()
!=
null
)
project
.
setResolution
(
req
.
getResolution
());
projectMapper
.
updateById
(
project
);
return
toDTO
(
project
);
...
...
yaoai-comic-studio/yaoai-bootstrap/src/main/java/com/yaoai/YaoAiApplication.java
View file @
3b6236dd
...
...
@@ -12,4 +12,5 @@ public class YaoAiApplication {
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
YaoAiApplication
.
class
,
args
);
}
}
yaoai-comic-studio/yaoai-bootstrap/src/main/resources/db/migration/V11__character_three_views.sql
0 → 100644
View file @
3b6236dd
ALTER
TABLE
characters
ADD
COLUMN
front_image_url
VARCHAR
(
1024
)
NULL
AFTER
image_url
,
ADD
COLUMN
front_image_tos_key
VARCHAR
(
512
)
NULL
AFTER
image_tos_key
,
ADD
COLUMN
side_image_url
VARCHAR
(
1024
)
NULL
AFTER
front_image_url
,
ADD
COLUMN
side_image_tos_key
VARCHAR
(
512
)
NULL
AFTER
front_image_tos_key
,
ADD
COLUMN
back_image_url
VARCHAR
(
1024
)
NULL
AFTER
side_image_url
,
ADD
COLUMN
back_image_tos_key
VARCHAR
(
512
)
NULL
AFTER
side_image_tos_key
;
UPDATE
characters
SET
front_image_url
=
image_url
,
front_image_tos_key
=
image_tos_key
WHERE
image_url
IS
NOT
NULL
AND
(
front_image_url
IS
NULL
OR
front_image_url
=
''
);
yaoai-comic-studio/yaoai-bootstrap/src/main/resources/db/migration/V12__project_display_options.sql
0 → 100644
View file @
3b6236dd
ALTER
TABLE
projects
ADD
COLUMN
aspect_ratio
VARCHAR
(
20
)
DEFAULT
NULL
COMMENT
'画幅比例'
AFTER
style
,
ADD
COLUMN
resolution
VARCHAR
(
20
)
DEFAULT
NULL
COMMENT
'清晰度'
AFTER
aspect_ratio
;
yaoai-comic-studio/yaoai-domain/src/main/java/com/yaoai/domain/entity/Character.java
View file @
3b6236dd
...
...
@@ -28,13 +28,19 @@ public class Character {
private
String
costume
;
private
String
visualHint
;
/**
供 Seedream 文生图使用的英文 Prompt
*/
/**
Base prompt for standardized character image generation.
*/
private
String
imagePrompt
;
/**
Seedream 生成的临时 URL(仅展示)
*/
/**
Keep the primary image for backward compatibility; this points to the front view.
*/
private
String
imageUrl
;
/** 已存入 TOS 的 key */
private
String
imageTosKey
;
private
String
frontImageUrl
;
private
String
frontImageTosKey
;
private
String
sideImageUrl
;
private
String
sideImageTosKey
;
private
String
backImageUrl
;
private
String
backImageTosKey
;
/** draft / generating / ready / failed */
private
String
status
;
...
...
yaoai-comic-studio/yaoai-domain/src/main/java/com/yaoai/domain/entity/Project.java
View file @
3b6236dd
...
...
@@ -27,6 +27,10 @@ public class Project {
private
String
style
;
private
String
aspectRatio
;
private
String
resolution
;
private
String
coverKey
;
private
LocalDateTime
createdAt
;
...
...
yaoai-comic-studio/yaoai-pipeline/src/main/java/com/yaoai/pipeline/service/AssetGenPipelineService.java
View file @
3b6236dd
...
...
@@ -8,16 +8,13 @@ import java.util.List;
public
interface
AssetGenPipelineService
{
/** 根据大纲 + 分集内容,AI 提取角色列表并保存(不含生图) */
List
<
Character
>
extractCharacters
(
Long
projectId
,
Long
tenantId
);
/** 根据大纲 + 分集内容,AI 提取场景列表并保存(不含生图) */
List
<
Scene
>
extractScenes
(
Long
projectId
,
Long
tenantId
);
/**
为指定角色调用 Seedream 生图并存 TOS
*/
/**
Generate three standardized character turnaround views: front, side, and back.
*/
Character
generateCharacterImage
(
Long
characterId
,
Long
tenantId
);
/** 为指定场景调用 Seedream 生图并存 TOS */
Scene
generateSceneImage
(
Long
sceneId
,
Long
tenantId
);
List
<
Character
>
listCharacters
(
Long
projectId
,
Long
tenantId
);
...
...
@@ -32,9 +29,8 @@ public interface AssetGenPipelineService {
void
deleteScene
(
Long
id
,
Long
tenantId
);
/** 上传用户自定义角色图,存 TOS 并更新 imageUrl */
Character
uploadCharacterImage
(
Long
characterId
,
Long
tenantId
,
InputStream
stream
,
long
size
,
String
contentType
,
String
ext
);
Character
uploadCharacterImage
(
Long
characterId
,
Long
tenantId
,
String
viewType
,
InputStream
stream
,
long
size
,
String
contentType
,
String
ext
);
/** 上传用户自定义场景图,存 TOS 并更新 imageUrl */
Scene
uploadSceneImage
(
Long
sceneId
,
Long
tenantId
,
InputStream
stream
,
long
size
,
String
contentType
,
String
ext
);
}
yaoai-comic-studio/yaoai-pipeline/src/main/java/com/yaoai/pipeline/service/impl/AssetGenPipelineServiceImpl.java
View file @
3b6236dd
...
...
@@ -11,13 +11,14 @@ import com.yaoai.common.exception.ErrorCode;
import
com.yaoai.domain.entity.Character
;
import
com.yaoai.domain.entity.Episode
;
import
com.yaoai.domain.entity.Outline
;
import
com.yaoai.domain.entity.Project
;
import
com.yaoai.domain.entity.Scene
;
import
com.yaoai.domain.mapper.CharacterMapper
;
import
com.yaoai.domain.mapper.EpisodeMapper
;
import
com.yaoai.domain.mapper.OutlineMapper
;
import
com.yaoai.domain.mapper.ProjectMapper
;
import
com.yaoai.domain.mapper.SceneMapper
;
import
com.yaoai.pipeline.service.AssetGenPipelineService
;
import
com.yaoai.pipeline.service.ImageGenPipelineService
;
import
com.yaoai.storage.service.TosService
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -31,58 +32,63 @@ import java.net.http.HttpRequest;
import
java.net.http.HttpResponse
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Map
;
import
java.util.concurrent.CompletableFuture
;
@Slf4j
@Service
@RequiredArgsConstructor
public
class
AssetGenPipelineServiceImpl
implements
AssetGenPipelineService
{
private
final
LlmService
llmService
;
private
final
SeedreamService
seedreamService
;
private
final
TosService
tosService
;
private
final
CharacterMapper
characterMapper
;
private
final
SceneMapper
sceneMapper
;
private
final
OutlineMapper
outlineMapper
;
private
final
EpisodeMapper
episodeMapper
;
private
final
ObjectMapper
objectMapper
;
private
final
HttpClient
httpClient
=
HttpClient
.
newHttpClient
();
private
static
final
String
CHARACTER_SYSTEM
=
"""
你是影视
剧角色设定专家。根据剧情大纲和分集内容,提取主要角色并生成结构化信息
。
请以 JSON 数组返回,格式
:
你是影视
短剧角色设定专家。请根据给定的大纲和分集信息,提取主要角色并输出 JSON 数组
。
返回格式如下
:
[
{
"
name
": "
角色姓名
",
"
role_type
": "
角色类型(女主
/
男主
/
反派
/
配角)
",
"
gender
": "
male
或
female
",
"
age
": "
年龄描述
(如:
18
岁
/
30
多岁)
",
"
personality
": "
性格特
点(
50
字以内)
",
"
costume
": "
主要服装描述
(
50
字以内)
",
"
visual_hint
": "
外貌特征
(
50
字以内)
",
"
image_prompt
": "
英文
文生图
Prompt
(
40
词以内,描述角色正面全身,适合
2
D
anime
或现实风格)
"
"
role_type
": "
female_lead
/
male_lead
/
antagonist
/
supporting
",
"
gender
": "
male
or
female
",
"
age
": "
年龄描述
",
"
personality
": "
性格特
征,
30
字以内
",
"
costume
": "
主要服装描述
,
30
字以内
",
"
visual_hint
": "
外貌特征
,
30
字以内
",
"
image_prompt
": "
英文
角色设定图
prompt
,突出服装、发型、体型、气质,用于统一角色三视图生成
"
}
]
只返回
主要角色(3~8个),只返回 JSON 数组
。
只返回
3 到 8 个主要角色,只返回 JSON
。
"""
;
private
static
final
String
SCENE_SYSTEM
=
"""
你是影视
剧场景设计专家。根据剧情大纲和分集内容,提取主要场景并生成结构化信息
。
请以 JSON 数组返回,格式
:
你是影视
短剧场景设定专家。请根据给定的大纲和分集信息,提取主要场景并输出 JSON 数组
。
返回格式如下
:
[
{
"
name
": "
场景名称
",
"
scene_type
": "
indoor
或
outdoor
",
"
description
": "
场景描述
(
50
字以内)
",
"
image_prompt
": "
英文
文生图
Prompt
(
40
词以内,描述场景环境,电影级质感)
"
"
scene_type
": "
indoor
or
outdoor
",
"
description
": "
场景描述
,
30
字以内
",
"
image_prompt
": "
英文
场景图
prompt
,电影感环境描述
"
}
]
只返回
主要场景(3~6个),只返回 JSON 数组
。
只返回
3 到 6 个主要场景,只返回 JSON
。
"""
;
private
final
LlmService
llmService
;
private
final
SeedreamService
seedreamService
;
private
final
TosService
tosService
;
private
final
CharacterMapper
characterMapper
;
private
final
SceneMapper
sceneMapper
;
private
final
OutlineMapper
outlineMapper
;
private
final
EpisodeMapper
episodeMapper
;
private
final
ProjectMapper
projectMapper
;
private
final
ObjectMapper
objectMapper
;
private
final
HttpClient
httpClient
=
HttpClient
.
newHttpClient
();
@Override
public
List
<
Character
>
extractCharacters
(
Long
projectId
,
Long
tenantId
)
{
String
context
=
buildProjectContext
(
projectId
,
tenantId
);
Project
project
=
loadProject
(
projectId
,
tenantId
);
ProjectVisualStyle
visualStyle
=
resolveProjectVisualStyle
(
project
);
String
context
=
buildCharacterExtractionContext
(
projectId
,
tenantId
,
visualStyle
);
log
.
info
(
"Extracting characters: projectId={}"
,
projectId
);
String
raw
=
llmService
.
chat
(
ChatRequest
.
builder
()
...
...
@@ -92,23 +98,22 @@ public class AssetGenPipelineServiceImpl implements AssetGenPipelineService {
try
{
List
<
Map
<
String
,
Object
>>
items
=
objectMapper
.
readValue
(
extractJson
(
raw
),
new
TypeReference
<>()
{});
List
<
Character
>
result
=
new
ArrayList
<>();
for
(
Map
<
String
,
Object
>
m
:
items
)
{
Character
c
=
new
Character
();
c
.
setProjectId
(
projectId
);
c
.
setTenantId
(
tenantId
);
c
.
setName
((
String
)
m
.
getOrDefault
(
"name"
,
""
));
c
.
setRoleType
((
String
)
m
.
getOrDefault
(
"role_type"
,
""
));
c
.
setGender
((
String
)
m
.
getOrDefault
(
"gender"
,
""
));
c
.
setAge
((
String
)
m
.
getOrDefault
(
"age"
,
""
));
c
.
setPersonality
((
String
)
m
.
getOrDefault
(
"personality"
,
""
));
c
.
setCostume
((
String
)
m
.
getOrDefault
(
"costume"
,
""
));
c
.
setVisualHint
((
String
)
m
.
getOrDefault
(
"visual_hint"
,
""
));
c
.
setImagePrompt
((
String
)
m
.
getOrDefault
(
"image_prompt"
,
""
));
c
.
setStatus
(
"draft"
);
characterMapper
.
insert
(
c
);
result
.
add
(
c
);
for
(
Map
<
String
,
Object
>
ite
m
:
items
)
{
Character
c
haracter
=
new
Character
();
c
haracter
.
setProjectId
(
projectId
);
c
haracter
.
setTenantId
(
tenantId
);
c
haracter
.
setName
((
String
)
ite
m
.
getOrDefault
(
"name"
,
""
));
c
haracter
.
setRoleType
((
String
)
ite
m
.
getOrDefault
(
"role_type"
,
""
));
c
haracter
.
setGender
((
String
)
ite
m
.
getOrDefault
(
"gender"
,
""
));
c
haracter
.
setAge
((
String
)
ite
m
.
getOrDefault
(
"age"
,
""
));
c
haracter
.
setPersonality
((
String
)
ite
m
.
getOrDefault
(
"personality"
,
""
));
c
haracter
.
setCostume
((
String
)
ite
m
.
getOrDefault
(
"costume"
,
""
));
c
haracter
.
setVisualHint
((
String
)
ite
m
.
getOrDefault
(
"visual_hint"
,
""
));
c
haracter
.
setImagePrompt
((
String
)
ite
m
.
getOrDefault
(
"image_prompt"
,
""
));
c
haracter
.
setStatus
(
"draft"
);
characterMapper
.
insert
(
c
haracter
);
result
.
add
(
c
haracter
);
}
log
.
info
(
"Characters extracted: count={}"
,
result
.
size
());
return
result
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Character extraction failed"
,
e
);
...
...
@@ -128,19 +133,18 @@ public class AssetGenPipelineServiceImpl implements AssetGenPipelineService {
try
{
List
<
Map
<
String
,
Object
>>
items
=
objectMapper
.
readValue
(
extractJson
(
raw
),
new
TypeReference
<>()
{});
List
<
Scene
>
result
=
new
ArrayList
<>();
for
(
Map
<
String
,
Object
>
m
:
items
)
{
Scene
s
=
new
Scene
();
s
.
setProjectId
(
projectId
);
s
.
setTenantId
(
tenantId
);
s
.
setName
((
String
)
m
.
getOrDefault
(
"name"
,
""
));
s
.
setSceneType
((
String
)
m
.
getOrDefault
(
"scene_type"
,
"indoor"
));
s
.
setDescription
((
String
)
m
.
getOrDefault
(
"description"
,
""
));
s
.
setImagePrompt
((
String
)
m
.
getOrDefault
(
"image_prompt"
,
""
));
s
.
setStatus
(
"draft"
);
sceneMapper
.
insert
(
s
);
result
.
add
(
s
);
for
(
Map
<
String
,
Object
>
ite
m
:
items
)
{
Scene
s
cene
=
new
Scene
();
s
cene
.
setProjectId
(
projectId
);
s
cene
.
setTenantId
(
tenantId
);
s
cene
.
setName
((
String
)
ite
m
.
getOrDefault
(
"name"
,
""
));
s
cene
.
setSceneType
((
String
)
ite
m
.
getOrDefault
(
"scene_type"
,
"indoor"
));
s
cene
.
setDescription
((
String
)
ite
m
.
getOrDefault
(
"description"
,
""
));
s
cene
.
setImagePrompt
((
String
)
ite
m
.
getOrDefault
(
"image_prompt"
,
""
));
s
cene
.
setStatus
(
"draft"
);
sceneMapper
.
insert
(
s
cene
);
result
.
add
(
s
cene
);
}
log
.
info
(
"Scenes extracted: count={}"
,
result
.
size
());
return
result
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Scene extraction failed"
,
e
);
...
...
@@ -150,72 +154,75 @@ public class AssetGenPipelineServiceImpl implements AssetGenPipelineService {
@Override
public
Character
generateCharacterImage
(
Long
characterId
,
Long
tenantId
)
{
Character
c
=
characterMapper
.
selectById
(
characterId
);
if
(
c
==
null
||
!
tenantId
.
equals
(
c
.
getTenantId
()))
{
throw
new
BizException
(
ErrorCode
.
NOT_FOUND
,
"角色不存在"
);
}
if
(
c
.
getImagePrompt
()
==
null
||
c
.
getImagePrompt
().
isBlank
())
{
Character
character
=
requireCharacter
(
characterId
,
tenantId
);
Project
project
=
loadProject
(
character
.
getProjectId
(),
tenantId
);
ProjectVisualStyle
visualStyle
=
resolveProjectVisualStyle
(
project
);
if
(
character
.
getImagePrompt
()
==
null
||
character
.
getImagePrompt
().
isBlank
())
{
throw
new
BizException
(
ErrorCode
.
INVALID_PARAM
,
"角色缺少图片 Prompt"
);
}
c
.
setStatus
(
"generating"
);
characterMapper
.
updateById
(
c
);
c
haracter
.
setStatus
(
"generating"
);
characterMapper
.
updateById
(
c
haracter
);
try
{
String
imageUrl
=
seedreamService
.
generateImage
(
c
.
getImagePrompt
());
byte
[]
bytes
=
downloadBytes
(
imageUrl
);
String
key
=
TosService
.
buildKey
(
c
.
getTenantId
(),
c
.
getProjectId
(),
"characters"
,
c
.
getName
()
+
".jpg"
);
tosService
.
upload
(
key
,
new
ByteArrayInputStream
(
bytes
),
bytes
.
length
,
"image/jpeg"
);
CompletableFuture
<
CharacterViewAsset
>
frontFuture
=
CompletableFuture
.
supplyAsync
(()
->
generateCharacterViewAsset
(
character
,
visualStyle
,
"front"
));
CompletableFuture
<
CharacterViewAsset
>
sideFuture
=
CompletableFuture
.
supplyAsync
(()
->
generateCharacterViewAsset
(
character
,
visualStyle
,
"side"
));
CompletableFuture
<
CharacterViewAsset
>
backFuture
=
CompletableFuture
.
supplyAsync
(()
->
generateCharacterViewAsset
(
character
,
visualStyle
,
"back"
));
c
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
c
.
setImageTosKey
(
key
);
c
.
setStatus
(
"ready"
);
characterMapper
.
updateById
(
c
);
log
.
info
(
"Character image generated: id={}, key={}"
,
characterId
,
key
);
return
c
;
CompletableFuture
.
allOf
(
frontFuture
,
sideFuture
,
backFuture
).
join
();
applyCharacterView
(
character
,
frontFuture
.
join
().
viewType
(),
frontFuture
.
join
().
key
());
applyCharacterView
(
character
,
sideFuture
.
join
().
viewType
(),
sideFuture
.
join
().
key
());
applyCharacterView
(
character
,
backFuture
.
join
().
viewType
(),
backFuture
.
join
().
key
());
character
.
setStatus
(
"ready"
);
characterMapper
.
updateById
(
character
);
return
character
;
}
catch
(
BizException
e
)
{
c
.
setStatus
(
"failed"
);
characterMapper
.
updateById
(
c
);
c
haracter
.
setStatus
(
"failed"
);
characterMapper
.
updateById
(
c
haracter
);
throw
e
;
}
catch
(
Exception
e
)
{
c
.
setStatus
(
"failed"
);
characterMapper
.
updateById
(
c
);
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"角色
图片
生成失败: "
+
e
.
getMessage
());
c
haracter
.
setStatus
(
"failed"
);
characterMapper
.
updateById
(
c
haracter
);
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"角色
三视图
生成失败: "
+
e
.
getMessage
());
}
}
@Override
public
Scene
generateSceneImage
(
Long
sceneId
,
Long
tenantId
)
{
Scene
s
=
sceneMapper
.
selectById
(
sceneId
);
if
(
s
==
null
||
!
tenantId
.
equals
(
s
.
getTenantId
()))
{
Scene
s
cene
=
sceneMapper
.
selectById
(
sceneId
);
if
(
s
cene
==
null
||
!
tenantId
.
equals
(
scene
.
getTenantId
()))
{
throw
new
BizException
(
ErrorCode
.
NOT_FOUND
,
"场景不存在"
);
}
if
(
s
.
getImagePrompt
()
==
null
||
s
.
getImagePrompt
().
isBlank
())
{
if
(
s
cene
.
getImagePrompt
()
==
null
||
scene
.
getImagePrompt
().
isBlank
())
{
throw
new
BizException
(
ErrorCode
.
INVALID_PARAM
,
"场景缺少图片 Prompt"
);
}
s
.
setStatus
(
"generating"
);
sceneMapper
.
updateById
(
s
);
s
cene
.
setStatus
(
"generating"
);
sceneMapper
.
updateById
(
s
cene
);
try
{
String
imageUrl
=
seedreamService
.
generateImage
(
s
.
getImagePrompt
());
String
imageUrl
=
seedreamService
.
generateImage
(
s
cene
.
getImagePrompt
());
byte
[]
bytes
=
downloadBytes
(
imageUrl
);
String
key
=
TosService
.
buildKey
(
s
.
getTenantId
(),
s
.
getProjectId
(),
"scenes"
,
s
.
getName
()
+
".jpg"
);
String
key
=
TosService
.
buildKey
(
s
cene
.
getTenantId
(),
scene
.
getProjectId
(),
"scenes"
,
scene
.
getName
()
+
".jpg"
);
tosService
.
upload
(
key
,
new
ByteArrayInputStream
(
bytes
),
bytes
.
length
,
"image/jpeg"
);
s
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
s
.
setImageTosKey
(
key
);
s
.
setStatus
(
"ready"
);
sceneMapper
.
updateById
(
s
);
log
.
info
(
"Scene image generated: id={}, key={}"
,
sceneId
,
key
);
return
s
;
scene
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
scene
.
setImageTosKey
(
key
);
scene
.
setStatus
(
"ready"
);
sceneMapper
.
updateById
(
scene
);
return
scene
;
}
catch
(
BizException
e
)
{
s
.
setStatus
(
"failed"
);
sceneMapper
.
updateById
(
s
);
s
cene
.
setStatus
(
"failed"
);
sceneMapper
.
updateById
(
s
cene
);
throw
e
;
}
catch
(
Exception
e
)
{
s
.
setStatus
(
"failed"
);
sceneMapper
.
updateById
(
s
);
s
cene
.
setStatus
(
"failed"
);
sceneMapper
.
updateById
(
s
cene
);
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"场景图片生成失败: "
+
e
.
getMessage
());
}
}
...
...
@@ -252,80 +259,393 @@ public class AssetGenPipelineServiceImpl implements AssetGenPipelineService {
@Override
public
void
deleteCharacter
(
Long
id
,
Long
tenantId
)
{
Character
c
=
characterMapper
.
selectById
(
id
);
if
(
c
!=
null
&&
tenantId
.
equals
(
c
.
getTenantId
()))
characterMapper
.
deleteById
(
id
);
Character
character
=
characterMapper
.
selectById
(
id
);
if
(
character
!=
null
&&
tenantId
.
equals
(
character
.
getTenantId
()))
{
characterMapper
.
deleteById
(
id
);
}
}
@Override
public
void
deleteScene
(
Long
id
,
Long
tenantId
)
{
Scene
s
=
sceneMapper
.
selectById
(
id
);
if
(
s
!=
null
&&
tenantId
.
equals
(
s
.
getTenantId
()))
sceneMapper
.
deleteById
(
id
);
Scene
scene
=
sceneMapper
.
selectById
(
id
);
if
(
scene
!=
null
&&
tenantId
.
equals
(
scene
.
getTenantId
()))
{
sceneMapper
.
deleteById
(
scene
);
}
}
@Override
public
Character
uploadCharacterImage
(
Long
characterId
,
Long
tenantId
,
public
Character
uploadCharacterImage
(
Long
characterId
,
Long
tenantId
,
String
viewType
,
InputStream
stream
,
long
size
,
String
contentType
,
String
ext
)
{
Character
c
=
characterMapper
.
selectById
(
character
Id
);
if
(
c
==
null
||
!
tenantId
.
equals
(
c
.
getTenantId
()))
throw
new
RuntimeException
(
"角色不存在或无权限"
)
;
String
key
=
"projects/"
+
c
.
getProjectId
()
+
"/characters/"
+
characterId
+
"/image."
+
e
xt
;
Character
c
haracter
=
requireCharacter
(
characterId
,
tenant
Id
);
String
normalizedViewType
=
normalizeCharacterViewType
(
viewType
);
String
safeExt
=
(
ext
==
null
||
ext
.
isBlank
())
?
"jpg"
:
ext
;
String
key
=
"projects/"
+
c
haracter
.
getProjectId
()
+
"/characters/"
+
characterId
+
"/"
+
normalizedViewType
+
"."
+
safeE
xt
;
tosService
.
upload
(
key
,
stream
,
size
,
contentType
);
c
.
setImageTosKey
(
key
);
c
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
c
.
setStatus
(
"ready"
);
characterMapper
.
updateById
(
c
);
return
c
;
applyCharacterView
(
character
,
normalizedViewType
,
key
);
character
.
setStatus
(
"ready"
);
characterMapper
.
updateById
(
character
);
return
character
;
}
@Override
public
Scene
uploadSceneImage
(
Long
sceneId
,
Long
tenantId
,
InputStream
stream
,
long
size
,
String
contentType
,
String
ext
)
{
Scene
s
=
sceneMapper
.
selectById
(
sceneId
);
if
(
s
==
null
||
!
tenantId
.
equals
(
s
.
getTenantId
()))
Scene
s
cene
=
sceneMapper
.
selectById
(
sceneId
);
if
(
s
cene
==
null
||
!
tenantId
.
equals
(
scene
.
getTenantId
()))
{
throw
new
RuntimeException
(
"场景不存在或无权限"
);
String
key
=
"projects/"
+
s
.
getProjectId
()
+
"/scenes/"
+
sceneId
+
"/image."
+
ext
;
}
String
safeExt
=
(
ext
==
null
||
ext
.
isBlank
())
?
"jpg"
:
ext
;
String
key
=
"projects/"
+
scene
.
getProjectId
()
+
"/scenes/"
+
sceneId
+
"/image."
+
safeExt
;
tosService
.
upload
(
key
,
stream
,
size
,
contentType
);
s
.
setImageTosKey
(
key
);
s
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
s
.
setStatus
(
"ready"
);
sceneMapper
.
updateById
(
s
);
return
s
;
s
cene
.
setImageTosKey
(
key
);
s
cene
.
setImageUrl
(
tosService
.
publicUrl
(
key
));
s
cene
.
setStatus
(
"ready"
);
sceneMapper
.
updateById
(
s
cene
);
return
s
cene
;
}
// ---- helpers ----
private
Character
requireCharacter
(
Long
characterId
,
Long
tenantId
)
{
Character
character
=
characterMapper
.
selectById
(
characterId
);
if
(
character
==
null
||
!
tenantId
.
equals
(
character
.
getTenantId
()))
{
throw
new
BizException
(
ErrorCode
.
NOT_FOUND
,
"角色不存在"
);
}
return
character
;
}
private
CharacterViewAsset
generateCharacterViewAsset
(
Character
character
,
ProjectVisualStyle
visualStyle
,
String
viewType
)
{
try
{
String
prompt
=
buildCharacterViewPrompt
(
character
.
getImagePrompt
(),
visualStyle
,
viewType
);
String
generatedUrl
=
seedreamService
.
generateImage
(
prompt
);
byte
[]
bytes
=
downloadBytes
(
generatedUrl
);
String
key
=
TosService
.
buildKey
(
character
.
getTenantId
(),
character
.
getProjectId
(),
"characters/"
+
character
.
getId
(),
viewType
+
".jpg"
);
tosService
.
upload
(
key
,
new
ByteArrayInputStream
(
bytes
),
bytes
.
length
,
"image/jpeg"
);
return
new
CharacterViewAsset
(
viewType
,
key
);
}
catch
(
BizException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"角色"
+
viewType
+
"视图生成失败: "
+
e
.
getMessage
());
}
}
private
void
applyCharacterView
(
Character
character
,
String
viewType
,
String
key
)
{
String
publicUrl
=
tosService
.
publicUrl
(
key
);
switch
(
normalizeCharacterViewType
(
viewType
))
{
case
"front"
->
{
character
.
setImageUrl
(
publicUrl
);
character
.
setImageTosKey
(
key
);
character
.
setFrontImageUrl
(
publicUrl
);
character
.
setFrontImageTosKey
(
key
);
}
case
"side"
->
{
character
.
setSideImageUrl
(
publicUrl
);
character
.
setSideImageTosKey
(
key
);
}
case
"back"
->
{
character
.
setBackImageUrl
(
publicUrl
);
character
.
setBackImageTosKey
(
key
);
}
default
->
throw
new
BizException
(
ErrorCode
.
INVALID_PARAM
,
"不支持的角色视图: "
+
viewType
);
}
}
private
String
buildCharacterViewPrompt
(
String
basePrompt
,
ProjectVisualStyle
visualStyle
,
String
viewType
)
{
String
angleInstruction
=
switch
(
normalizeCharacterViewType
(
viewType
))
{
case
"front"
->
"front view, full body, facing camera"
;
case
"side"
->
"left side profile view, full body"
;
case
"back"
->
"back view, full body, facing away"
;
default
->
throw
new
BizException
(
ErrorCode
.
INVALID_PARAM
,
"不支持的角色视图: "
+
viewType
);
};
List
<
String
>
promptParts
=
new
ArrayList
<>();
String
normalizedPrompt
=
basePrompt
==
null
?
""
:
basePrompt
.
trim
();
if
(!
normalizedPrompt
.
isBlank
())
{
promptParts
.
add
(
normalizedPrompt
);
}
promptParts
.
add
(
visualStyle
.
renderGuidance
());
promptParts
.
addAll
(
buildStyleSpecificPromptParts
(
visualStyle
));
promptParts
.
add
(
"neutral standing pose"
);
promptParts
.
add
(
"consistent costume and hairstyle across all views"
);
promptParts
.
add
(
"clean plain studio background"
);
promptParts
.
add
(
angleInstruction
);
promptParts
.
add
(
"single character"
);
promptParts
.
add
(
"no extra people"
);
promptParts
.
add
(
"no collage"
);
promptParts
.
add
(
"no split panel"
);
promptParts
.
add
(
"no props blocking the body"
);
promptParts
.
addAll
(
buildStyleNegativePromptParts
(
visualStyle
));
return
String
.
join
(
", "
,
promptParts
);
}
private
String
normalizeCharacterViewType
(
String
viewType
)
{
if
(
viewType
==
null
||
viewType
.
isBlank
())
{
return
"front"
;
}
String
normalized
=
viewType
.
toLowerCase
(
Locale
.
ROOT
);
return
switch
(
normalized
)
{
case
"front"
,
"side"
,
"back"
->
normalized
;
default
->
throw
new
BizException
(
ErrorCode
.
INVALID_PARAM
,
"不支持的角色视图: "
+
viewType
);
};
}
private
String
buildProjectContext
(
Long
projectId
,
Long
tenantId
)
{
Outline
outline
=
outlineMapper
.
findLatestByProject
(
projectId
,
tenantId
);
List
<
Episode
>
episodes
=
episodeMapper
.
findByProject
(
projectId
,
tenantId
);
StringBuilder
sb
=
new
StringBuilder
();
StringBuilder
builder
=
new
StringBuilder
();
if
(
outline
!=
null
)
{
sb
.
append
(
"剧名:"
).
append
(
outline
.
getTitle
()).
append
(
"\n"
);
sb
.
append
(
"类型:"
).
append
(
outline
.
getGenre
()).
append
(
"\n"
);
sb
.
append
(
"故事梗概:"
).
append
(
outline
.
getSynopsis
()).
append
(
"\n\n"
);
builder
.
append
(
"剧名:"
).
append
(
outline
.
getTitle
()).
append
(
'\n'
);
builder
.
append
(
"类型:"
).
append
(
outline
.
getGenre
()).
append
(
'\n'
);
builder
.
append
(
"故事梗概:"
).
append
(
outline
.
getSynopsis
()).
append
(
"\n\n"
);
}
builder
.
append
(
"分集摘要:\n"
);
for
(
Episode
episode
:
episodes
)
{
builder
.
append
(
"第"
)
.
append
(
episode
.
getEpisodeNumber
())
.
append
(
"集《"
)
.
append
(
episode
.
getTitle
())
.
append
(
"》:"
)
.
append
(
episode
.
getSummary
())
.
append
(
'\n'
);
}
return
builder
.
toString
();
}
private
String
buildCharacterExtractionContext
(
Long
projectId
,
Long
tenantId
,
ProjectVisualStyle
visualStyle
)
{
StringBuilder
builder
=
new
StringBuilder
(
buildProjectContext
(
projectId
,
tenantId
));
if
(
builder
.
length
()
>
0
)
{
builder
.
append
(
"\n\n"
);
}
sb
.
append
(
"分集摘要:\n"
);
for
(
Episode
ep
:
episodes
)
{
sb
.
append
(
"第"
).
append
(
ep
.
getEpisodeNumber
()).
append
(
"集 《"
).
append
(
ep
.
getTitle
()).
append
(
"》:"
);
sb
.
append
(
ep
.
getSummary
()).
append
(
"\n"
);
builder
.
append
(
"Character visual style requirements:\n"
);
builder
.
append
(
"- Selected project style: "
).
append
(
visualStyle
.
label
()).
append
(
'\n'
);
builder
.
append
(
"- Style direction: "
).
append
(
visualStyle
.
extractionGuidance
()).
append
(
'\n'
);
builder
.
append
(
"- The image_prompt must be suitable for generating a consistent front/side/back turnaround sheet for the same person in the selected visual style.\n"
);
builder
.
append
(
"- Output target: "
).
append
(
buildExtractionTargetRule
(
visualStyle
)).
append
(
'\n'
);
builder
.
append
(
"- Avoid: "
).
append
(
buildExtractionAvoidRule
(
visualStyle
)).
append
(
'\n'
);
builder
.
append
(
"- Additional style notes: "
).
append
(
visualStyle
.
extractionGuidance
()).
append
(
'\n'
);
return
builder
.
toString
();
}
private
Project
loadProject
(
Long
projectId
,
Long
tenantId
)
{
return
projectMapper
.
findActiveById
(
projectId
,
tenantId
).
orElse
(
null
);
}
private
ProjectVisualStyle
resolveProjectVisualStyle
(
Project
project
)
{
if
(
project
==
null
||
project
.
getStyle
()
==
null
||
project
.
getStyle
().
isBlank
())
{
return
new
ProjectVisualStyle
(
"default"
,
"真人短剧视觉"
,
"grounded live-action short drama characters with realistic styling"
,
"grounded live-action short drama aesthetic, cinematic realism, realistic wardrobe"
);
}
return
sb
.
toString
();
String
slug
=
project
.
getStyle
().
trim
().
toLowerCase
(
Locale
.
ROOT
);
return
switch
(
slug
)
{
case
"live_action_drama"
->
new
ProjectVisualStyle
(
slug
,
"真人短剧视觉"
,
"live-action short drama styling with realistic actors, believable wardrobe, and production-ready character design"
,
"live-action short drama aesthetic, realistic actors, cinematic realism, believable wardrobe details"
);
case
"japanese_anime"
->
new
ProjectVisualStyle
(
slug
,
"2D日漫"
,
"2D Japanese anime styling with clean line art, cel shading, expressive silhouettes, and strong character readability"
,
"2D Japanese anime aesthetic, clean line art, cel shading, expressive character design, polished turnaround sheet"
);
case
"korean_webtoon"
->
new
ProjectVisualStyle
(
slug
,
"2D韩漫都市"
,
"Korean webtoon urban styling with fashionable silhouettes, soft shading, and modern romance or city-drama appeal"
,
"2D Korean webtoon aesthetic, modern urban fashion, clean line art, soft shading, stylish character sheet"
);
case
"chinese_anime"
->
new
ProjectVisualStyle
(
slug
,
"2D国漫"
,
"2D Chinese animation styling with strong silhouette design, refined costume details, and contemporary guoman appeal"
,
"2D Chinese animation aesthetic, clean line art, refined costume design, guoman character sheet"
);
case
"chinese_fantasy_3d"
->
new
ProjectVisualStyle
(
slug
,
"3D国风仙侠"
,
"stylized 3D Chinese fantasy styling with layered costume details, elegant accessories, and xianxia or guofeng presentation"
,
"3D Chinese fantasy aesthetic, stylized character render, ornate costume layers, xianxia guofeng hero design"
);
case
"cg_cinematic"
->
new
ProjectVisualStyle
(
slug
,
"CG电影感"
,
"high-end CGI cinematic styling with hero-asset presentation, premium materials, and polished production design"
,
"cinematic CGI character design, premium materials, high-end render quality, hero asset presentation"
);
case
"urban"
->
new
ProjectVisualStyle
(
slug
,
"Urban emotional drama"
,
"modern city setting, fashionable contemporary wardrobe, restrained makeup, grounded live-action drama references"
,
"modern urban live-action drama aesthetic, contemporary Chinese wardrobe, cinematic realism, grounded emotional tone"
);
case
"fantasy"
->
new
ProjectVisualStyle
(
slug
,
"Fantasy xianxia drama"
,
"fantasy or xianxia worldbuilding, layered costume details, elegant accessories, but still designed as live-action costume drama characters"
,
"Chinese fantasy xianxia live-action costume drama aesthetic, refined accessories, layered fabrics, cinematic realism"
);
case
"romance"
->
new
ProjectVisualStyle
(
slug
,
"Romance drama"
,
"attractive but believable real-world styling, polished wardrobe, intimate emotional tone, live-action drama reference"
,
"contemporary live-action romance drama aesthetic, polished styling, soft cinematic lighting, realistic people"
);
case
"historical"
->
new
ProjectVisualStyle
(
slug
,
"Historical costume drama"
,
"historical Chinese costume drama styling, era-appropriate silhouette and accessories, realistic live-action wardrobe construction"
,
"Chinese historical live-action costume drama aesthetic, period wardrobe, elegant materials, cinematic realism"
);
case
"suspense"
->
new
ProjectVisualStyle
(
slug
,
"Suspense thriller"
,
"grounded thriller styling, practical wardrobe, realistic tension, understated colors, live-action reference"
,
"grounded suspense thriller live-action aesthetic, restrained palette, realistic wardrobe, cinematic realism"
);
case
"comedy"
->
new
ProjectVisualStyle
(
slug
,
"Comedy drama"
,
"bright and expressive but still realistic live-action styling, contemporary wardrobe, approachable character design"
,
"bright live-action comedy-drama aesthetic, expressive styling, realistic people, clean contemporary wardrobe"
);
default
->
new
ProjectVisualStyle
(
slug
,
"Custom style: "
+
slug
,
"match the selected project style while keeping the result visually coherent and suitable for character turnaround generation"
,
"match the selected project style `"
+
slug
+
"` with a clear, consistent character-design presentation"
);
};
}
private
List
<
String
>
buildStyleSpecificPromptParts
(
ProjectVisualStyle
visualStyle
)
{
return
switch
(
visualStyle
.
slug
())
{
case
"japanese_anime"
->
List
.
of
(
"2D Japanese anime character turnaround sheet"
,
"clean line art"
,
"cel shading"
,
"anime-style color blocking"
);
case
"korean_webtoon"
->
List
.
of
(
"2D Korean webtoon character turnaround sheet"
,
"clean line art"
,
"soft webtoon shading"
,
"modern urban fashion styling"
);
case
"chinese_anime"
->
List
.
of
(
"2D Chinese animation character turnaround sheet"
,
"clean line art"
,
"stylized guoman character design"
,
"refined costume detailing"
);
case
"chinese_fantasy_3d"
->
List
.
of
(
"3D Chinese fantasy character turnaround sheet"
,
"stylized 3D render"
,
"ornate costume layers"
,
"xianxia-inspired accessories"
);
case
"cg_cinematic"
->
List
.
of
(
"cinematic CGI character turnaround sheet"
,
"high-end CGI rendering"
,
"hero asset presentation"
,
"premium material definition"
);
default
->
List
.
of
(
"live-action TV drama character turnaround sheet"
,
"production-ready costume design reference"
,
"realistic human anatomy and natural skin texture"
);
};
}
private
List
<
String
>
buildStyleNegativePromptParts
(
ProjectVisualStyle
visualStyle
)
{
return
switch
(
visualStyle
.
slug
())
{
case
"japanese_anime"
,
"korean_webtoon"
,
"chinese_anime"
->
List
.
of
(
"not live action"
,
"not photorealistic skin texture"
,
"not 3D render"
,
"not photo"
);
case
"chinese_fantasy_3d"
,
"cg_cinematic"
->
List
.
of
(
"not live action photo"
,
"not flat 2D illustration"
,
"not manga panel"
,
"not collage"
);
default
->
List
.
of
(
"not anime"
,
"not manga"
,
"not cartoon"
,
"not cel shaded"
,
"not illustration"
);
};
}
private
String
buildExtractionTargetRule
(
ProjectVisualStyle
visualStyle
)
{
return
switch
(
visualStyle
.
slug
())
{
case
"japanese_anime"
->
"a 2D Japanese anime turnaround sheet with front, side, and back consistency"
;
case
"korean_webtoon"
->
"a 2D Korean webtoon turnaround sheet with modern urban styling and clear silhouette consistency"
;
case
"chinese_anime"
->
"a 2D guoman turnaround sheet with refined costume details and consistent proportions"
;
case
"chinese_fantasy_3d"
->
"a 3D Chinese fantasy turnaround sheet with layered costume construction and clean silhouette readability"
;
case
"cg_cinematic"
->
"a cinematic CGI turnaround sheet with premium material definition and hero-asset clarity"
;
default
->
"a live-action short drama turnaround sheet with realistic wardrobe, hair, and body proportions"
;
};
}
private
String
buildExtractionAvoidRule
(
ProjectVisualStyle
visualStyle
)
{
return
switch
(
visualStyle
.
slug
())
{
case
"japanese_anime"
,
"korean_webtoon"
,
"chinese_anime"
->
"live-action photography, photorealistic actor language, or realistic skin-texture wording"
;
case
"chinese_fantasy_3d"
,
"cg_cinematic"
->
"flat 2D illustration wording, manga panel composition, or live-action actor-photo language"
;
default
->
"anime, manga, cartoon, cel-shaded, or illustrated wording unless the selected style explicitly requires it"
;
};
}
private
byte
[]
downloadBytes
(
String
url
)
throws
Exception
{
HttpRequest
req
=
HttpRequest
.
newBuilder
().
uri
(
URI
.
create
(
url
)).
GET
().
build
();
HttpResponse
<
byte
[]>
resp
=
httpClient
.
send
(
req
,
HttpResponse
.
BodyHandlers
.
ofByteArray
());
if
(
resp
.
statusCode
()
!=
200
)
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"下载图片失败 HTTP "
+
resp
.
statusCode
());
return
resp
.
body
();
HttpRequest
request
=
HttpRequest
.
newBuilder
().
uri
(
URI
.
create
(
url
)).
GET
().
build
();
HttpResponse
<
byte
[]>
response
=
httpClient
.
send
(
request
,
HttpResponse
.
BodyHandlers
.
ofByteArray
());
if
(
response
.
statusCode
()
!=
200
)
{
throw
new
BizException
(
ErrorCode
.
INTERNAL_ERROR
,
"下载图片失败 HTTP "
+
response
.
statusCode
());
}
return
response
.
body
();
}
private
String
extractJson
(
String
raw
)
{
String
s
=
raw
.
strip
();
if
(
s
.
startsWith
(
"```"
))
{
int
start
=
s
.
indexOf
(
'\n'
)
+
1
;
int
end
=
s
.
lastIndexOf
(
"```"
);
if
(
end
>
start
)
s
=
s
.
substring
(
start
,
end
).
strip
();
String
text
=
raw
==
null
?
""
:
raw
.
strip
();
if
(
text
.
startsWith
(
"```"
))
{
int
start
=
text
.
indexOf
(
'\n'
)
+
1
;
int
end
=
text
.
lastIndexOf
(
"```"
);
if
(
end
>
start
)
{
text
=
text
.
substring
(
start
,
end
).
strip
();
}
}
return
s
;
return
text
;
}
private
record
CharacterViewAsset
(
String
viewType
,
String
key
)
{
}
private
record
ProjectVisualStyle
(
String
slug
,
String
label
,
String
extractionGuidance
,
String
renderGuidance
)
{
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment