release: opensource snapshot 2026-02-27 19:25:00
This commit is contained in:
837
prisma/schema.prisma
Normal file
837
prisma/schema.prisma
Normal file
@@ -0,0 +1,837 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
refresh_token String? @db.Text
|
||||
access_token String? @db.Text
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String? @db.Text
|
||||
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? @db.Text
|
||||
descriptions String? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageUrls String? @db.Text
|
||||
selectedIndex Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
previousImageUrl String? @db.Text
|
||||
previousImageUrls String? @db.Text
|
||||
previousDescription String? @db.Text // 上一次的描述词(用于撤回)
|
||||
previousDescriptions String? @db.Text // 上一次的描述词数组(用于撤回)
|
||||
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? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
isSelected Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
previousImageUrl String? @db.Text
|
||||
previousDescription String? @db.Text // 上一次的描述词(用于撤回)
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("LocationImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
location NovelPromotionLocation @relation("LocationImages", fields: [locationId], references: [id], onDelete: Cascade)
|
||||
selectedByLocations NovelPromotionLocation[] @relation("SelectedLocationImage")
|
||||
|
||||
@@unique([locationId, imageIndex])
|
||||
@@index([locationId])
|
||||
@@index([imageMediaId])
|
||||
@@map("location_images")
|
||||
}
|
||||
|
||||
model NovelPromotionCharacter {
|
||||
id String @id @default(uuid())
|
||||
novelPromotionProjectId String
|
||||
name String
|
||||
aliases String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
customVoiceUrl String? @db.Text
|
||||
customVoiceMediaId String?
|
||||
customVoiceMedia MediaObject? @relation("NovelPromotionCharacterVoiceMedia", fields: [customVoiceMediaId], references: [id], onDelete: SetNull)
|
||||
voiceId String?
|
||||
voiceType String?
|
||||
profileData String? @db.Text
|
||||
profileConfirmed Boolean @default(false)
|
||||
introduction String? @db.Text // 角色介绍(身份、关系、称呼映射,如"我"对应此角色)
|
||||
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? @db.Text // 场景简要描述(用途/人物关联)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
sourceGlobalLocationId String? // 🆕 来源全局场景ID(复制时记录)
|
||||
selectedImageId String?
|
||||
selectedImage LocationImage? @relation("SelectedLocationImage", fields: [selectedImageId], references: [id], onDelete: SetNull)
|
||||
images LocationImage[] @relation("LocationImages")
|
||||
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? @db.Text
|
||||
novelText String? @db.Text
|
||||
audioUrl String? @db.Text
|
||||
audioMediaId String?
|
||||
audioMedia MediaObject? @relation("NovelPromotionEpisodeAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||||
srtContent String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
speakerVoices String? @db.Text
|
||||
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 @db.Text // JSON 存储编辑项目数据
|
||||
renderStatus String? // pending | rendering | completed | failed
|
||||
renderTaskId String?
|
||||
outputUrl String? @db.Text
|
||||
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 @db.Text
|
||||
location String? @db.Text
|
||||
content String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
characters String? @db.Text
|
||||
endText String? @db.Text
|
||||
shotCount Int?
|
||||
startText String? @db.Text
|
||||
screenplay String? @db.Text
|
||||
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? @db.Text
|
||||
cameraMove String? @db.Text
|
||||
description String? @db.Text
|
||||
location String? @db.Text
|
||||
characters String? @db.Text
|
||||
srtSegment String? @db.Text
|
||||
srtStart Float?
|
||||
srtEnd Float?
|
||||
duration Float?
|
||||
imagePrompt String? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("NovelPromotionPanelImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
imageHistory String? @db.Text
|
||||
videoPrompt String? @db.Text
|
||||
firstLastFramePrompt String? @db.Text
|
||||
videoUrl String? @db.Text
|
||||
videoGenerationMode String? @db.Text // 视频生成方式: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? @db.Text
|
||||
linkedToNextPanel Boolean @default(false)
|
||||
lipSyncTaskId String?
|
||||
lipSyncVideoUrl String?
|
||||
lipSyncVideoMediaId String?
|
||||
lipSyncVideoMedia MediaObject? @relation("NovelPromotionPanelLipSyncVideoMedia", fields: [lipSyncVideoMediaId], references: [id], onDelete: SetNull)
|
||||
sketchImageUrl String? @db.Text
|
||||
sketchImageMediaId String?
|
||||
sketchImageMedia MediaObject? @relation("NovelPromotionPanelSketchMedia", fields: [sketchImageMediaId], references: [id], onDelete: SetNull)
|
||||
photographyRules String? @db.Text
|
||||
actingNotes String? @db.Text // 演技指导数据 JSON
|
||||
previousImageUrl String? @db.Text
|
||||
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? @db.Text
|
||||
artStyle String @default("american-comic")
|
||||
artStylePrompt String? @db.Text
|
||||
characterModel String? // 用户配置的角色图片模型
|
||||
locationModel String? // 用户配置的场景图片模型
|
||||
storyboardModel String? // 用户配置的分镜图片模型
|
||||
editModel String? // 用户配置的修图/编辑模型
|
||||
videoResolution String @default("720p")
|
||||
capabilityOverrides String? @db.Text
|
||||
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? @db.Text
|
||||
locations String? @db.Text
|
||||
characters String? @db.Text
|
||||
plot String? @db.Text
|
||||
imagePrompt String? @db.Text
|
||||
scale String? @db.Text
|
||||
module String? @db.Text
|
||||
focus String? @db.Text
|
||||
zhSummarize String? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("NovelPromotionShotImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
pov String? @db.Text
|
||||
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? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
panelCount Int @default(9)
|
||||
storyboardTextJson String? @db.Text
|
||||
imageHistory String? @db.Text
|
||||
candidateImages String? @db.Text
|
||||
lastError String?
|
||||
photographyPlan String? @db.Text
|
||||
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? @db.Text
|
||||
imagePrompt String? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("SupplementaryPanelImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
characters String? @db.Text
|
||||
location String? @db.Text
|
||||
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? @db.Text
|
||||
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 @db.Decimal(18, 6)
|
||||
metadata String? @db.Text
|
||||
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? @db.Text
|
||||
|
||||
// API Key 配置(极简版)
|
||||
llmBaseUrl String? @default("https://openrouter.ai/api/v1")
|
||||
llmApiKey String? @db.Text // 加密存储
|
||||
falApiKey String? @db.Text // FAL(图片+视频+语音)
|
||||
googleAiKey String? @db.Text // Google AI(Gemini 图片)
|
||||
arkApiKey String? @db.Text // 火山引擎(Seedream+Seedance)
|
||||
qwenApiKey String? @db.Text // 阿里百炼(声音设计)
|
||||
|
||||
// 自定义模型列表 + 价格(JSON)
|
||||
customModels String? @db.Text
|
||||
|
||||
// 自定义 OpenAI 兼容提供商列表(JSON,包含加密的 API Key)
|
||||
customProviders String? @db.Text
|
||||
|
||||
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 @db.Text
|
||||
voicePresetId String?
|
||||
audioUrl String? @db.Text
|
||||
audioMediaId String?
|
||||
audioMedia MediaObject? @relation("NovelPromotionVoiceLineAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
emotionPrompt String? @db.Text
|
||||
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 @db.Text
|
||||
audioMediaId String?
|
||||
audioMedia MediaObject? @relation("VoicePresetAudioMedia", fields: [audioMediaId], references: [id], onDelete: SetNull)
|
||||
description String? @db.Text
|
||||
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) @db.Decimal(18, 6)
|
||||
frozenAmount Decimal @default(0) @db.Decimal(18, 6)
|
||||
totalSpent Decimal @default(0) @db.Decimal(18, 6)
|
||||
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 @db.Decimal(18, 6)
|
||||
status String @default("pending")
|
||||
source String? @db.VarChar(64)
|
||||
taskId String?
|
||||
requestId String?
|
||||
idempotencyKey String? @unique
|
||||
metadata String? @db.Text
|
||||
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 @db.Decimal(18, 6)
|
||||
balanceAfter Decimal @db.Decimal(18, 6)
|
||||
description String? @db.Text
|
||||
relatedId String?
|
||||
freezeId String?
|
||||
operatorId String? @db.VarChar(64)
|
||||
externalOrderId String? @db.VarChar(128)
|
||||
idempotencyKey String? @db.VarChar(128)
|
||||
projectId String? @db.VarChar(128) // 关联项目 ID,用于流水展示项目名
|
||||
episodeId String? @db.VarChar(128) // 关联集数 ID,用于流水展示集数
|
||||
taskType String? @db.VarChar(64) // 任务类型 key(与 action 一致),用于前端 i18n
|
||||
billingMeta String? @db.Text // 计费详情 JSON: { quantity, unit, model, resolution, duration, tokens... }
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@index([type])
|
||||
@@index([createdAt])
|
||||
@@index([freezeId])
|
||||
@@index([externalOrderId])
|
||||
@@index([projectId])
|
||||
@@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? @db.Text
|
||||
billingInfo Json?
|
||||
billedAt DateTime?
|
||||
queuedAt DateTime @default(now())
|
||||
startedAt DateTime?
|
||||
finishedAt DateTime?
|
||||
heartbeatAt DateTime?
|
||||
enqueuedAt DateTime?
|
||||
enqueueAttempts Int @default(0)
|
||||
lastEnqueueError String? @db.Text
|
||||
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? @db.Text
|
||||
profileData String? @db.Text
|
||||
profileConfirmed Boolean @default(false)
|
||||
voiceId String?
|
||||
voiceType String?
|
||||
customVoiceUrl String? @db.Text
|
||||
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? @db.Text
|
||||
descriptions String? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("GlobalCharacterAppearanceImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
imageUrls String? @db.Text
|
||||
selectedIndex Int?
|
||||
previousImageUrl String? @db.Text
|
||||
previousImageMediaId String?
|
||||
previousImageMedia MediaObject? @relation("GlobalCharacterAppearancePreviousImageMedia", fields: [previousImageMediaId], references: [id], onDelete: SetNull)
|
||||
previousImageUrls String? @db.Text
|
||||
previousDescription String? @db.Text // 上一次的描述词(用于撤回)
|
||||
previousDescriptions String? @db.Text // 上一次的描述词数组(用于撤回)
|
||||
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? @db.Text
|
||||
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? @db.Text
|
||||
imageUrl String? @db.Text
|
||||
imageMediaId String?
|
||||
imageMedia MediaObject? @relation("GlobalLocationImageMedia", fields: [imageMediaId], references: [id], onDelete: SetNull)
|
||||
isSelected Boolean @default(false)
|
||||
previousImageUrl String? @db.Text
|
||||
previousImageMediaId String?
|
||||
previousImageMedia MediaObject? @relation("GlobalLocationImagePreviousImageMedia", fields: [previousImageMediaId], references: [id], onDelete: SetNull)
|
||||
previousDescription String? @db.Text // 上一次的描述词(用于撤回)
|
||||
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? @db.Text // 详细描述
|
||||
voiceId String? // qwen-tts-vd 的 voice ID
|
||||
voiceType String @default("qwen-designed") // qwen-designed | custom
|
||||
customVoiceUrl String? @db.Text // 上传的音频 URL(预览用)
|
||||
customVoiceMediaId String?
|
||||
customVoiceMedia MediaObject? @relation("GlobalVoiceCustomVoiceMedia", fields: [customVoiceMediaId], references: [id], onDelete: SetNull)
|
||||
voicePrompt String? @db.Text // 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 @db.VarChar(512)
|
||||
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 @db.Text
|
||||
checksum String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([runId])
|
||||
@@index([tableName, fieldName])
|
||||
@@map("legacy_media_refs_backup")
|
||||
}
|
||||
829
prisma/schema.sqlit.prisma
Normal file
829
prisma/schema.sqlit.prisma
Normal file
@@ -0,0 +1,829 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user