830 lines
31 KiB
Plaintext
830 lines
31 KiB
Plaintext
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "sqlite"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
model Account {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
type String
|
||
provider String
|
||
providerAccountId String
|
||
refresh_token String?
|
||
access_token String?
|
||
expires_at Int?
|
||
token_type String?
|
||
scope String?
|
||
id_token String?
|
||
session_state String?
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([provider, providerAccountId])
|
||
@@index([userId])
|
||
@@map("account")
|
||
}
|
||
|
||
model CharacterAppearance {
|
||
id String @id @default(uuid())
|
||
characterId String
|
||
appearanceIndex Int
|
||
changeReason String
|
||
description String?
|
||
descriptions String?
|
||
imageUrl String?
|
||
imageUrls String?
|
||
selectedIndex Int?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
previousImageUrl String?
|
||
previousImageUrls String?
|
||
previousDescription String? // 上一次的描述词(用于撤回)
|
||
previousDescriptions String? // 上一次的描述词数组(用于撤回)
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("CharacterAppearanceImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
character NovelPromotionCharacter @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([characterId, appearanceIndex])
|
||
@@index([characterId])
|
||
@@index([imageMediaId])
|
||
@@map("character_appearances")
|
||
}
|
||
|
||
model LocationImage {
|
||
id String @id @default(uuid())
|
||
locationId String
|
||
imageIndex Int
|
||
description String?
|
||
imageUrl String?
|
||
isSelected Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
previousImageUrl String?
|
||
previousDescription String? // 上一次的描述词(用于撤回)
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("LocationImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
location NovelPromotionLocation @relation(fields: [locationId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([locationId, imageIndex])
|
||
@@index([locationId])
|
||
@@index([imageMediaId])
|
||
@@map("location_images")
|
||
}
|
||
|
||
model NovelPromotionCharacter {
|
||
id String @id @default(uuid())
|
||
novelPromotionProjectId String
|
||
name String
|
||
aliases String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
customVoiceUrl String?
|
||
customVoiceMediaId String?
|
||
customVoiceMedia MediaObject? @relation("NovelPromotionCharacterVoiceMedia", fields: [customVoiceMediaId], references: [id], onDelete: SetNull)
|
||
voiceId String?
|
||
voiceType String?
|
||
profileData String?
|
||
profileConfirmed Boolean @default(false)
|
||
introduction String? // 角色介绍(身份、关系、称呼映射,如"我"对应此角色)
|
||
sourceGlobalCharacterId String? // 🆕 来源全局角色ID(复制时记录)
|
||
appearances CharacterAppearance[]
|
||
novelPromotionProject NovelPromotionProject @relation(fields: [novelPromotionProjectId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([novelPromotionProjectId])
|
||
@@index([customVoiceMediaId])
|
||
@@map("novel_promotion_characters")
|
||
}
|
||
|
||
model NovelPromotionLocation {
|
||
id String @id @default(uuid())
|
||
novelPromotionProjectId String
|
||
name String
|
||
summary String? // 场景简要描述(用途/人物关联)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
sourceGlobalLocationId String? // 🆕 来源全局场景ID(复制时记录)
|
||
images LocationImage[]
|
||
novelPromotionProject NovelPromotionProject @relation(fields: [novelPromotionProjectId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([novelPromotionProjectId])
|
||
@@map("novel_promotion_locations")
|
||
}
|
||
|
||
model NovelPromotionEpisode {
|
||
id String @id @default(uuid())
|
||
novelPromotionProjectId String
|
||
episodeNumber Int
|
||
name String
|
||
description String?
|
||
novelText String?
|
||
audioUrl String?
|
||
audioMediaId String?
|
||
audioMedia MediaObject? @relation("NovelPromotionEpisodeAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||
srtContent String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
speakerVoices String?
|
||
clips NovelPromotionClip[]
|
||
novelPromotionProject NovelPromotionProject @relation(fields: [novelPromotionProjectId], references: [id], onDelete: Cascade)
|
||
shots NovelPromotionShot[]
|
||
storyboards NovelPromotionStoryboard[]
|
||
voiceLines NovelPromotionVoiceLine[]
|
||
editorProject VideoEditorProject?
|
||
|
||
@@unique([novelPromotionProjectId, episodeNumber])
|
||
@@index([novelPromotionProjectId])
|
||
@@index([audioMediaId])
|
||
@@map("novel_promotion_episodes")
|
||
}
|
||
|
||
// 视频编辑器项目 - 存储剪辑数据
|
||
model VideoEditorProject {
|
||
id String @id @default(uuid())
|
||
episodeId String @unique
|
||
projectData String // JSON 存储编辑项目数据
|
||
renderStatus String? // pending | rendering | completed | failed
|
||
renderTaskId String?
|
||
outputUrl String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
episode NovelPromotionEpisode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("video_editor_projects")
|
||
}
|
||
|
||
model NovelPromotionClip {
|
||
id String @id @default(uuid())
|
||
episodeId String
|
||
start Int?
|
||
end Int?
|
||
duration Int?
|
||
summary String
|
||
location String?
|
||
content String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
characters String?
|
||
endText String?
|
||
shotCount Int?
|
||
startText String?
|
||
screenplay String?
|
||
episode NovelPromotionEpisode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
|
||
shots NovelPromotionShot[]
|
||
storyboard NovelPromotionStoryboard?
|
||
|
||
@@index([episodeId])
|
||
@@map("novel_promotion_clips")
|
||
}
|
||
|
||
model NovelPromotionPanel {
|
||
id String @id @default(uuid())
|
||
storyboardId String
|
||
panelIndex Int
|
||
panelNumber Int?
|
||
shotType String?
|
||
cameraMove String?
|
||
description String?
|
||
location String?
|
||
characters String?
|
||
srtSegment String?
|
||
srtStart Float?
|
||
srtEnd Float?
|
||
duration Float?
|
||
imagePrompt String?
|
||
imageUrl String?
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("NovelPromotionPanelImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
imageHistory String?
|
||
videoPrompt String?
|
||
firstLastFramePrompt String?
|
||
videoUrl String?
|
||
videoGenerationMode String? // 视频生成方式:normal | firstlastframe
|
||
videoMediaId String?
|
||
videoMedia MediaObject? @relation("NovelPromotionPanelVideoMedia", fields: [videoMediaId], references: [id], onDelete: SetNull)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
sceneType String?
|
||
candidateImages String?
|
||
linkedToNextPanel Boolean @default(false)
|
||
lipSyncTaskId String?
|
||
lipSyncVideoUrl String?
|
||
lipSyncVideoMediaId String?
|
||
lipSyncVideoMedia MediaObject? @relation("NovelPromotionPanelLipSyncVideoMedia", fields: [lipSyncVideoMediaId], references: [id], onDelete: SetNull)
|
||
sketchImageUrl String?
|
||
sketchImageMediaId String?
|
||
sketchImageMedia MediaObject? @relation("NovelPromotionPanelSketchMedia", fields: [sketchImageMediaId], references: [id], onDelete: SetNull)
|
||
photographyRules String?
|
||
actingNotes String? // 演技指导数据 JSON
|
||
previousImageUrl String?
|
||
previousImageMediaId String?
|
||
previousImageMedia MediaObject? @relation("NovelPromotionPanelPreviousImageMedia", fields: [previousImageMediaId], references: [id], onDelete: SetNull)
|
||
storyboard NovelPromotionStoryboard @relation(fields: [storyboardId], references: [id], onDelete: Cascade)
|
||
matchedVoiceLines NovelPromotionVoiceLine[]
|
||
|
||
@@unique([storyboardId, panelIndex])
|
||
@@index([storyboardId])
|
||
@@index([imageMediaId])
|
||
@@index([videoMediaId])
|
||
@@index([lipSyncVideoMediaId])
|
||
@@index([sketchImageMediaId])
|
||
@@index([previousImageMediaId])
|
||
@@map("novel_promotion_panels")
|
||
}
|
||
|
||
model NovelPromotionProject {
|
||
id String @id @default(uuid())
|
||
projectId String @unique
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
analysisModel String? // 用户配置的分析模型(nullable,必须配置后才能使用)
|
||
imageModel String? // 用户配置的图片模型
|
||
videoModel String? // 用户配置的视频模型
|
||
videoRatio String @default("9:16")
|
||
ttsRate String @default("+50%")
|
||
globalAssetText String?
|
||
artStyle String @default("american-comic")
|
||
artStylePrompt String?
|
||
characterModel String? // 用户配置的角色图片模型
|
||
locationModel String? // 用户配置的场景图片模型
|
||
storyboardModel String? // 用户配置的分镜图片模型
|
||
editModel String? // 用户配置的修图/编辑模型
|
||
videoResolution String @default("720p")
|
||
capabilityOverrides String?
|
||
workflowMode String @default("srt")
|
||
lastEpisodeId String?
|
||
imageResolution String @default("2K")
|
||
importStatus String?
|
||
characters NovelPromotionCharacter[]
|
||
episodes NovelPromotionEpisode[]
|
||
locations NovelPromotionLocation[]
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("novel_promotion_projects")
|
||
}
|
||
|
||
model NovelPromotionShot {
|
||
id String @id @default(uuid())
|
||
episodeId String
|
||
clipId String?
|
||
shotId String
|
||
srtStart Int
|
||
srtEnd Int
|
||
srtDuration Float
|
||
sequence String?
|
||
locations String?
|
||
characters String?
|
||
plot String?
|
||
imagePrompt String?
|
||
scale String?
|
||
module String?
|
||
focus String?
|
||
zhSummarize String?
|
||
imageUrl String?
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("NovelPromotionShotImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
pov String?
|
||
clip NovelPromotionClip? @relation(fields: [clipId], references: [id], onDelete: Cascade)
|
||
episode NovelPromotionEpisode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([clipId])
|
||
@@index([episodeId])
|
||
@@index([shotId])
|
||
@@index([imageMediaId])
|
||
@@map("novel_promotion_shots")
|
||
}
|
||
|
||
model NovelPromotionStoryboard {
|
||
id String @id @default(uuid())
|
||
episodeId String
|
||
clipId String @unique
|
||
storyboardImageUrl String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
panelCount Int @default(9)
|
||
storyboardTextJson String?
|
||
imageHistory String?
|
||
candidateImages String?
|
||
lastError String?
|
||
photographyPlan String?
|
||
panels NovelPromotionPanel[]
|
||
clip NovelPromotionClip @relation(fields: [clipId], references: [id], onDelete: Cascade)
|
||
episode NovelPromotionEpisode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
|
||
supplementaryPanels SupplementaryPanel[]
|
||
|
||
@@index([clipId])
|
||
@@index([episodeId])
|
||
@@map("novel_promotion_storyboards")
|
||
}
|
||
|
||
model SupplementaryPanel {
|
||
id String @id @default(uuid())
|
||
storyboardId String
|
||
sourceType String
|
||
sourcePanelId String?
|
||
description String?
|
||
imagePrompt String?
|
||
imageUrl String?
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("SupplementaryPanelImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
characters String?
|
||
location String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
storyboard NovelPromotionStoryboard @relation(fields: [storyboardId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([storyboardId])
|
||
@@index([imageMediaId])
|
||
@@map("supplementary_panels")
|
||
}
|
||
|
||
model Project {
|
||
id String @id @default(uuid())
|
||
name String
|
||
description String?
|
||
mode String @default("novel-promotion")
|
||
userId String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
lastAccessedAt DateTime?
|
||
novelPromotionData NovelPromotionProject?
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
usageCosts UsageCost[]
|
||
|
||
@@index([userId])
|
||
@@map("projects")
|
||
}
|
||
|
||
model Session {
|
||
id String @id @default(uuid())
|
||
sessionToken String @unique(map: "Session_sessionToken_key")
|
||
userId String
|
||
expires DateTime
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId])
|
||
@@map("session")
|
||
}
|
||
|
||
model UsageCost {
|
||
id String @id @default(uuid())
|
||
projectId String
|
||
userId String
|
||
apiType String
|
||
model String
|
||
action String
|
||
quantity Int
|
||
unit String
|
||
cost Decimal
|
||
metadata String?
|
||
createdAt DateTime @default(now())
|
||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([apiType])
|
||
@@index([createdAt])
|
||
@@index([projectId])
|
||
@@index([userId])
|
||
@@map("usage_costs")
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(uuid())
|
||
name String @unique(map: "User_name_key")
|
||
email String?
|
||
emailVerified DateTime?
|
||
image String?
|
||
password String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
accounts Account[]
|
||
projects Project[]
|
||
sessions Session[]
|
||
usageCosts UsageCost[]
|
||
balance UserBalance?
|
||
preferences UserPreference?
|
||
|
||
// 资产中心
|
||
globalAssetFolders GlobalAssetFolder[]
|
||
globalCharacters GlobalCharacter[]
|
||
globalLocations GlobalLocation[]
|
||
globalVoices GlobalVoice[]
|
||
tasks Task[]
|
||
taskEvents TaskEvent[]
|
||
|
||
@@map("user")
|
||
}
|
||
|
||
model UserPreference {
|
||
id String @id @default(uuid())
|
||
userId String @unique
|
||
analysisModel String? // 用户配置的分析模型(nullable,必须配置后才能使用)
|
||
characterModel String? // 用户配置的角色图片模型
|
||
locationModel String? // 用户配置的场景图片模型
|
||
storyboardModel String? // 用户配置的分镜图片模型
|
||
editModel String? // 用户配置的修图模型
|
||
videoModel String? // 用户配置的视频模型
|
||
lipSyncModel String? // 用户配置的口型同步模型
|
||
videoRatio String @default("9:16")
|
||
videoResolution String @default("720p")
|
||
artStyle String @default("american-comic")
|
||
ttsRate String @default("+50%")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
imageResolution String @default("2K")
|
||
capabilityDefaults String?
|
||
|
||
// API Key 配置(极简版)
|
||
llmBaseUrl String? @default("https://openrouter.ai/api/v1")
|
||
llmApiKey String? // 加密存储
|
||
falApiKey String? // FAL(图片+视频+语音)
|
||
googleAiKey String? // Google AI(Gemini 图片)
|
||
arkApiKey String? // 火山引擎(Seedream+Seedance)
|
||
qwenApiKey String? // 阿里百炼(声音设计)
|
||
|
||
// 自定义模型列表 + 价格(JSON)
|
||
customModels String?
|
||
|
||
// 自定义 OpenAI 兼容提供商列表(JSON,包含加密的 API Key)
|
||
customProviders String?
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("user_preferences")
|
||
}
|
||
|
||
model VerificationToken {
|
||
identifier String
|
||
token String @unique(map: "VerificationToken_token_key")
|
||
expires DateTime
|
||
|
||
@@unique([identifier, token])
|
||
@@map("verificationtoken")
|
||
}
|
||
|
||
model NovelPromotionVoiceLine {
|
||
id String @id @default(uuid())
|
||
episodeId String
|
||
lineIndex Int
|
||
speaker String
|
||
content String
|
||
voicePresetId String?
|
||
audioUrl String?
|
||
audioMediaId String?
|
||
audioMedia MediaObject? @relation("NovelPromotionVoiceLineAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
emotionPrompt String?
|
||
emotionStrength Float? @default(0.4)
|
||
matchedPanelIndex Int?
|
||
matchedStoryboardId String?
|
||
audioDuration Int?
|
||
matchedPanelId String?
|
||
episode NovelPromotionEpisode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
|
||
matchedPanel NovelPromotionPanel? @relation(fields: [matchedPanelId], references: [id])
|
||
|
||
@@unique([episodeId, lineIndex])
|
||
@@index([episodeId])
|
||
@@index([matchedPanelId])
|
||
@@index([audioMediaId])
|
||
@@map("novel_promotion_voice_lines")
|
||
}
|
||
|
||
model VoicePreset {
|
||
id String @id @default(uuid())
|
||
name String
|
||
audioUrl String
|
||
audioMediaId String?
|
||
audioMedia MediaObject? @relation("VoicePresetAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||
description String?
|
||
gender String?
|
||
isSystem Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([audioMediaId])
|
||
@@map("voice_presets")
|
||
}
|
||
|
||
model UserBalance {
|
||
id String @id @default(uuid())
|
||
userId String @unique
|
||
balance Decimal @default(0)
|
||
frozenAmount Decimal @default(0)
|
||
totalSpent Decimal @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("user_balances")
|
||
}
|
||
|
||
model BalanceFreeze {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
amount Decimal
|
||
status String @default("pending")
|
||
source String?
|
||
taskId String?
|
||
requestId String?
|
||
idempotencyKey String? @unique
|
||
metadata String?
|
||
expiresAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
|
||
@@index([userId])
|
||
@@index([status])
|
||
@@index([taskId])
|
||
@@map("balance_freezes")
|
||
}
|
||
|
||
model BalanceTransaction {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
type String
|
||
amount Decimal
|
||
balanceAfter Decimal
|
||
description String?
|
||
relatedId String?
|
||
freezeId String?
|
||
operatorId String?
|
||
externalOrderId String?
|
||
idempotencyKey String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([userId])
|
||
@@index([type])
|
||
@@index([createdAt])
|
||
@@index([freezeId])
|
||
@@index([externalOrderId])
|
||
@@unique([userId, type, idempotencyKey])
|
||
@@map("balance_transactions")
|
||
}
|
||
|
||
model Task {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
projectId String
|
||
episodeId String?
|
||
type String
|
||
targetType String
|
||
targetId String
|
||
status String @default("queued")
|
||
progress Int @default(0)
|
||
attempt Int @default(0)
|
||
maxAttempts Int @default(5)
|
||
priority Int @default(0)
|
||
dedupeKey String? @unique
|
||
externalId String?
|
||
payload Json?
|
||
result Json?
|
||
errorCode String?
|
||
errorMessage String?
|
||
billingInfo Json?
|
||
billedAt DateTime?
|
||
queuedAt DateTime @default(now())
|
||
startedAt DateTime?
|
||
finishedAt DateTime?
|
||
heartbeatAt DateTime?
|
||
enqueuedAt DateTime?
|
||
enqueueAttempts Int @default(0)
|
||
lastEnqueueError String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
events TaskEvent[]
|
||
|
||
@@index([status])
|
||
@@index([type])
|
||
@@index([targetType, targetId])
|
||
@@index([projectId])
|
||
@@index([userId])
|
||
@@index([heartbeatAt])
|
||
@@map("tasks")
|
||
}
|
||
|
||
model TaskEvent {
|
||
id Int @id @default(autoincrement())
|
||
taskId String
|
||
projectId String
|
||
userId String
|
||
eventType String
|
||
payload Json?
|
||
createdAt DateTime @default(now())
|
||
|
||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([projectId, id])
|
||
@@index([taskId])
|
||
@@index([userId])
|
||
@@map("task_events")
|
||
}
|
||
|
||
// ==================== 资产中心 ====================
|
||
|
||
// 资产文件夹(一层,不支持嵌套)
|
||
model GlobalAssetFolder {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
name String
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
characters GlobalCharacter[]
|
||
locations GlobalLocation[]
|
||
voices GlobalVoice[]
|
||
|
||
@@index([userId])
|
||
@@map("global_asset_folders")
|
||
}
|
||
|
||
// 全局角色(结构与 NovelPromotionCharacter 一致)
|
||
model GlobalCharacter {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
folderId String?
|
||
name String
|
||
aliases String?
|
||
profileData String?
|
||
profileConfirmed Boolean @default(false)
|
||
voiceId String?
|
||
voiceType String?
|
||
customVoiceUrl String?
|
||
customVoiceMediaId String?
|
||
customVoiceMedia MediaObject? @relation("GlobalCharacterVoiceMedia", fields: [customVoiceMediaId], references: [id], onDelete: SetNull)
|
||
globalVoiceId String? // 绑定的全局音色 ID
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
folder GlobalAssetFolder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||
appearances GlobalCharacterAppearance[]
|
||
|
||
@@index([userId])
|
||
@@index([folderId])
|
||
@@index([customVoiceMediaId])
|
||
@@map("global_characters")
|
||
}
|
||
|
||
// 全局角色形象(结构与 CharacterAppearance 一致)
|
||
model GlobalCharacterAppearance {
|
||
id String @id @default(uuid())
|
||
characterId String
|
||
appearanceIndex Int
|
||
changeReason String @default("default")
|
||
description String?
|
||
descriptions String?
|
||
imageUrl String?
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("GlobalCharacterAppearanceImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
imageUrls String?
|
||
selectedIndex Int?
|
||
previousImageUrl String?
|
||
previousImageMediaId String?
|
||
previousImageMedia MediaObject? @relation("GlobalCharacterAppearancePreviousImageMedia", fields: [previousImageMediaId], references: [id], onDelete: SetNull)
|
||
previousImageUrls String?
|
||
previousDescription String? // 上一次的描述词(用于撤回)
|
||
previousDescriptions String? // 上一次的描述词数组(用于撤回)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
character GlobalCharacter @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([characterId, appearanceIndex])
|
||
@@index([characterId])
|
||
@@index([imageMediaId])
|
||
@@index([previousImageMediaId])
|
||
@@map("global_character_appearances")
|
||
}
|
||
|
||
// 全局场景(结构与 NovelPromotionLocation 一致)
|
||
model GlobalLocation {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
folderId String?
|
||
name String
|
||
summary String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
folder GlobalAssetFolder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||
images GlobalLocationImage[]
|
||
|
||
@@index([userId])
|
||
@@index([folderId])
|
||
@@map("global_locations")
|
||
}
|
||
|
||
// 全局场景图片(结构与 LocationImage 一致)
|
||
model GlobalLocationImage {
|
||
id String @id @default(uuid())
|
||
locationId String
|
||
imageIndex Int
|
||
description String?
|
||
imageUrl String?
|
||
imageMediaId String?
|
||
imageMedia MediaObject? @relation("GlobalLocationImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||
isSelected Boolean @default(false)
|
||
previousImageUrl String?
|
||
previousImageMediaId String?
|
||
previousImageMedia MediaObject? @relation("GlobalLocationImagePreviousImageMedia", fields: [previousImageMediaId], references: [id], onDelete: SetNull)
|
||
previousDescription String? // 上一次的描述词(用于撤回)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
location GlobalLocation @relation(fields: [locationId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([locationId, imageIndex])
|
||
@@index([locationId])
|
||
@@index([imageMediaId])
|
||
@@index([previousImageMediaId])
|
||
@@map("global_location_images")
|
||
}
|
||
|
||
// 全局音色库
|
||
model GlobalVoice {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
folderId String?
|
||
name String // 音色名称
|
||
description String? // 详细描述
|
||
voiceId String? // qwen-tts-vd 的 voice ID
|
||
voiceType String @default("qwen-designed") // qwen-designed | custom
|
||
customVoiceUrl String? // 上传的音频 URL(预览用)
|
||
customVoiceMediaId String?
|
||
customVoiceMedia MediaObject? @relation("GlobalVoiceCustomVoiceMedia", fields: [customVoiceMediaId], references: [id], onDelete: SetNull)
|
||
voicePrompt String? // AI 设计时的提示词
|
||
gender String? // male | female | neutral
|
||
language String @default("zh")
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
folder GlobalAssetFolder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||
|
||
@@index([userId])
|
||
@@index([folderId])
|
||
@@index([customVoiceMediaId])
|
||
@@map("global_voices")
|
||
}
|
||
|
||
model MediaObject {
|
||
id String @id @default(uuid())
|
||
publicId String @unique
|
||
storageKey String @unique
|
||
sha256 String?
|
||
mimeType String?
|
||
sizeBytes BigInt?
|
||
width Int?
|
||
height Int?
|
||
durationMs Int?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @default(now()) @updatedAt
|
||
|
||
characterAppearanceImages CharacterAppearance[] @relation("CharacterAppearanceImageMedia")
|
||
locationImages LocationImage[] @relation("LocationImageMedia")
|
||
novelPromotionCharacterVoices NovelPromotionCharacter[] @relation("NovelPromotionCharacterVoiceMedia")
|
||
novelPromotionEpisodeAudios NovelPromotionEpisode[] @relation("NovelPromotionEpisodeAudioMedia")
|
||
novelPromotionPanelImages NovelPromotionPanel[] @relation("NovelPromotionPanelImageMedia")
|
||
novelPromotionPanelVideos NovelPromotionPanel[] @relation("NovelPromotionPanelVideoMedia")
|
||
novelPromotionPanelLipSyncVideos NovelPromotionPanel[] @relation("NovelPromotionPanelLipSyncVideoMedia")
|
||
novelPromotionPanelSketchImages NovelPromotionPanel[] @relation("NovelPromotionPanelSketchMedia")
|
||
novelPromotionPanelPreviousImages NovelPromotionPanel[] @relation("NovelPromotionPanelPreviousImageMedia")
|
||
novelPromotionShotImages NovelPromotionShot[] @relation("NovelPromotionShotImageMedia")
|
||
supplementaryPanelImages SupplementaryPanel[] @relation("SupplementaryPanelImageMedia")
|
||
novelPromotionVoiceLineAudios NovelPromotionVoiceLine[] @relation("NovelPromotionVoiceLineAudioMedia")
|
||
voicePresetAudios VoicePreset[] @relation("VoicePresetAudioMedia")
|
||
globalCharacterVoices GlobalCharacter[] @relation("GlobalCharacterVoiceMedia")
|
||
globalCharacterAppearanceImages GlobalCharacterAppearance[] @relation("GlobalCharacterAppearanceImageMedia")
|
||
globalCharacterAppearancePreviousImgs GlobalCharacterAppearance[] @relation("GlobalCharacterAppearancePreviousImageMedia")
|
||
globalLocationImageImages GlobalLocationImage[] @relation("GlobalLocationImageMedia")
|
||
globalLocationImagePreviousImages GlobalLocationImage[] @relation("GlobalLocationImagePreviousImageMedia")
|
||
globalVoiceCustomVoices GlobalVoice[] @relation("GlobalVoiceCustomVoiceMedia")
|
||
|
||
@@index([createdAt])
|
||
@@map("media_objects")
|
||
}
|
||
|
||
model LegacyMediaRefBackup {
|
||
id String @id @default(uuid())
|
||
runId String
|
||
tableName String
|
||
rowId String
|
||
fieldName String
|
||
legacyValue String
|
||
checksum String
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([runId])
|
||
@@index([tableName, fieldName])
|
||
@@map("legacy_media_refs_backup")
|
||
}
|