refactor(*): 代码质量治理 - 收敛接口客户端、模型职责与异步计费巡检
This commit is contained in:
1 parent
9ca9dd9df6
commit
b3199c5e7e
42 files changed
+3051
-2271
No files matched your search
@@ -334,6 +334,7 @@ func RegisterSessionRoutes(r *gin.RouterGroup, svc *service.Service) {
|
||||
r.GET("/sessions/:id", querySession)
|
||||
r.POST("/files", uploadFile)
|
||||
r.GET("/sessions/:id/results", downloadResults)
|
||||
// 兼容旧客户端的废弃路由;新调用统一使用上方 REST 风格路径。
|
||||
r.POST("/create_session", createSession)
|
||||
r.GET("/query_session/:id", querySession)
|
||||
r.POST("/upload_file", uploadFile)
|
||||
|
||||
@@ -2,8 +2,6 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TaskStatus string
|
||||
@@ -149,710 +147,3 @@ const (
|
||||
WorkflowStepStatusFailed WorkflowStepStatus = "failed"
|
||||
WorkflowStepStatusSkipped WorkflowStepStatus = "skipped"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Username string `json:"username" gorm:"uniqueIndex;size:80"`
|
||||
Email string `json:"email,omitempty" gorm:"size:160"`
|
||||
DisplayName string `json:"displayName" gorm:"size:80"`
|
||||
Role UserRole `json:"role" gorm:"index;size:24"`
|
||||
Status UserStatus `json:"status" gorm:"index;size:24"`
|
||||
PasswordHash string `json:"-"`
|
||||
LastLoginAt *time.Time `json:"lastLoginAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AuthSession struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TokenHash string `json:"-"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserIdentity struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
Provider string `json:"provider" gorm:"size:32;uniqueIndex:idx_user_identity_provider_subject,priority:1"`
|
||||
Subject string `json:"subject" gorm:"size:160;uniqueIndex:idx_user_identity_provider_subject,priority:2"`
|
||||
ProviderUsername string `json:"providerUsername" gorm:"size:160"`
|
||||
AvatarURL string `json:"avatarUrl"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type OAuthState struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Provider string `json:"provider" gorm:"index;size:32"`
|
||||
StateHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
CodeVerifier string `json:"-" gorm:"size:160"`
|
||||
NextPath string `json:"nextPath"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
UsedAt *time.Time `json:"usedAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type EmailVerificationCode struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Email string `json:"email" gorm:"index;size:160"`
|
||||
CodeHash string `json:"-" gorm:"size:64"`
|
||||
Purpose string `json:"purpose" gorm:"index;size:32"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
UsedAt *time.Time `json:"usedAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type ModelChannel struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
Scope ChannelScope `json:"scope" gorm:"index;size:24"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
Name string `json:"name" gorm:"size:80"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"-"`
|
||||
SecretKey string `json:"-"`
|
||||
APIFormat string `json:"apiFormat" gorm:"size:24"`
|
||||
ConcurrencyLimit int `json:"concurrencyLimit"`
|
||||
ModelsJSON string `json:"modelsJson" gorm:"type:text"`
|
||||
HeadersJSON string `json:"-" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
type ChannelModel struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"size:36;index;uniqueIndex:idx_channel_model_key_active,priority:1,where:deleted_at IS NULL"`
|
||||
ModelKey string `json:"modelKey" gorm:"size:120;uniqueIndex:idx_channel_model_key_active,priority:2,where:deleted_at IS NULL"`
|
||||
DisplayName string `json:"displayName" gorm:"size:160"`
|
||||
Capability string `json:"capability" gorm:"size:32;index"`
|
||||
Protocol ChannelInterfaceType `json:"protocol" gorm:"size:32;index"`
|
||||
BillingMode string `json:"billingMode" gorm:"size:32"`
|
||||
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits"`
|
||||
InputTokenPriceMicrocredits int64 `json:"inputTokenPriceMicrocredits"`
|
||||
OutputTokenPriceMicrocredits int64 `json:"outputTokenPriceMicrocredits"`
|
||||
CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits"`
|
||||
PriceConfigured bool `json:"priceConfigured" gorm:"index"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
PriceVersion int64 `json:"priceVersion"`
|
||||
CapabilityConfigJSON string `json:"-" gorm:"type:text"`
|
||||
CapabilityVersion int64 `json:"capabilityVersion"`
|
||||
CapabilityConfig map[string]any `json:"capabilityConfig,omitempty" gorm:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
type ApiCallLog struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_api_logs_user_created,priority:1"`
|
||||
UserDisplayName string `json:"userDisplayName,omitempty" gorm:"-"`
|
||||
UserAccount string `json:"userAccount,omitempty" gorm:"-"`
|
||||
ChannelID string `json:"channelId" gorm:"index;size:36;index:idx_api_logs_channel_created,priority:1"`
|
||||
ChannelName string `json:"channelName" gorm:"-"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
|
||||
TaskStatus TaskStatus `json:"taskStatus,omitempty" gorm:"-"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
Source string `json:"source" gorm:"index;size:64"`
|
||||
Capability string `json:"capability" gorm:"index;size:32"`
|
||||
Operation string `json:"operation" gorm:"size:64"`
|
||||
RequestKind string `json:"requestKind" gorm:"index;size:24"`
|
||||
Billable bool `json:"billable" gorm:"index"`
|
||||
APIFormat string `json:"apiFormat" gorm:"size:24"`
|
||||
Method string `json:"method" gorm:"size:16"`
|
||||
Path string `json:"path"`
|
||||
Model string `json:"model" gorm:"size:120;index:idx_api_logs_model_created,priority:1"`
|
||||
Status ApiCallStatus `json:"status" gorm:"index;size:24;index:idx_api_logs_status_created,priority:1"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
PollCount int `json:"pollCount"`
|
||||
ProviderStatus string `json:"providerStatus,omitempty" gorm:"size:32"`
|
||||
InputTokens int64 `json:"inputTokens"`
|
||||
OutputTokens int64 `json:"outputTokens"`
|
||||
CachedTokens int64 `json:"cachedTokens"`
|
||||
UsageAvailable bool `json:"usageAvailable"`
|
||||
MediaCount int `json:"mediaCount"`
|
||||
MediaPreviewURL string `json:"mediaPreviewUrl,omitempty" gorm:"-"`
|
||||
MediaPreviewKind string `json:"mediaPreviewKind,omitempty" gorm:"-"`
|
||||
VideoSeconds int `json:"videoSeconds"`
|
||||
ProviderRequestID string `json:"providerRequestId" gorm:"size:160"`
|
||||
EstimatedCostMicros int64 `json:"estimatedCostMicros"`
|
||||
CostAvailable bool `json:"costAvailable"`
|
||||
Currency string `json:"currency" gorm:"size:12"`
|
||||
ErrorCode string `json:"errorCode,omitempty" gorm:"index;size:80"`
|
||||
Error string `json:"error"`
|
||||
ConcurrencyLimit int `json:"concurrencyLimit"`
|
||||
UpstreamURL string `json:"upstreamUrl"`
|
||||
RequestContentType string `json:"requestContentType,omitempty" gorm:"size:160"`
|
||||
RequestBody string `json:"requestBody,omitempty" gorm:"type:text"`
|
||||
ResponseBody string `json:"responseBody,omitempty" gorm:"type:text"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index;index:idx_api_logs_user_created,priority:2;index:idx_api_logs_channel_created,priority:2;index:idx_api_logs_model_created,priority:2;index:idx_api_logs_status_created,priority:2"`
|
||||
}
|
||||
|
||||
type CreditAccount struct {
|
||||
UserID string `json:"userId" gorm:"primaryKey;size:36"`
|
||||
AvailableMicrocredits int64 `json:"availableMicrocredits"`
|
||||
ReservedMicrocredits int64 `json:"reservedMicrocredits"`
|
||||
Version int64 `json:"version"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CreditLedgerEntry struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;index:idx_credit_ledger_user_created,priority:1"`
|
||||
Type CreditLedgerType `json:"type" gorm:"size:32;index"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
AvailableDeltaMicrocredits int64 `json:"availableDeltaMicrocredits"`
|
||||
ReservedDeltaMicrocredits int64 `json:"reservedDeltaMicrocredits"`
|
||||
AvailableAfterMicrocredits int64 `json:"availableAfterMicrocredits"`
|
||||
ReservedAfterMicrocredits int64 `json:"reservedAfterMicrocredits"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
RedeemCodeID string `json:"redeemCodeId,omitempty" gorm:"index;size:36"`
|
||||
ActorUserID string `json:"actorUserId,omitempty" gorm:"index;size:36"`
|
||||
Model string `json:"model,omitempty" gorm:"size:120;index"`
|
||||
ChannelID string `json:"channelId,omitempty" gorm:"size:36;index"`
|
||||
Scene string `json:"scene,omitempty" gorm:"size:80;index"`
|
||||
Note string `json:"note,omitempty" gorm:"size:500"`
|
||||
ReferenceKey *string `json:"referenceKey,omitempty" gorm:"size:180;uniqueIndex"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_credit_ledger_user_created,priority:2"`
|
||||
}
|
||||
|
||||
type BillingOrder struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_billing_user_idempotency,priority:1"`
|
||||
IdempotencyKey string `json:"idempotencyKey" gorm:"size:160;uniqueIndex:idx_billing_user_idempotency,priority:2"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"index;size:36"`
|
||||
ChannelModelID string `json:"channelModelId" gorm:"index;size:36"`
|
||||
Model string `json:"model" gorm:"index;size:120"`
|
||||
Capability string `json:"capability" gorm:"index;size:32"`
|
||||
Scene string `json:"scene" gorm:"index;size:80"`
|
||||
BillingMode string `json:"billingMode" gorm:"size:32"`
|
||||
PriceVersion int64 `json:"priceVersion"`
|
||||
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits"`
|
||||
MultiplierBasisPoints int64 `json:"multiplierBasisPoints"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
ReservedAmountMicrocredits int64 `json:"reservedAmountMicrocredits"`
|
||||
ActualAmountMicrocredits int64 `json:"actualAmountMicrocredits"`
|
||||
RefundedAmountMicrocredits int64 `json:"refundedAmountMicrocredits"`
|
||||
InputTokenPriceMicrocredits int64 `json:"inputTokenPriceMicrocredits"`
|
||||
OutputTokenPriceMicrocredits int64 `json:"outputTokenPriceMicrocredits"`
|
||||
CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits"`
|
||||
InputTokens int64 `json:"inputTokens"`
|
||||
OutputTokens int64 `json:"outputTokens"`
|
||||
CachedTokens int64 `json:"cachedTokens"`
|
||||
UsageAvailable bool `json:"usageAvailable"`
|
||||
Status BillingStatus `json:"status" gorm:"index;size:24"`
|
||||
ProviderRequestID string `json:"providerRequestId,omitempty" gorm:"index;size:160"`
|
||||
Error string `json:"error,omitempty" gorm:"size:1000"`
|
||||
ResolvedBy string `json:"resolvedBy,omitempty" gorm:"index;size:36"`
|
||||
ResolutionNote string `json:"resolutionNote,omitempty" gorm:"size:500"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
SettledAt *time.Time `json:"settledAt"`
|
||||
RefundedAt *time.Time `json:"refundedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type RedeemBatch struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
Count int `json:"count"`
|
||||
Note string `json:"note" gorm:"size:500"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
CodesCipher string `json:"-" gorm:"type:text"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
AvailableCount int64 `json:"availableCount" gorm:"->;-:migration"`
|
||||
RedeemedCount int64 `json:"redeemedCount" gorm:"->;-:migration"`
|
||||
DisabledCount int64 `json:"disabledCount" gorm:"->;-:migration"`
|
||||
ExpiredCount int64 `json:"expiredCount" gorm:"->;-:migration"`
|
||||
}
|
||||
|
||||
type RedeemCode struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
BatchID string `json:"batchId" gorm:"index;size:36;index:idx_redeem_codes_batch_status,priority:1;index:idx_redeem_codes_batch_created,priority:1"`
|
||||
CodeHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
CodeSuffix string `json:"codeSuffix" gorm:"size:4"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
Status RedeemCodeStatus `json:"status" gorm:"index;size:24;index:idx_redeem_codes_batch_status,priority:2"`
|
||||
RedeemedBy string `json:"redeemedBy,omitempty" gorm:"index;size:36"`
|
||||
RedeemedAt *time.Time `json:"redeemedAt"`
|
||||
RedeemedIP string `json:"redeemedIp,omitempty" gorm:"size:64"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_redeem_codes_batch_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ModelPricing struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"size:36;uniqueIndex:idx_model_pricing_scope,priority:1"`
|
||||
Model string `json:"model" gorm:"size:120;uniqueIndex:idx_model_pricing_scope,priority:2"`
|
||||
Capability string `json:"capability" gorm:"size:32;uniqueIndex:idx_model_pricing_scope,priority:3"`
|
||||
Currency string `json:"currency" gorm:"size:12"`
|
||||
InputPerMillionMicros int64 `json:"inputPerMillionMicros"`
|
||||
OutputPerMillionMicros int64 `json:"outputPerMillionMicros"`
|
||||
CachedPerMillionMicros int64 `json:"cachedPerMillionMicros"`
|
||||
PerRequestMicros int64 `json:"perRequestMicros"`
|
||||
PerMediaMicros int64 `json:"perMediaMicros"`
|
||||
PerVideoSecondMicros int64 `json:"perVideoSecondMicros"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserDailyActivity struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:64"`
|
||||
Day time.Time `json:"day" gorm:"type:date;uniqueIndex:idx_user_daily_activity_day_user,priority:1;index"`
|
||||
UserID string `json:"userId" gorm:"size:36;uniqueIndex:idx_user_daily_activity_day_user,priority:2;index"`
|
||||
FirstActiveAt *time.Time `json:"firstActiveAt"`
|
||||
LastActiveAt *time.Time `json:"lastActiveAt"`
|
||||
LoginCount int `json:"loginCount"`
|
||||
TaskCount int `json:"taskCount"`
|
||||
AgentMessageCount int `json:"agentMessageCount"`
|
||||
CanvasActive bool `json:"canvasActive"`
|
||||
AssetCount int `json:"assetCount"`
|
||||
ResourceCount int `json:"resourceCount"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type SystemSetting struct {
|
||||
Key string `json:"key" gorm:"primaryKey;size:80"`
|
||||
ValueJSON string `json:"valueJson" gorm:"type:text"`
|
||||
UpdatedBy string `json:"updatedBy" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserOSSSetting struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_user_oss_settings_user_created,priority:1"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
ValueJSON string `json:"-" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_user_oss_settings_user_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserDailyUploadUsage struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:64"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_user_daily_upload_day,priority:1"`
|
||||
Day string `json:"day" gorm:"size:10;index;uniqueIndex:idx_user_daily_upload_day,priority:2"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Skill struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
OwnerID string `json:"ownerId" gorm:"index;size:36"`
|
||||
AuthorName string `json:"authorName" gorm:"size:120;index"`
|
||||
AuthorAvatarURL string `json:"authorAvatarUrl" gorm:"size:1000"`
|
||||
Name string `json:"name" gorm:"size:80;index"`
|
||||
Description string `json:"description" gorm:"size:500"`
|
||||
Instruction string `json:"instruction" gorm:"type:text"`
|
||||
Status int `json:"status" gorm:"index"`
|
||||
Source int `json:"source" gorm:"index"`
|
||||
Tag string `json:"tag" gorm:"size:32;index"`
|
||||
SortWeight int `json:"sortWeight" gorm:"index"`
|
||||
IsPrivate bool `json:"isPrivate" gorm:"index"`
|
||||
MarkdownURL string `json:"markdownUrl" gorm:"size:500"`
|
||||
ShowcaseMediaJSON string `json:"-" gorm:"type:text"`
|
||||
ExtraInfo string `json:"extraInfo" gorm:"type:text"`
|
||||
InitialLikeCount int64 `json:"initialLikeCount"`
|
||||
InitialAddedCount int64 `json:"initialAddedCount"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type UserSkillState struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_user_skill_state_user_skill,priority:1"`
|
||||
SkillID string `json:"skillId" gorm:"size:36;index;uniqueIndex:idx_user_skill_state_user_skill,priority:2"`
|
||||
Added bool `json:"added" gorm:"index"`
|
||||
Liked bool `json:"liked" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_resources_user_created,priority:1"`
|
||||
Kind string `json:"kind" gorm:"index;size:24"`
|
||||
Status ResourceStatus `json:"status" gorm:"index;size:24"`
|
||||
Provider string `json:"provider" gorm:"size:24"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Bucket string `json:"bucket" gorm:"size:160"`
|
||||
// 用户 OSS 每次修改都会生成新版本,资源固定引用创建时的版本,避免历史资源因换密钥失效。
|
||||
StorageSettingID string `json:"-" gorm:"index;size:36"`
|
||||
ObjectKey string `json:"objectKey" gorm:"index"`
|
||||
PublicURL string `json:"publicUrl"`
|
||||
MimeType string `json:"mimeType" gorm:"size:120"`
|
||||
Size int64 `json:"size"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
ETag string `json:"etag" gorm:"size:160"`
|
||||
Error string `json:"error"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_resources_user_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_assets_user_updated,priority:1"`
|
||||
Kind string `json:"kind" gorm:"index;size:24"`
|
||||
Category AssetCategory `json:"category" gorm:"index;size:32"`
|
||||
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
|
||||
PrimaryVersionID string `json:"primaryVersionId,omitempty" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_assets_user_updated,priority:2"`
|
||||
}
|
||||
|
||||
type ProjectAssetLink struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_project_asset_links_unique,priority:1"`
|
||||
AssetID string `json:"assetId" gorm:"index;size:36;uniqueIndex:idx_project_asset_links_unique,priority:2"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProjectAssetCandidate struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
UnitID string `json:"unitId,omitempty" gorm:"index;size:36"`
|
||||
ShotID string `json:"shotId,omitempty" gorm:"index;size:36"`
|
||||
Name string `json:"name" gorm:"size:240"`
|
||||
Category AssetCategory `json:"category" gorm:"index;size:32"`
|
||||
Status string `json:"status" gorm:"index;size:32"`
|
||||
DetailsJSON string `json:"detailsJson" gorm:"type:text"`
|
||||
ResolvedAssetID string `json:"resolvedAssetId,omitempty" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AssetVersion struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AssetID string `json:"assetId" gorm:"index;size:36;uniqueIndex:idx_asset_versions_number,priority:1"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_asset_versions_number,priority:2"`
|
||||
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
|
||||
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
|
||||
Prompt string `json:"prompt" gorm:"type:text"`
|
||||
Note string `json:"note" gorm:"size:500"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AssetRepresentation struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36;uniqueIndex:idx_asset_representations_task_role,priority:1"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_asset_representations_version_role,priority:1"`
|
||||
ResourceID string `json:"resourceId,omitempty" gorm:"index;size:36"`
|
||||
MediaType string `json:"mediaType" gorm:"index;size:24"`
|
||||
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_asset_representations_task_role,priority:2;uniqueIndex:idx_asset_representations_version_role,priority:2"`
|
||||
MetadataJSON string `json:"metadataJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// VoiceProfile 是可复用的声音身份;试听音频只是表现资源,不等同于声音本身。
|
||||
type VoiceProfile struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_voice_profiles_user_provider_key,priority:1"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Provider string `json:"provider" gorm:"size:48;uniqueIndex:idx_voice_profiles_user_provider_key,priority:2"`
|
||||
VoiceKey string `json:"voiceKey" gorm:"size:160;uniqueIndex:idx_voice_profiles_user_provider_key,priority:3"`
|
||||
Language string `json:"language" gorm:"size:80"`
|
||||
Timbre string `json:"timbre" gorm:"size:240"`
|
||||
SampleResourceID string `json:"sampleResourceId,omitempty" gorm:"index;size:36"`
|
||||
CompatibleModelsJSON string `json:"compatibleModelsJson" gorm:"type:text"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CharacterVoiceBinding struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"uniqueIndex;size:36"`
|
||||
VoiceProfileID string `json:"voiceProfileId" gorm:"index;size:36"`
|
||||
Instructions string `json:"instructions" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// Project 是短剧领域聚合根;CanvasProject 仍代表可独立创作的画布文档。
|
||||
type Project struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_projects_user_name,priority:1"`
|
||||
Name string `json:"name" gorm:"size:240;uniqueIndex:idx_projects_user_name,priority:2"`
|
||||
Type string `json:"type" gorm:"size:32;index"`
|
||||
AspectRatio string `json:"aspectRatio" gorm:"size:16"`
|
||||
SourceType string `json:"sourceType" gorm:"size:32"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
StylePresetID string `json:"stylePresetId" gorm:"size:64"`
|
||||
Status ProjectStatus `json:"status" gorm:"index;size:24"`
|
||||
Revision int64 `json:"revision"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type ProjectUnit struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
ParentID string `json:"parentId,omitempty" gorm:"index;size:36"`
|
||||
Kind ProjectUnitKind `json:"kind" gorm:"index;size:24"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
SourceText string `json:"sourceText" gorm:"type:text"`
|
||||
Status ProjectUnitStatus `json:"status" gorm:"index;size:24"`
|
||||
Position int `json:"position"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CanvasUnitLink struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:1"`
|
||||
CanvasID string `json:"canvasId" gorm:"index;size:80;uniqueIndex:idx_canvas_unit_links_unique,priority:2"`
|
||||
UnitID string `json:"unitId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:3"`
|
||||
Role string `json:"role" gorm:"size:32"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type Shot struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
UnitID string `json:"unitId" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Position int `json:"position"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ShotAssetReference struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ShotID string `json:"shotId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:1"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:2"`
|
||||
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_shot_asset_reference_unique,priority:3"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type WorkflowTemplateVersion struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
TemplateKey string `json:"templateKey" gorm:"size:80;uniqueIndex:idx_workflow_template_version,priority:1"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_workflow_template_version,priority:2"`
|
||||
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type WorkflowInstance struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:1"`
|
||||
UnitID string `json:"unitId,omitempty" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:2"`
|
||||
TemplateVersionID string `json:"templateVersionId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:3"`
|
||||
Scope string `json:"scope" gorm:"index;size:24"`
|
||||
Status WorkflowStatus `json:"status" gorm:"index;size:24"`
|
||||
Revision int64 `json:"revision"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowStepInstance struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
WorkflowInstanceID string `json:"workflowInstanceId" gorm:"index;size:36;uniqueIndex:idx_workflow_steps_instance_key,priority:1"`
|
||||
StepKey string `json:"stepKey" gorm:"size:80;uniqueIndex:idx_workflow_steps_instance_key,priority:2"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Position int `json:"position"`
|
||||
Status WorkflowStepStatus `json:"status" gorm:"index;size:24"`
|
||||
InputJSON string `json:"inputJson" gorm:"type:text"`
|
||||
OutputJSON string `json:"outputJson" gorm:"type:text"`
|
||||
Error string `json:"error" gorm:"type:text"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
CompletedAt *time.Time `json:"completedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowStepTask struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
WorkflowStepID string `json:"workflowStepId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:1"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:2"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type CanvasProject struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:80"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_canvas_projects_user_updated,priority:1"`
|
||||
ProjectID string `json:"projectId,omitempty" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_canvas_projects_user_updated,priority:2"`
|
||||
}
|
||||
|
||||
type CanvasShare struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_canvas_share_owner_project,priority:1"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80;uniqueIndex:idx_canvas_share_owner_project,priority:2"`
|
||||
TokenHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
TokenCipher string `json:"-" gorm:"type:text"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type PromptTemplate struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_prompt_template_operation_version,priority:1"`
|
||||
Name string `json:"name" gorm:"size:120"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_prompt_template_operation_version,priority:2"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
OutputType string `json:"outputType" gorm:"size:24"`
|
||||
Enabled bool `json:"enabled" gorm:"index;index:idx_prompt_template_active,priority:2"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// UserPromptCustomization 只保存用户的创作策略层,动态上下文和输出契约始终由服务端编译器注入。
|
||||
type UserPromptCustomization struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_user_prompt_operation,priority:1"`
|
||||
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_user_prompt_operation,priority:2"`
|
||||
Mode string `json:"mode" gorm:"size:24"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
BaseTemplateID string `json:"baseTemplateId" gorm:"size:36;index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Announcement struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Title string `json:"title" gorm:"size:120"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
Level AnnouncementLevel `json:"level" gorm:"index;size:24"`
|
||||
Status AnnouncementStatus `json:"status" gorm:"index;size:24;index:idx_announcements_status_published,priority:1"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
PublishedAt time.Time `json:"publishedAt" gorm:"index:idx_announcements_status_published,priority:2"`
|
||||
ClosedAt *time.Time `json:"closedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserAnnouncementRead struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:1"`
|
||||
AnnouncementID string `json:"announcementId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:2"`
|
||||
ReadAt time.Time `json:"readAt"`
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_tasks_user_created,priority:1"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80"`
|
||||
Type string `json:"type" gorm:"index;size:64"`
|
||||
Status TaskStatus `json:"status" gorm:"index;size:24;index:idx_tasks_status_created,priority:1;index:idx_tasks_claim,priority:1;index:idx_tasks_provider_cancel,priority:1"`
|
||||
Stage string `json:"stage" gorm:"size:80"`
|
||||
Progress int `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
Operation string `json:"operation" gorm:"size:64"`
|
||||
Provider string `json:"provider" gorm:"size:64"`
|
||||
Model string `json:"model" gorm:"size:120"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
ProviderRequestID string `json:"providerRequestId,omitempty" gorm:"index;size:160"`
|
||||
ProviderCancelStatus ProviderCancelStatus `json:"providerCancelStatus,omitempty" gorm:"index;size:24;index:idx_tasks_provider_cancel,priority:2"`
|
||||
ProviderCancelError string `json:"providerCancelError,omitempty" gorm:"type:text"`
|
||||
ProviderCancelAttempts int `json:"providerCancelAttempts,omitempty"`
|
||||
ProviderCancelRequestedAt *time.Time `json:"providerCancelRequestedAt,omitempty"`
|
||||
ProviderCancelledAt *time.Time `json:"providerCancelledAt,omitempty"`
|
||||
ProviderCancelNextCheckAt *time.Time `json:"providerCancelNextCheckAt,omitempty" gorm:"index:idx_tasks_provider_cancel,priority:3"`
|
||||
PollStage string `json:"pollStage,omitempty" gorm:"size:32"`
|
||||
NextPollAt *time.Time `json:"nextPollAt,omitempty" gorm:"index"`
|
||||
LeaseOwner string `json:"-" gorm:"index;size:120"`
|
||||
LeaseExpiresAt *time.Time `json:"-" gorm:"index;index:idx_tasks_claim,priority:2"`
|
||||
InputJSON string `json:"inputJson" gorm:"type:text"`
|
||||
ResultJSON string `json:"resultJson" gorm:"type:text"`
|
||||
TextDraft string `json:"textDraft,omitempty" gorm:"type:text"`
|
||||
Error string `json:"error"`
|
||||
Attempts int `json:"attempts"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
CompletedAt *time.Time `json:"completedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_tasks_user_created,priority:2;index:idx_tasks_status_created,priority:2;index:idx_tasks_claim,priority:3"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// TaskTextDelta 只保存可回放窗口内的文本增量;最终正文和失败草稿分别归并到 Task.ResultJSON 与 Task.TextDraft。
|
||||
type TaskTextDelta struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_task_text_deltas_user_created,priority:1"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_task_text_deltas_sequence,priority:1"`
|
||||
Sequence int64 `json:"sequence" gorm:"uniqueIndex:idx_task_text_deltas_sequence,priority:2"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
ByteCount int64 `json:"byteCount"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_task_text_deltas_user_created,priority:2"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80"`
|
||||
Status SessionStatus `json:"status" gorm:"index;size:24"`
|
||||
Prompt string `json:"prompt"`
|
||||
CanvasSnapshotJSON string `json:"canvasSnapshotJson" gorm:"type:text"`
|
||||
CanvasOpsJSON string `json:"canvasOpsJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
Role string `json:"role" gorm:"size:24"`
|
||||
Content string `json:"content"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type TaskLog struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36"`
|
||||
Level string `json:"level" gorm:"size:24"`
|
||||
Message string `json:"message"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type SessionFile struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
FileName string `json:"fileName"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Path string `json:"-"`
|
||||
Size int64 `json:"size"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
Kind string `json:"kind" gorm:"size:64"`
|
||||
URL string `json:"url"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ModelChannel struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
Scope ChannelScope `json:"scope" gorm:"index;size:24"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
Name string `json:"name" gorm:"size:80"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"-"`
|
||||
SecretKey string `json:"-"`
|
||||
APIFormat string `json:"apiFormat" gorm:"size:24"`
|
||||
ConcurrencyLimit int `json:"concurrencyLimit"`
|
||||
ModelsJSON string `json:"modelsJson" gorm:"type:text"`
|
||||
HeadersJSON string `json:"-" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
type ChannelModel struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"size:36;index;uniqueIndex:idx_channel_model_key_active,priority:1,where:deleted_at IS NULL"`
|
||||
ModelKey string `json:"modelKey" gorm:"size:120;uniqueIndex:idx_channel_model_key_active,priority:2,where:deleted_at IS NULL"`
|
||||
DisplayName string `json:"displayName" gorm:"size:160"`
|
||||
Capability string `json:"capability" gorm:"size:32;index"`
|
||||
Protocol ChannelInterfaceType `json:"protocol" gorm:"size:32;index"`
|
||||
BillingMode string `json:"billingMode" gorm:"size:32"`
|
||||
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits"`
|
||||
InputTokenPriceMicrocredits int64 `json:"inputTokenPriceMicrocredits"`
|
||||
OutputTokenPriceMicrocredits int64 `json:"outputTokenPriceMicrocredits"`
|
||||
CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits"`
|
||||
PriceConfigured bool `json:"priceConfigured" gorm:"index"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
PriceVersion int64 `json:"priceVersion"`
|
||||
CapabilityConfigJSON string `json:"-" gorm:"type:text"`
|
||||
CapabilityVersion int64 `json:"capabilityVersion"`
|
||||
CapabilityConfig map[string]any `json:"capabilityConfig,omitempty" gorm:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
type ApiCallLog struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_api_logs_user_created,priority:1"`
|
||||
UserDisplayName string `json:"userDisplayName,omitempty" gorm:"-"`
|
||||
UserAccount string `json:"userAccount,omitempty" gorm:"-"`
|
||||
ChannelID string `json:"channelId" gorm:"index;size:36;index:idx_api_logs_channel_created,priority:1"`
|
||||
ChannelName string `json:"channelName" gorm:"-"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
|
||||
TaskStatus TaskStatus `json:"taskStatus,omitempty" gorm:"-"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
Source string `json:"source" gorm:"index;size:64"`
|
||||
Capability string `json:"capability" gorm:"index;size:32"`
|
||||
Operation string `json:"operation" gorm:"size:64"`
|
||||
RequestKind string `json:"requestKind" gorm:"index;size:24"`
|
||||
Billable bool `json:"billable" gorm:"index"`
|
||||
APIFormat string `json:"apiFormat" gorm:"size:24"`
|
||||
Method string `json:"method" gorm:"size:16"`
|
||||
Path string `json:"path"`
|
||||
Model string `json:"model" gorm:"size:120;index:idx_api_logs_model_created,priority:1"`
|
||||
Status ApiCallStatus `json:"status" gorm:"index;size:24;index:idx_api_logs_status_created,priority:1"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
PollCount int `json:"pollCount"`
|
||||
ProviderStatus string `json:"providerStatus,omitempty" gorm:"size:32"`
|
||||
InputTokens int64 `json:"inputTokens"`
|
||||
OutputTokens int64 `json:"outputTokens"`
|
||||
CachedTokens int64 `json:"cachedTokens"`
|
||||
UsageAvailable bool `json:"usageAvailable"`
|
||||
MediaCount int `json:"mediaCount"`
|
||||
MediaPreviewURL string `json:"mediaPreviewUrl,omitempty" gorm:"-"`
|
||||
MediaPreviewKind string `json:"mediaPreviewKind,omitempty" gorm:"-"`
|
||||
VideoSeconds int `json:"videoSeconds"`
|
||||
ProviderRequestID string `json:"providerRequestId" gorm:"size:160"`
|
||||
EstimatedCostMicros int64 `json:"estimatedCostMicros"`
|
||||
CostAvailable bool `json:"costAvailable"`
|
||||
Currency string `json:"currency" gorm:"size:12"`
|
||||
ErrorCode string `json:"errorCode,omitempty" gorm:"index;size:80"`
|
||||
Error string `json:"error"`
|
||||
ConcurrencyLimit int `json:"concurrencyLimit"`
|
||||
UpstreamURL string `json:"upstreamUrl"`
|
||||
RequestContentType string `json:"requestContentType,omitempty" gorm:"size:160"`
|
||||
RequestBody string `json:"requestBody,omitempty" gorm:"type:text"`
|
||||
ResponseBody string `json:"responseBody,omitempty" gorm:"type:text"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index;index:idx_api_logs_user_created,priority:2;index:idx_api_logs_channel_created,priority:2;index:idx_api_logs_model_created,priority:2;index:idx_api_logs_status_created,priority:2"`
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type CreditAccount struct {
|
||||
UserID string `json:"userId" gorm:"primaryKey;size:36"`
|
||||
AvailableMicrocredits int64 `json:"availableMicrocredits"`
|
||||
ReservedMicrocredits int64 `json:"reservedMicrocredits"`
|
||||
Version int64 `json:"version"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CreditLedgerEntry struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;index:idx_credit_ledger_user_created,priority:1"`
|
||||
Type CreditLedgerType `json:"type" gorm:"size:32;index"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
AvailableDeltaMicrocredits int64 `json:"availableDeltaMicrocredits"`
|
||||
ReservedDeltaMicrocredits int64 `json:"reservedDeltaMicrocredits"`
|
||||
AvailableAfterMicrocredits int64 `json:"availableAfterMicrocredits"`
|
||||
ReservedAfterMicrocredits int64 `json:"reservedAfterMicrocredits"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
RedeemCodeID string `json:"redeemCodeId,omitempty" gorm:"index;size:36"`
|
||||
ActorUserID string `json:"actorUserId,omitempty" gorm:"index;size:36"`
|
||||
Model string `json:"model,omitempty" gorm:"size:120;index"`
|
||||
ChannelID string `json:"channelId,omitempty" gorm:"size:36;index"`
|
||||
Scene string `json:"scene,omitempty" gorm:"size:80;index"`
|
||||
Note string `json:"note,omitempty" gorm:"size:500"`
|
||||
ReferenceKey *string `json:"referenceKey,omitempty" gorm:"size:180;uniqueIndex"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_credit_ledger_user_created,priority:2"`
|
||||
}
|
||||
|
||||
type BillingOrder struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_billing_user_idempotency,priority:1"`
|
||||
IdempotencyKey string `json:"idempotencyKey" gorm:"size:160;uniqueIndex:idx_billing_user_idempotency,priority:2"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"index;size:36"`
|
||||
ChannelModelID string `json:"channelModelId" gorm:"index;size:36"`
|
||||
Model string `json:"model" gorm:"index;size:120"`
|
||||
Capability string `json:"capability" gorm:"index;size:32"`
|
||||
Scene string `json:"scene" gorm:"index;size:80"`
|
||||
BillingMode string `json:"billingMode" gorm:"size:32"`
|
||||
PriceVersion int64 `json:"priceVersion"`
|
||||
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits"`
|
||||
MultiplierBasisPoints int64 `json:"multiplierBasisPoints"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
ReservedAmountMicrocredits int64 `json:"reservedAmountMicrocredits"`
|
||||
ActualAmountMicrocredits int64 `json:"actualAmountMicrocredits"`
|
||||
RefundedAmountMicrocredits int64 `json:"refundedAmountMicrocredits"`
|
||||
InputTokenPriceMicrocredits int64 `json:"inputTokenPriceMicrocredits"`
|
||||
OutputTokenPriceMicrocredits int64 `json:"outputTokenPriceMicrocredits"`
|
||||
CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits"`
|
||||
InputTokens int64 `json:"inputTokens"`
|
||||
OutputTokens int64 `json:"outputTokens"`
|
||||
CachedTokens int64 `json:"cachedTokens"`
|
||||
UsageAvailable bool `json:"usageAvailable"`
|
||||
Status BillingStatus `json:"status" gorm:"index;size:24"`
|
||||
ProviderRequestID string `json:"providerRequestId,omitempty" gorm:"index;size:160"`
|
||||
Error string `json:"error,omitempty" gorm:"size:1000"`
|
||||
ResolvedBy string `json:"resolvedBy,omitempty" gorm:"index;size:36"`
|
||||
ResolutionNote string `json:"resolutionNote,omitempty" gorm:"size:500"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
SettledAt *time.Time `json:"settledAt"`
|
||||
RefundedAt *time.Time `json:"refundedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type RedeemBatch struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
Count int `json:"count"`
|
||||
Note string `json:"note" gorm:"size:500"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
CodesCipher string `json:"-" gorm:"type:text"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
AvailableCount int64 `json:"availableCount" gorm:"->;-:migration"`
|
||||
RedeemedCount int64 `json:"redeemedCount" gorm:"->;-:migration"`
|
||||
DisabledCount int64 `json:"disabledCount" gorm:"->;-:migration"`
|
||||
ExpiredCount int64 `json:"expiredCount" gorm:"->;-:migration"`
|
||||
}
|
||||
|
||||
type RedeemCode struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
BatchID string `json:"batchId" gorm:"index;size:36;index:idx_redeem_codes_batch_status,priority:1;index:idx_redeem_codes_batch_created,priority:1"`
|
||||
CodeHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
CodeSuffix string `json:"codeSuffix" gorm:"size:4"`
|
||||
AmountMicrocredits int64 `json:"amountMicrocredits"`
|
||||
Status RedeemCodeStatus `json:"status" gorm:"index;size:24;index:idx_redeem_codes_batch_status,priority:2"`
|
||||
RedeemedBy string `json:"redeemedBy,omitempty" gorm:"index;size:36"`
|
||||
RedeemedAt *time.Time `json:"redeemedAt"`
|
||||
RedeemedIP string `json:"redeemedIp,omitempty" gorm:"size:64"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_redeem_codes_batch_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ModelPricing struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ChannelID string `json:"channelId" gorm:"size:36;uniqueIndex:idx_model_pricing_scope,priority:1"`
|
||||
Model string `json:"model" gorm:"size:120;uniqueIndex:idx_model_pricing_scope,priority:2"`
|
||||
Capability string `json:"capability" gorm:"size:32;uniqueIndex:idx_model_pricing_scope,priority:3"`
|
||||
Currency string `json:"currency" gorm:"size:12"`
|
||||
InputPerMillionMicros int64 `json:"inputPerMillionMicros"`
|
||||
OutputPerMillionMicros int64 `json:"outputPerMillionMicros"`
|
||||
CachedPerMillionMicros int64 `json:"cachedPerMillionMicros"`
|
||||
PerRequestMicros int64 `json:"perRequestMicros"`
|
||||
PerMediaMicros int64 `json:"perMediaMicros"`
|
||||
PerVideoSecondMicros int64 `json:"perVideoSecondMicros"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Username string `json:"username" gorm:"uniqueIndex;size:80"`
|
||||
Email string `json:"email,omitempty" gorm:"size:160"`
|
||||
DisplayName string `json:"displayName" gorm:"size:80"`
|
||||
Role UserRole `json:"role" gorm:"index;size:24"`
|
||||
Status UserStatus `json:"status" gorm:"index;size:24"`
|
||||
PasswordHash string `json:"-"`
|
||||
LastLoginAt *time.Time `json:"lastLoginAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AuthSession struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TokenHash string `json:"-"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserIdentity struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
Provider string `json:"provider" gorm:"size:32;uniqueIndex:idx_user_identity_provider_subject,priority:1"`
|
||||
Subject string `json:"subject" gorm:"size:160;uniqueIndex:idx_user_identity_provider_subject,priority:2"`
|
||||
ProviderUsername string `json:"providerUsername" gorm:"size:160"`
|
||||
AvatarURL string `json:"avatarUrl"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type OAuthState struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Provider string `json:"provider" gorm:"index;size:32"`
|
||||
StateHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
CodeVerifier string `json:"-" gorm:"size:160"`
|
||||
NextPath string `json:"nextPath"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
UsedAt *time.Time `json:"usedAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type EmailVerificationCode struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Email string `json:"email" gorm:"index;size:160"`
|
||||
CodeHash string `json:"-" gorm:"size:64"`
|
||||
Purpose string `json:"purpose" gorm:"index;size:32"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
UsedAt *time.Time `json:"usedAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type UserDailyActivity struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:64"`
|
||||
Day time.Time `json:"day" gorm:"type:date;uniqueIndex:idx_user_daily_activity_day_user,priority:1;index"`
|
||||
UserID string `json:"userId" gorm:"size:36;uniqueIndex:idx_user_daily_activity_day_user,priority:2;index"`
|
||||
FirstActiveAt *time.Time `json:"firstActiveAt"`
|
||||
LastActiveAt *time.Time `json:"lastActiveAt"`
|
||||
LoginCount int `json:"loginCount"`
|
||||
TaskCount int `json:"taskCount"`
|
||||
AgentMessageCount int `json:"agentMessageCount"`
|
||||
CanvasActive bool `json:"canvasActive"`
|
||||
AssetCount int `json:"assetCount"`
|
||||
ResourceCount int `json:"resourceCount"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type SystemSetting struct {
|
||||
Key string `json:"key" gorm:"primaryKey;size:80"`
|
||||
ValueJSON string `json:"valueJson" gorm:"type:text"`
|
||||
UpdatedBy string `json:"updatedBy" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserOSSSetting struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_user_oss_settings_user_created,priority:1"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
ValueJSON string `json:"-" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_user_oss_settings_user_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserDailyUploadUsage struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:64"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_user_daily_upload_day,priority:1"`
|
||||
Day string `json:"day" gorm:"size:10;index;uniqueIndex:idx_user_daily_upload_day,priority:2"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Skill struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
OwnerID string `json:"ownerId" gorm:"index;size:36"`
|
||||
AuthorName string `json:"authorName" gorm:"size:120;index"`
|
||||
AuthorAvatarURL string `json:"authorAvatarUrl" gorm:"size:1000"`
|
||||
Name string `json:"name" gorm:"size:80;index"`
|
||||
Description string `json:"description" gorm:"size:500"`
|
||||
Instruction string `json:"instruction" gorm:"type:text"`
|
||||
Status int `json:"status" gorm:"index"`
|
||||
Source int `json:"source" gorm:"index"`
|
||||
Tag string `json:"tag" gorm:"size:32;index"`
|
||||
SortWeight int `json:"sortWeight" gorm:"index"`
|
||||
IsPrivate bool `json:"isPrivate" gorm:"index"`
|
||||
MarkdownURL string `json:"markdownUrl" gorm:"size:500"`
|
||||
ShowcaseMediaJSON string `json:"-" gorm:"type:text"`
|
||||
ExtraInfo string `json:"extraInfo" gorm:"type:text"`
|
||||
InitialLikeCount int64 `json:"initialLikeCount"`
|
||||
InitialAddedCount int64 `json:"initialAddedCount"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type UserSkillState struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_user_skill_state_user_skill,priority:1"`
|
||||
SkillID string `json:"skillId" gorm:"size:36;index;uniqueIndex:idx_user_skill_state_user_skill,priority:2"`
|
||||
Added bool `json:"added" gorm:"index"`
|
||||
Liked bool `json:"liked" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Resource struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_resources_user_created,priority:1"`
|
||||
Kind string `json:"kind" gorm:"index;size:24"`
|
||||
Status ResourceStatus `json:"status" gorm:"index;size:24"`
|
||||
Provider string `json:"provider" gorm:"size:24"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Bucket string `json:"bucket" gorm:"size:160"`
|
||||
// 用户 OSS 每次修改都会生成新版本,资源固定引用创建时的版本,避免历史资源因换密钥失效。
|
||||
StorageSettingID string `json:"-" gorm:"index;size:36"`
|
||||
ObjectKey string `json:"objectKey" gorm:"index"`
|
||||
PublicURL string `json:"publicUrl"`
|
||||
MimeType string `json:"mimeType" gorm:"size:120"`
|
||||
Size int64 `json:"size"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
ETag string `json:"etag" gorm:"size:160"`
|
||||
Error string `json:"error"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_resources_user_created,priority:2"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_assets_user_updated,priority:1"`
|
||||
Kind string `json:"kind" gorm:"index;size:24"`
|
||||
Category AssetCategory `json:"category" gorm:"index;size:32"`
|
||||
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
|
||||
PrimaryVersionID string `json:"primaryVersionId,omitempty" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_assets_user_updated,priority:2"`
|
||||
}
|
||||
|
||||
type ProjectAssetLink struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_project_asset_links_unique,priority:1"`
|
||||
AssetID string `json:"assetId" gorm:"index;size:36;uniqueIndex:idx_project_asset_links_unique,priority:2"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProjectAssetCandidate struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
UnitID string `json:"unitId,omitempty" gorm:"index;size:36"`
|
||||
ShotID string `json:"shotId,omitempty" gorm:"index;size:36"`
|
||||
Name string `json:"name" gorm:"size:240"`
|
||||
Category AssetCategory `json:"category" gorm:"index;size:32"`
|
||||
Status string `json:"status" gorm:"index;size:32"`
|
||||
DetailsJSON string `json:"detailsJson" gorm:"type:text"`
|
||||
ResolvedAssetID string `json:"resolvedAssetId,omitempty" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AssetVersion struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AssetID string `json:"assetId" gorm:"index;size:36;uniqueIndex:idx_asset_versions_number,priority:1"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_asset_versions_number,priority:2"`
|
||||
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
|
||||
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
|
||||
Prompt string `json:"prompt" gorm:"type:text"`
|
||||
Note string `json:"note" gorm:"size:500"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AssetRepresentation struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
TaskID string `json:"taskId,omitempty" gorm:"index;size:36;uniqueIndex:idx_asset_representations_task_role,priority:1"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_asset_representations_version_role,priority:1"`
|
||||
ResourceID string `json:"resourceId,omitempty" gorm:"index;size:36"`
|
||||
MediaType string `json:"mediaType" gorm:"index;size:24"`
|
||||
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_asset_representations_task_role,priority:2;uniqueIndex:idx_asset_representations_version_role,priority:2"`
|
||||
MetadataJSON string `json:"metadataJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// VoiceProfile 是可复用的声音身份;试听音频只是表现资源,不等同于声音本身。
|
||||
type VoiceProfile struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_voice_profiles_user_provider_key,priority:1"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Provider string `json:"provider" gorm:"size:48;uniqueIndex:idx_voice_profiles_user_provider_key,priority:2"`
|
||||
VoiceKey string `json:"voiceKey" gorm:"size:160;uniqueIndex:idx_voice_profiles_user_provider_key,priority:3"`
|
||||
Language string `json:"language" gorm:"size:80"`
|
||||
Timbre string `json:"timbre" gorm:"size:240"`
|
||||
SampleResourceID string `json:"sampleResourceId,omitempty" gorm:"index;size:36"`
|
||||
CompatibleModelsJSON string `json:"compatibleModelsJson" gorm:"type:text"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CharacterVoiceBinding struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"uniqueIndex;size:36"`
|
||||
VoiceProfileID string `json:"voiceProfileId" gorm:"index;size:36"`
|
||||
Instructions string `json:"instructions" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// Project 是短剧领域聚合根;CanvasProject 仍代表可独立创作的画布文档。
|
||||
type Project struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_projects_user_name,priority:1"`
|
||||
Name string `json:"name" gorm:"size:240;uniqueIndex:idx_projects_user_name,priority:2"`
|
||||
Type string `json:"type" gorm:"size:32;index"`
|
||||
AspectRatio string `json:"aspectRatio" gorm:"size:16"`
|
||||
SourceType string `json:"sourceType" gorm:"size:32"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
StylePresetID string `json:"stylePresetId" gorm:"size:64"`
|
||||
Status ProjectStatus `json:"status" gorm:"index;size:24"`
|
||||
Revision int64 `json:"revision"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type ProjectUnit struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
ParentID string `json:"parentId,omitempty" gorm:"index;size:36"`
|
||||
Kind ProjectUnitKind `json:"kind" gorm:"index;size:24"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
SourceText string `json:"sourceText" gorm:"type:text"`
|
||||
Status ProjectUnitStatus `json:"status" gorm:"index;size:24"`
|
||||
Position int `json:"position"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CanvasUnitLink struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:1"`
|
||||
CanvasID string `json:"canvasId" gorm:"index;size:80;uniqueIndex:idx_canvas_unit_links_unique,priority:2"`
|
||||
UnitID string `json:"unitId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:3"`
|
||||
Role string `json:"role" gorm:"size:32"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type Shot struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36"`
|
||||
UnitID string `json:"unitId" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Position int `json:"position"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ShotAssetReference struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ShotID string `json:"shotId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:1"`
|
||||
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:2"`
|
||||
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_shot_asset_reference_unique,priority:3"`
|
||||
Status string `json:"status" gorm:"index;size:24"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type WorkflowTemplateVersion struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
TemplateKey string `json:"templateKey" gorm:"size:80;uniqueIndex:idx_workflow_template_version,priority:1"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_workflow_template_version,priority:2"`
|
||||
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type WorkflowInstance struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:1"`
|
||||
UnitID string `json:"unitId,omitempty" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:2"`
|
||||
TemplateVersionID string `json:"templateVersionId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:3"`
|
||||
Scope string `json:"scope" gorm:"index;size:24"`
|
||||
Status WorkflowStatus `json:"status" gorm:"index;size:24"`
|
||||
Revision int64 `json:"revision"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowStepInstance struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
WorkflowInstanceID string `json:"workflowInstanceId" gorm:"index;size:36;uniqueIndex:idx_workflow_steps_instance_key,priority:1"`
|
||||
StepKey string `json:"stepKey" gorm:"size:80;uniqueIndex:idx_workflow_steps_instance_key,priority:2"`
|
||||
Name string `json:"name" gorm:"size:160"`
|
||||
Position int `json:"position"`
|
||||
Status WorkflowStepStatus `json:"status" gorm:"index;size:24"`
|
||||
InputJSON string `json:"inputJson" gorm:"type:text"`
|
||||
OutputJSON string `json:"outputJson" gorm:"type:text"`
|
||||
Error string `json:"error" gorm:"type:text"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
CompletedAt *time.Time `json:"completedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type WorkflowStepTask struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
WorkflowStepID string `json:"workflowStepId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:1"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:2"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type CanvasProject struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:80"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_canvas_projects_user_updated,priority:1"`
|
||||
ProjectID string `json:"projectId,omitempty" gorm:"index;size:36"`
|
||||
Title string `json:"title" gorm:"size:240"`
|
||||
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_canvas_projects_user_updated,priority:2"`
|
||||
}
|
||||
|
||||
type CanvasShare struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_canvas_share_owner_project,priority:1"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80;uniqueIndex:idx_canvas_share_owner_project,priority:2"`
|
||||
TokenHash string `json:"-" gorm:"uniqueIndex;size:64"`
|
||||
TokenCipher string `json:"-" gorm:"type:text"`
|
||||
Enabled bool `json:"enabled" gorm:"index"`
|
||||
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type PromptTemplate struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_prompt_template_operation_version,priority:1"`
|
||||
Name string `json:"name" gorm:"size:120"`
|
||||
Version int `json:"version" gorm:"uniqueIndex:idx_prompt_template_operation_version,priority:2"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
OutputType string `json:"outputType" gorm:"size:24"`
|
||||
Enabled bool `json:"enabled" gorm:"index;index:idx_prompt_template_active,priority:2"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// UserPromptCustomization 只保存用户的创作策略层,动态上下文和输出契约始终由服务端编译器注入。
|
||||
type UserPromptCustomization struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_user_prompt_operation,priority:1"`
|
||||
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_user_prompt_operation,priority:2"`
|
||||
Mode string `json:"mode" gorm:"size:24"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
BaseTemplateID string `json:"baseTemplateId" gorm:"size:36;index"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Announcement struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
Title string `json:"title" gorm:"size:120"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
Level AnnouncementLevel `json:"level" gorm:"index;size:24"`
|
||||
Status AnnouncementStatus `json:"status" gorm:"index;size:24;index:idx_announcements_status_published,priority:1"`
|
||||
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
|
||||
PublishedAt time.Time `json:"publishedAt" gorm:"index:idx_announcements_status_published,priority:2"`
|
||||
ClosedAt *time.Time `json:"closedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UserAnnouncementRead struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:1"`
|
||||
AnnouncementID string `json:"announcementId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:2"`
|
||||
ReadAt time.Time `json:"readAt"`
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Task struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_tasks_user_created,priority:1"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80"`
|
||||
Type string `json:"type" gorm:"index;size:64"`
|
||||
Status TaskStatus `json:"status" gorm:"index;size:24;index:idx_tasks_status_created,priority:1;index:idx_tasks_claim,priority:1;index:idx_tasks_provider_cancel,priority:1"`
|
||||
Stage string `json:"stage" gorm:"size:80"`
|
||||
Progress int `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
Operation string `json:"operation" gorm:"size:64"`
|
||||
Provider string `json:"provider" gorm:"size:64"`
|
||||
Model string `json:"model" gorm:"size:120"`
|
||||
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
|
||||
ProviderRequestID string `json:"providerRequestId,omitempty" gorm:"index;size:160"`
|
||||
ProviderCancelStatus ProviderCancelStatus `json:"providerCancelStatus,omitempty" gorm:"index;size:24;index:idx_tasks_provider_cancel,priority:2"`
|
||||
ProviderCancelError string `json:"providerCancelError,omitempty" gorm:"type:text"`
|
||||
ProviderCancelAttempts int `json:"providerCancelAttempts,omitempty"`
|
||||
ProviderCancelRequestedAt *time.Time `json:"providerCancelRequestedAt,omitempty"`
|
||||
ProviderCancelledAt *time.Time `json:"providerCancelledAt,omitempty"`
|
||||
ProviderCancelNextCheckAt *time.Time `json:"providerCancelNextCheckAt,omitempty" gorm:"index:idx_tasks_provider_cancel,priority:3"`
|
||||
PollStage string `json:"pollStage,omitempty" gorm:"size:32"`
|
||||
NextPollAt *time.Time `json:"nextPollAt,omitempty" gorm:"index"`
|
||||
LeaseOwner string `json:"-" gorm:"index;size:120"`
|
||||
LeaseExpiresAt *time.Time `json:"-" gorm:"index;index:idx_tasks_claim,priority:2"`
|
||||
InputJSON string `json:"inputJson" gorm:"type:text"`
|
||||
ResultJSON string `json:"resultJson" gorm:"type:text"`
|
||||
TextDraft string `json:"textDraft,omitempty" gorm:"type:text"`
|
||||
Error string `json:"error"`
|
||||
Attempts int `json:"attempts"`
|
||||
StartedAt *time.Time `json:"startedAt"`
|
||||
CompletedAt *time.Time `json:"completedAt"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_tasks_user_created,priority:2;index:idx_tasks_status_created,priority:2;index:idx_tasks_claim,priority:3"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// TaskTextDelta 只保存可回放窗口内的文本增量;最终正文和失败草稿分别归并到 Task.ResultJSON 与 Task.TextDraft。
|
||||
type TaskTextDelta struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36;index:idx_task_text_deltas_user_created,priority:1"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_task_text_deltas_sequence,priority:1"`
|
||||
Sequence int64 `json:"sequence" gorm:"uniqueIndex:idx_task_text_deltas_sequence,priority:2"`
|
||||
Content string `json:"content" gorm:"type:text"`
|
||||
ByteCount int64 `json:"byteCount"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_task_text_deltas_user_created,priority:2"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
ProjectID string `json:"projectId" gorm:"index;size:80"`
|
||||
Status SessionStatus `json:"status" gorm:"index;size:24"`
|
||||
Prompt string `json:"prompt"`
|
||||
CanvasSnapshotJSON string `json:"canvasSnapshotJson" gorm:"type:text"`
|
||||
CanvasOpsJSON string `json:"canvasOpsJson" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
Role string `json:"role" gorm:"size:24"`
|
||||
Content string `json:"content"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type TaskLog struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36"`
|
||||
Level string `json:"level" gorm:"size:24"`
|
||||
Message string `json:"message"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type SessionFile struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
FileName string `json:"fileName"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Path string `json:"-"`
|
||||
Size int64 `json:"size"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
ID string `json:"id" gorm:"primaryKey;size:36"`
|
||||
UserID string `json:"userId" gorm:"index;size:36"`
|
||||
TaskID string `json:"taskId" gorm:"index;size:36"`
|
||||
SessionID string `json:"sessionId" gorm:"index;size:36"`
|
||||
Kind string `json:"kind" gorm:"size:64"`
|
||||
URL string `json:"url"`
|
||||
Payload string `json:"payload" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
// BillingReviewStats 记录需要人工关注的长期未闭合订单数量。
|
||||
// 巡检只读数据库,不改变订单状态或积分余额。
|
||||
type BillingReviewStats struct {
|
||||
Reserved int64 `json:"reserved"`
|
||||
Running int64 `json:"running"`
|
||||
Uncertain int64 `json:"uncertain"`
|
||||
Oldest *time.Time `json:"oldest,omitempty"`
|
||||
}
|
||||
|
||||
func (s BillingReviewStats) Total() int64 {
|
||||
return s.Reserved + s.Running + s.Uncertain
|
||||
}
|
||||
|
||||
// StaleBillingReviewStats 查询超过 age 未更新、仍处于未闭合状态的订单。
|
||||
func (r *Repository) StaleBillingReviewStats(now time.Time, age time.Duration) (BillingReviewStats, error) {
|
||||
cutoff := now.Add(-age)
|
||||
stats := BillingReviewStats{}
|
||||
var rows []struct {
|
||||
Status model.BillingStatus
|
||||
Count int64
|
||||
}
|
||||
if err := r.db.Model(&model.BillingOrder{}).
|
||||
Select("status, count(*) AS count").
|
||||
Where("status IN ? AND updated_at < ?", []model.BillingStatus{
|
||||
model.BillingStatusReserved,
|
||||
model.BillingStatusRunning,
|
||||
model.BillingStatusUncertain,
|
||||
}, cutoff).
|
||||
Group("status").Scan(&rows).Error; err != nil {
|
||||
return stats, err
|
||||
}
|
||||
for _, row := range rows {
|
||||
switch row.Status {
|
||||
case model.BillingStatusReserved:
|
||||
stats.Reserved = row.Count
|
||||
case model.BillingStatusRunning:
|
||||
stats.Running = row.Count
|
||||
case model.BillingStatusUncertain:
|
||||
stats.Uncertain = row.Count
|
||||
}
|
||||
}
|
||||
if stats.Total() == 0 {
|
||||
return stats, nil
|
||||
}
|
||||
var oldest struct {
|
||||
CreatedAt time.Time
|
||||
}
|
||||
if err := r.db.Model(&model.BillingOrder{}).
|
||||
Select("created_at").
|
||||
Where("status IN ? AND updated_at < ?", []model.BillingStatus{
|
||||
model.BillingStatusReserved,
|
||||
model.BillingStatusRunning,
|
||||
model.BillingStatusUncertain,
|
||||
}, cutoff).
|
||||
Order("created_at asc").Limit(1).Scan(&oldest).Error; err != nil {
|
||||
return stats, err
|
||||
}
|
||||
stats.Oldest = &oldest.CreatedAt
|
||||
return stats, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
)
|
||||
|
||||
const billingReviewStaleAfter = 15 * time.Minute
|
||||
|
||||
// AuditBillingReview 执行只读账单巡检,提醒运维及时进入人工核对链路。
|
||||
// 资金状态必须由人工依据上游证据结算或退款,巡检不能代替资金动作。
|
||||
func (s *Service) AuditBillingReview() error {
|
||||
stats, err := s.repo.StaleBillingReviewStats(time.Now(), billingReviewStaleAfter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stats.Total() == 0 {
|
||||
return nil
|
||||
}
|
||||
oldest := ""
|
||||
if stats.Oldest != nil {
|
||||
oldest = stats.Oldest.UTC().Format(time.RFC3339)
|
||||
}
|
||||
log.Printf("billing review audit stale_orders=%d reserved=%d running=%d uncertain=%d oldest=%s", stats.Total(), stats.Reserved, stats.Running, stats.Uncertain, oldest)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) startBillingReviewAudit() {
|
||||
audit := func() {
|
||||
if err := s.AuditBillingReview(); err != nil {
|
||||
log.Printf("billing review audit failed: %v", err)
|
||||
}
|
||||
}
|
||||
audit()
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
audit()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1022,7 +1022,13 @@ func runVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]
|
||||
id := resumedProviderRequestID(ctx)
|
||||
var created map[string]interface{}
|
||||
if id == "" && (input.Config.InterfaceType == "xai-video" || isGrokVideoConfig(input.Config)) {
|
||||
requestBody, err := grokVideoBody(input)
|
||||
var requestBody interface{}
|
||||
var err error
|
||||
if input.Config.InterfaceType == "xai-video" {
|
||||
requestBody, err = xaiVideoRequestBody(input)
|
||||
} else {
|
||||
requestBody, err = grokVideoBody(input)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1109,21 +1115,21 @@ func runGeminiVeoVideoTask(ctx context.Context, input canvasGenerationInput) (ma
|
||||
}
|
||||
id := resumedProviderRequestID(ctx)
|
||||
if id == "" {
|
||||
instance := map[string]interface{}{"prompt": strings.TrimSpace(input.Prompt)}
|
||||
instance := geminiVeoInstance{Prompt: strings.TrimSpace(input.Prompt)}
|
||||
if len(input.ReferenceImages) == 1 {
|
||||
raw, mimeType, err := mediaBytes(input.ReferenceImages[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 Gemini Veo 起始图失败:%w", err)
|
||||
}
|
||||
instance["image"] = map[string]interface{}{"bytesBase64Encoded": base64.StdEncoding.EncodeToString(raw), "mimeType": mimeType}
|
||||
instance.Image = &geminiVeoImage{BytesBase64Encoded: base64.StdEncoding.EncodeToString(raw), MIMEType: mimeType}
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"instances": []interface{}{instance},
|
||||
"parameters": map[string]interface{}{
|
||||
"aspectRatio": normalizeNewAPIChannel2Ratio(input.Config.Size, strings.ToLower(input.Config.Model)),
|
||||
"durationSeconds": normalizeSeedanceVideosDuration(input.Config.VideoSeconds),
|
||||
"resolution": normalizeNewAPIChannel2Resolution(input.Config.VQuality, strings.ToLower(input.Config.Model)),
|
||||
"sampleCount": 1,
|
||||
body := geminiVeoRequest{
|
||||
Instances: []geminiVeoInstance{instance},
|
||||
Parameters: geminiVeoParameters{
|
||||
AspectRatio: normalizeNewAPIChannel2Ratio(input.Config.Size, strings.ToLower(input.Config.Model)),
|
||||
DurationSeconds: normalizeSeedanceVideosDuration(input.Config.VideoSeconds),
|
||||
Resolution: normalizeNewAPIChannel2Resolution(input.Config.VQuality, strings.ToLower(input.Config.Model)),
|
||||
SampleCount: 1,
|
||||
},
|
||||
}
|
||||
var created map[string]interface{}
|
||||
@@ -1273,7 +1279,7 @@ func runNewAPIChannel2VideoTask(ctx context.Context, input canvasGenerationInput
|
||||
id := resumedProviderRequestID(ctx)
|
||||
var created map[string]interface{}
|
||||
if id == "" {
|
||||
body, err := newAPIChannel2VideoBody(input)
|
||||
body, err := newAPIChannel2VideoRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1393,9 +1399,9 @@ func logNewAPIChannel2QueryRetry(ctx context.Context, providerTaskID string, ret
|
||||
_ = metadata.Service.log(metadata.UserID, metadata.TaskID, "warn", "上游任务查询失败,1 分钟后重试", payload)
|
||||
}
|
||||
|
||||
func newAPIChannel2VideoBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
func newAPIChannel2VideoRequestBody(input canvasGenerationInput) (newAPIVideoRequest, error) {
|
||||
if len(input.ReferenceImages) > 9 || len(input.ReferenceVideos) > 3 || len(input.ReferenceAudios) > 3 {
|
||||
return nil, errors.New("NewAPI Video Generations 最多支持 9 张参考图、3 个参考视频和 3 个参考音频")
|
||||
return newAPIVideoRequest{}, errors.New("NewAPI Video Generations 最多支持 9 张参考图、3 个参考视频和 3 个参考音频")
|
||||
}
|
||||
modelName := strings.ToLower(strings.TrimSpace(input.Config.Model))
|
||||
requiresSingleImage := modelName == "grok-video-1.5" || modelName == "grok-video-1.5-1080p"
|
||||
@@ -1405,19 +1411,19 @@ func newAPIChannel2VideoBody(input canvasGenerationInput) (map[string]interface{
|
||||
for _, image := range input.ReferenceImages {
|
||||
url, err := videoGenerationsMediaURL(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return newAPIVideoRequest{}, err
|
||||
}
|
||||
images = append(images, url)
|
||||
}
|
||||
}
|
||||
if requiresSingleImage {
|
||||
if len(images) != 1 {
|
||||
return nil, fmt.Errorf("NewAPI Video Generations 的 %s 必须且只能提供 1 张参考图(当前 %d 张)", input.Config.Model, len(images))
|
||||
return newAPIVideoRequest{}, fmt.Errorf("NewAPI Video Generations 的 %s 必须且只能提供 1 张参考图(当前 %d 张)", input.Config.Model, len(images))
|
||||
}
|
||||
}
|
||||
frameImages, err := videoFrameImageURLs(input, images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return newAPIVideoRequest{}, err
|
||||
}
|
||||
if len(frameImages) > 0 {
|
||||
images = frameImages
|
||||
@@ -1429,44 +1435,54 @@ func newAPIChannel2VideoBody(input canvasGenerationInput) (map[string]interface{
|
||||
}
|
||||
ratio := normalizeNewAPIChannel2Ratio(input.Config.Size, modelName)
|
||||
resolution := normalizeNewAPIChannel2Resolution(input.Config.VQuality, modelName)
|
||||
body := map[string]interface{}{
|
||||
"model": input.Config.Model,
|
||||
"prompt": strings.TrimSpace(input.Prompt),
|
||||
"seconds": strconv.Itoa(seconds),
|
||||
"aspect_ratio": ratio,
|
||||
"resolution": resolution,
|
||||
body := newAPIVideoRequest{
|
||||
Model: input.Config.Model,
|
||||
Prompt: strings.TrimSpace(input.Prompt),
|
||||
Seconds: strconv.Itoa(seconds),
|
||||
AspectRatio: ratio,
|
||||
Resolution: resolution,
|
||||
}
|
||||
if videoCapabilitySupportsAudio(input) {
|
||||
body["generate_audio"] = parseBool(input.Config.VideoGenerateAudio, true)
|
||||
value := parseBool(input.Config.VideoGenerateAudio, true)
|
||||
body.GenerateAudio = &value
|
||||
}
|
||||
if len(images) > 0 {
|
||||
body["image_urls"] = images
|
||||
body.ImageURLs = images
|
||||
}
|
||||
videoURLs := make([]string, 0, len(input.ReferenceVideos))
|
||||
for _, video := range input.ReferenceVideos {
|
||||
url, err := videoGenerationsMediaURL(video)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return newAPIVideoRequest{}, err
|
||||
}
|
||||
videoURLs = append(videoURLs, url)
|
||||
}
|
||||
if len(videoURLs) > 0 {
|
||||
body["video_urls"] = videoURLs
|
||||
body.VideoURLs = videoURLs
|
||||
}
|
||||
audioURLs := make([]string, 0, len(input.ReferenceAudios))
|
||||
for _, audio := range input.ReferenceAudios {
|
||||
url, err := videoGenerationsMediaURL(audio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return newAPIVideoRequest{}, err
|
||||
}
|
||||
audioURLs = append(audioURLs, url)
|
||||
}
|
||||
if len(audioURLs) > 0 {
|
||||
body["audio_urls"] = audioURLs
|
||||
body.AudioURLs = audioURLs
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// 兼容现有单元测试和旧调用方;实际请求路径使用上面的类型化 DTO。
|
||||
func newAPIChannel2VideoBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
body, err := newAPIChannel2VideoRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return requestAsMap(body)
|
||||
}
|
||||
|
||||
func videoGenerationsMediaURL(media providerMedia) (string, error) {
|
||||
value := strings.TrimSpace(firstNonEmpty(media.URL, media.DataURL))
|
||||
if isPublicMediaURL(value) || strings.HasPrefix(value, "data:") {
|
||||
@@ -1680,7 +1696,11 @@ func validateGenerationInterface(mode string, interfaceType string) error {
|
||||
|
||||
func grokVideoBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
if input.Config.InterfaceType == "xai-video" {
|
||||
return xaiVideoBody(input)
|
||||
body, err := xaiVideoRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return requestAsMap(body)
|
||||
}
|
||||
|
||||
seconds := defaultString(input.Config.VideoSeconds, "6")
|
||||
@@ -1713,33 +1733,42 @@ func grokVideoBody(input canvasGenerationInput) (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// xAI 生成接口与 legacy /videos 使用不同字段,保持独立可避免兼容字段触发上游 422。
|
||||
func xaiVideoBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
body := map[string]interface{}{
|
||||
"model": input.Config.Model,
|
||||
"prompt": strings.TrimSpace(input.Prompt),
|
||||
"duration": normalizeXAIVideoDuration(input.Config.VideoSeconds),
|
||||
"aspect_ratio": normalizeXAIVideoAspectRatio(input.Config.Size),
|
||||
"resolution": normalizeXAIVideoResolution(input.Config.VQuality),
|
||||
func xaiVideoRequestBody(input canvasGenerationInput) (xaiVideoRequest, error) {
|
||||
body := xaiVideoRequest{
|
||||
Model: input.Config.Model,
|
||||
Prompt: strings.TrimSpace(input.Prompt),
|
||||
Duration: normalizeXAIVideoDuration(input.Config.VideoSeconds),
|
||||
AspectRatio: normalizeXAIVideoAspectRatio(input.Config.Size),
|
||||
Resolution: normalizeXAIVideoResolution(input.Config.VQuality),
|
||||
}
|
||||
if !shouldSendNewAPIVideoImages(input) || len(input.ReferenceImages) == 0 {
|
||||
return body, nil
|
||||
}
|
||||
if len(input.ReferenceImages) > 1 {
|
||||
return nil, fmt.Errorf("xAI 图生视频只支持 1 张起始图,当前连接了 %d 张", len(input.ReferenceImages))
|
||||
return xaiVideoRequest{}, fmt.Errorf("xAI 图生视频只支持 1 张起始图,当前连接了 %d 张", len(input.ReferenceImages))
|
||||
}
|
||||
imageURL, err := openAIImageInputURL(input.ReferenceImages[0])
|
||||
if err != nil {
|
||||
return xaiVideoRequest{}, err
|
||||
}
|
||||
body.Image = &xaiVideoImage{URL: imageURL}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// 兼容旧的 map 断言调用;xAI 实际请求使用类型化 DTO。
|
||||
func xaiVideoBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
body, err := xaiVideoRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body["image"] = map[string]interface{}{"url": imageURL}
|
||||
return body, nil
|
||||
return requestAsMap(body)
|
||||
}
|
||||
|
||||
func runSeedanceVideosTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
id := resumedProviderRequestID(ctx)
|
||||
var created map[string]interface{}
|
||||
if id == "" {
|
||||
body, err := seedanceVideosBody(input)
|
||||
body, err := seedanceVideosRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1807,18 +1836,20 @@ func runSeedanceAgentPlanVideoTask(ctx context.Context, input canvasGenerationIn
|
||||
}
|
||||
}
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"model": input.Config.Model,
|
||||
"content": content,
|
||||
"ratio": normalizeSeedanceRatio(input.Config.Size),
|
||||
"resolution": normalizeSeedanceResolution(input.Config.VQuality, input.Config.Model),
|
||||
"duration": normalizeSeedanceDuration(input.Config.VideoSeconds),
|
||||
body := seedanceAgentPlanRequest{
|
||||
Model: input.Config.Model,
|
||||
Content: content,
|
||||
Ratio: normalizeSeedanceRatio(input.Config.Size),
|
||||
Resolution: normalizeSeedanceResolution(input.Config.VQuality, input.Config.Model),
|
||||
Duration: normalizeSeedanceDuration(input.Config.VideoSeconds),
|
||||
}
|
||||
if videoCapabilitySupportsAudio(input) {
|
||||
body["generate_audio"] = parseBool(input.Config.VideoGenerateAudio, true)
|
||||
value := parseBool(input.Config.VideoGenerateAudio, true)
|
||||
body.GenerateAudio = &value
|
||||
}
|
||||
if videoCapabilitySupportsWatermark(input) {
|
||||
body["watermark"] = parseBool(input.Config.VideoWatermark, false)
|
||||
value := parseBool(input.Config.VideoWatermark, false)
|
||||
body.Watermark = &value
|
||||
}
|
||||
if err := postJSON(ctx, input.Config, "/contents/generations/tasks", body, &created); err != nil {
|
||||
return nil, err
|
||||
@@ -2364,64 +2395,74 @@ func newAPIVideoPromptText(input canvasGenerationInput) string {
|
||||
return strings.TrimSpace(input.Prompt)
|
||||
}
|
||||
|
||||
func seedanceVideosBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
func seedanceVideosRequestBody(input canvasGenerationInput) (seedanceVideosRequest, error) {
|
||||
if (len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0) && len(input.ReferenceImages) == 0 {
|
||||
return nil, errors.New("Seedance 参考视频或参考音频需要同时连接至少 1 张主参考图")
|
||||
return seedanceVideosRequest{}, errors.New("Seedance 参考视频或参考音频需要同时连接至少 1 张主参考图")
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"model": input.Config.Model,
|
||||
"prompt": seedanceVideosPromptText(input),
|
||||
"aspect_ratio": normalizeSeedanceVideosRatio(input.Config.Size),
|
||||
"duration": normalizeSeedanceVideosDuration(input.Config.VideoSeconds),
|
||||
body := seedanceVideosRequest{
|
||||
Model: input.Config.Model,
|
||||
Prompt: seedanceVideosPromptText(input),
|
||||
AspectRatio: normalizeSeedanceVideosRatio(input.Config.Size),
|
||||
Duration: normalizeSeedanceVideosDuration(input.Config.VideoSeconds),
|
||||
}
|
||||
if videoCapabilitySupportsAudio(input) {
|
||||
body["generate_audio"] = parseBool(input.Config.VideoGenerateAudio, true)
|
||||
value := parseBool(input.Config.VideoGenerateAudio, true)
|
||||
body.GenerateAudio = &value
|
||||
}
|
||||
imageURLs := make([]string, 0, len(input.ReferenceImages))
|
||||
for _, image := range input.ReferenceImages {
|
||||
url, err := openAIImageInputURL(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return seedanceVideosRequest{}, err
|
||||
}
|
||||
imageURLs = append(imageURLs, url)
|
||||
}
|
||||
frameImageURLs, err := videoFrameImageURLs(input, imageURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return seedanceVideosRequest{}, err
|
||||
}
|
||||
if len(frameImageURLs) > 0 {
|
||||
body["image_urls"] = frameImageURLs
|
||||
body.ImageURLs = frameImageURLs
|
||||
} else if len(imageURLs) > 0 {
|
||||
body["image_url"] = imageURLs[0]
|
||||
body.ImageURL = imageURLs[0]
|
||||
if len(imageURLs) > 1 {
|
||||
body["reference_image_urls"] = imageURLs[1:]
|
||||
body.ReferenceImageURLs = imageURLs[1:]
|
||||
}
|
||||
}
|
||||
videoURLs := make([]string, 0, len(input.ReferenceVideos))
|
||||
for _, video := range input.ReferenceVideos {
|
||||
url, err := seedanceVideosMediaURL(video)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return seedanceVideosRequest{}, err
|
||||
}
|
||||
videoURLs = append(videoURLs, url)
|
||||
}
|
||||
if len(videoURLs) > 0 {
|
||||
body["reference_videos"] = videoURLs
|
||||
body.ReferenceVideos = videoURLs
|
||||
}
|
||||
audioURLs := make([]string, 0, len(input.ReferenceAudios))
|
||||
for _, audio := range input.ReferenceAudios {
|
||||
url, err := seedanceVideosMediaURL(audio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return seedanceVideosRequest{}, err
|
||||
}
|
||||
audioURLs = append(audioURLs, url)
|
||||
}
|
||||
if len(audioURLs) > 0 {
|
||||
body["reference_audios"] = audioURLs
|
||||
body.ReferenceAudios = audioURLs
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// 兼容旧的 map 断言调用;实际请求路径使用类型化 Seedance DTO。
|
||||
func seedanceVideosBody(input canvasGenerationInput) (map[string]interface{}, error) {
|
||||
body, err := seedanceVideosRequestBody(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return requestAsMap(body)
|
||||
}
|
||||
|
||||
func seedancePromptText(input canvasGenerationInput) string {
|
||||
return strings.TrimSpace(input.Prompt)
|
||||
}
|
||||
@@ -2526,248 +2567,3 @@ func seedanceErrorMessage(state map[string]interface{}) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isPublicMediaURL(value string) bool {
|
||||
lower := strings.ToLower(value)
|
||||
return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://")
|
||||
}
|
||||
|
||||
func isSeedanceVideoConfig(config providerConfig) bool {
|
||||
model := strings.ToLower(config.Model)
|
||||
return strings.Contains(model, "seedance") || strings.Contains(model, "doubao-seedance") || isArkPlanVideoConfig(config)
|
||||
}
|
||||
|
||||
func isGrokVideoConfig(config providerConfig) bool {
|
||||
return strings.Contains(strings.ToLower(strings.TrimSpace(config.Model)), "grok")
|
||||
}
|
||||
|
||||
func isArkPlanVideoConfig(config providerConfig) bool {
|
||||
return strings.Contains(strings.ToLower(config.BaseURL), "/api/plan/v3")
|
||||
}
|
||||
|
||||
func normalizeImageQuality(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "1k":
|
||||
return "low"
|
||||
case "2k":
|
||||
return "medium"
|
||||
case "4k":
|
||||
return "high"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func normalizePixelSize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" {
|
||||
return ""
|
||||
}
|
||||
// 画布按比例保存常用预设;图片接口只接受像素尺寸,必须在请求边界完成转换。
|
||||
switch value {
|
||||
case "1:1":
|
||||
return "1024x1024"
|
||||
case "3:2":
|
||||
return "1536x1024"
|
||||
case "2:3":
|
||||
return "1024x1536"
|
||||
case "4:3":
|
||||
return "1360x1024"
|
||||
case "3:4":
|
||||
return "1024x1360"
|
||||
case "16:9":
|
||||
return "1824x1024"
|
||||
case "9:16":
|
||||
return "1024x1824"
|
||||
case "21:9":
|
||||
return "2352x1008"
|
||||
}
|
||||
if strings.Contains(value, "x") {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeVideoSize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(value, "x") {
|
||||
return value
|
||||
}
|
||||
if value == "9:16" || value == "2:3" || value == "3:4" {
|
||||
return "720x1280"
|
||||
}
|
||||
return "1280x720"
|
||||
}
|
||||
|
||||
func normalizeVideoResolution(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" || value == "medium" || value == "high" {
|
||||
return "720p"
|
||||
}
|
||||
if value == "low" {
|
||||
return "480p"
|
||||
}
|
||||
if strings.EqualFold(value, "4k") {
|
||||
return "2160p"
|
||||
}
|
||||
if strings.HasSuffix(value, "p") {
|
||||
return value
|
||||
}
|
||||
return value + "p"
|
||||
}
|
||||
|
||||
func normalizeXAIVideoDuration(value string) int {
|
||||
duration, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || duration <= 0 {
|
||||
return 6
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
func normalizeXAIVideoResolution(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "480", "480p", "low":
|
||||
return "480p"
|
||||
case "1080", "1080p":
|
||||
return "1080p"
|
||||
case "2160", "2160p", "4k":
|
||||
return "2160p"
|
||||
default:
|
||||
return "720p"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeXAIVideoAspectRatio(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
allowed := map[string]bool{
|
||||
"1:1": true, "16:9": true, "9:16": true, "4:3": true,
|
||||
"3:4": true, "3:2": true, "2:3": true,
|
||||
}
|
||||
if allowed[value] {
|
||||
return value
|
||||
}
|
||||
parts := strings.Split(value, "x")
|
||||
if len(parts) != 2 {
|
||||
return "16:9"
|
||||
}
|
||||
width, widthErr := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
height, heightErr := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 {
|
||||
return "16:9"
|
||||
}
|
||||
ratio := float64(width) / float64(height)
|
||||
candidates := []struct {
|
||||
name string
|
||||
ratio float64
|
||||
}{
|
||||
{name: "1:1", ratio: 1},
|
||||
{name: "16:9", ratio: 16.0 / 9},
|
||||
{name: "9:16", ratio: 9.0 / 16},
|
||||
{name: "4:3", ratio: 4.0 / 3},
|
||||
{name: "3:4", ratio: 3.0 / 4},
|
||||
{name: "3:2", ratio: 3.0 / 2},
|
||||
{name: "2:3", ratio: 2.0 / 3},
|
||||
}
|
||||
bestName := "16:9"
|
||||
bestDifference := 2.0
|
||||
for _, candidate := range candidates {
|
||||
difference := ratio - candidate.ratio
|
||||
if difference < 0 {
|
||||
difference = -difference
|
||||
}
|
||||
if difference < bestDifference {
|
||||
bestName = candidate.name
|
||||
bestDifference = difference
|
||||
}
|
||||
}
|
||||
return bestName
|
||||
}
|
||||
|
||||
func normalizeSeedanceDuration(value string) int {
|
||||
if strings.TrimSpace(value) == "-1" {
|
||||
return -1
|
||||
}
|
||||
seconds, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || seconds <= 0 {
|
||||
return 5
|
||||
}
|
||||
return seconds
|
||||
}
|
||||
|
||||
func normalizeSeedanceVideosDuration(value string) int {
|
||||
return normalizeSeedanceDuration(value)
|
||||
}
|
||||
|
||||
func normalizeSeedanceRatio(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" || value == "adaptive" {
|
||||
return "adaptive"
|
||||
}
|
||||
switch value {
|
||||
case "16:9", "9:16", "1:1", "4:3", "3:4", "21:9":
|
||||
return value
|
||||
default:
|
||||
return "adaptive"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeSeedanceVideosRatio(value string) string {
|
||||
ratio := normalizeSeedanceRatio(value)
|
||||
if ratio == "adaptive" {
|
||||
return "16:9"
|
||||
}
|
||||
return ratio
|
||||
}
|
||||
|
||||
func normalizeSeedanceResolution(value string, model string) string {
|
||||
resolution := strings.TrimSuffix(strings.TrimSpace(value), "p")
|
||||
if strings.EqualFold(resolution, "4k") {
|
||||
resolution = "2160"
|
||||
}
|
||||
switch resolution {
|
||||
case "480", "720", "1080", "2160":
|
||||
default:
|
||||
if value == "low" {
|
||||
resolution = "480"
|
||||
} else {
|
||||
resolution = "720"
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(model), "fast") && (resolution == "1080" || resolution == "2160") {
|
||||
resolution = "720"
|
||||
}
|
||||
return resolution + "p"
|
||||
}
|
||||
|
||||
func parseBool(value string, fallback bool) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
func parseFloat(value string, fallback float64) float64 {
|
||||
number, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
if err != nil || number == 0 {
|
||||
return fallback
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
func sleepContext(ctx context.Context, duration time.Duration) error {
|
||||
timer := time.NewTimer(duration)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-timer.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package service
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func requestAsMap(value interface{}) (map[string]interface{}, error) {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type newAPIVideoRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Seconds string `json:"seconds"`
|
||||
AspectRatio string `json:"aspect_ratio"`
|
||||
Resolution string `json:"resolution"`
|
||||
GenerateAudio *bool `json:"generate_audio,omitempty"`
|
||||
ImageURLs []string `json:"image_urls,omitempty"`
|
||||
VideoURLs []string `json:"video_urls,omitempty"`
|
||||
AudioURLs []string `json:"audio_urls,omitempty"`
|
||||
}
|
||||
|
||||
type seedanceVideosRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
AspectRatio string `json:"aspect_ratio"`
|
||||
Duration int `json:"duration"`
|
||||
GenerateAudio *bool `json:"generate_audio,omitempty"`
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
ReferenceImageURLs []string `json:"reference_image_urls,omitempty"`
|
||||
ImageURLs []string `json:"image_urls,omitempty"`
|
||||
ReferenceVideos []string `json:"reference_videos,omitempty"`
|
||||
ReferenceAudios []string `json:"reference_audios,omitempty"`
|
||||
}
|
||||
|
||||
type xaiVideoRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Duration int `json:"duration"`
|
||||
AspectRatio string `json:"aspect_ratio"`
|
||||
Resolution string `json:"resolution"`
|
||||
Image *xaiVideoImage `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
type xaiVideoImage struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type geminiVeoRequest struct {
|
||||
Instances []geminiVeoInstance `json:"instances"`
|
||||
Parameters geminiVeoParameters `json:"parameters"`
|
||||
}
|
||||
|
||||
type geminiVeoInstance struct {
|
||||
Prompt string `json:"prompt"`
|
||||
Image *geminiVeoImage `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
type geminiVeoImage struct {
|
||||
BytesBase64Encoded string `json:"bytesBase64Encoded"`
|
||||
MIMEType string `json:"mimeType"`
|
||||
}
|
||||
|
||||
type geminiVeoParameters struct {
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
DurationSeconds int `json:"durationSeconds"`
|
||||
Resolution string `json:"resolution"`
|
||||
SampleCount int `json:"sampleCount"`
|
||||
}
|
||||
|
||||
type seedanceAgentPlanRequest struct {
|
||||
Model string `json:"model"`
|
||||
Content []map[string]interface{} `json:"content"`
|
||||
Ratio string `json:"ratio"`
|
||||
Resolution string `json:"resolution"`
|
||||
Duration int `json:"duration"`
|
||||
GenerateAudio *bool `json:"generate_audio,omitempty"`
|
||||
Watermark *bool `json:"watermark,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func isPublicMediaURL(value string) bool {
|
||||
lower := strings.ToLower(value)
|
||||
return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://")
|
||||
}
|
||||
|
||||
func isSeedanceVideoConfig(config providerConfig) bool {
|
||||
model := strings.ToLower(config.Model)
|
||||
return strings.Contains(model, "seedance") || strings.Contains(model, "doubao-seedance") || isArkPlanVideoConfig(config)
|
||||
}
|
||||
|
||||
func isGrokVideoConfig(config providerConfig) bool {
|
||||
return strings.Contains(strings.ToLower(strings.TrimSpace(config.Model)), "grok")
|
||||
}
|
||||
|
||||
func isArkPlanVideoConfig(config providerConfig) bool {
|
||||
return strings.Contains(strings.ToLower(config.BaseURL), "/api/plan/v3")
|
||||
}
|
||||
|
||||
func normalizeImageQuality(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "1k":
|
||||
return "low"
|
||||
case "2k":
|
||||
return "medium"
|
||||
case "4k":
|
||||
return "high"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func normalizePixelSize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" {
|
||||
return ""
|
||||
}
|
||||
// 画布按比例保存常用预设;图片接口只接受像素尺寸,必须在请求边界完成转换。
|
||||
switch value {
|
||||
case "1:1":
|
||||
return "1024x1024"
|
||||
case "3:2":
|
||||
return "1536x1024"
|
||||
case "2:3":
|
||||
return "1024x1536"
|
||||
case "4:3":
|
||||
return "1360x1024"
|
||||
case "3:4":
|
||||
return "1024x1360"
|
||||
case "16:9":
|
||||
return "1824x1024"
|
||||
case "9:16":
|
||||
return "1024x1824"
|
||||
case "21:9":
|
||||
return "2352x1008"
|
||||
}
|
||||
if strings.Contains(value, "x") {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeVideoSize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(value, "x") {
|
||||
return value
|
||||
}
|
||||
if value == "9:16" || value == "2:3" || value == "3:4" {
|
||||
return "720x1280"
|
||||
}
|
||||
return "1280x720"
|
||||
}
|
||||
|
||||
func normalizeVideoResolution(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" || value == "medium" || value == "high" {
|
||||
return "720p"
|
||||
}
|
||||
if value == "low" {
|
||||
return "480p"
|
||||
}
|
||||
if strings.EqualFold(value, "4k") {
|
||||
return "2160p"
|
||||
}
|
||||
if strings.HasSuffix(value, "p") {
|
||||
return value
|
||||
}
|
||||
return value + "p"
|
||||
}
|
||||
|
||||
func normalizeXAIVideoDuration(value string) int {
|
||||
duration, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || duration <= 0 {
|
||||
return 6
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
func normalizeXAIVideoResolution(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "480", "480p", "low":
|
||||
return "480p"
|
||||
case "1080", "1080p":
|
||||
return "1080p"
|
||||
case "2160", "2160p", "4k":
|
||||
return "2160p"
|
||||
default:
|
||||
return "720p"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeXAIVideoAspectRatio(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
allowed := map[string]bool{
|
||||
"1:1": true, "16:9": true, "9:16": true, "4:3": true,
|
||||
"3:4": true, "3:2": true, "2:3": true,
|
||||
}
|
||||
if allowed[value] {
|
||||
return value
|
||||
}
|
||||
parts := strings.Split(value, "x")
|
||||
if len(parts) != 2 {
|
||||
return "16:9"
|
||||
}
|
||||
width, widthErr := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
height, heightErr := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 {
|
||||
return "16:9"
|
||||
}
|
||||
ratio := float64(width) / float64(height)
|
||||
candidates := []struct {
|
||||
name string
|
||||
ratio float64
|
||||
}{
|
||||
{name: "1:1", ratio: 1},
|
||||
{name: "16:9", ratio: 16.0 / 9},
|
||||
{name: "9:16", ratio: 9.0 / 16},
|
||||
{name: "4:3", ratio: 4.0 / 3},
|
||||
{name: "3:4", ratio: 3.0 / 4},
|
||||
{name: "3:2", ratio: 3.0 / 2},
|
||||
{name: "2:3", ratio: 2.0 / 3},
|
||||
}
|
||||
bestName := "16:9"
|
||||
bestDifference := 2.0
|
||||
for _, candidate := range candidates {
|
||||
difference := ratio - candidate.ratio
|
||||
if difference < 0 {
|
||||
difference = -difference
|
||||
}
|
||||
if difference < bestDifference {
|
||||
bestName = candidate.name
|
||||
bestDifference = difference
|
||||
}
|
||||
}
|
||||
return bestName
|
||||
}
|
||||
|
||||
func normalizeSeedanceDuration(value string) int {
|
||||
if strings.TrimSpace(value) == "-1" {
|
||||
return -1
|
||||
}
|
||||
seconds, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil || seconds <= 0 {
|
||||
return 5
|
||||
}
|
||||
return seconds
|
||||
}
|
||||
|
||||
func normalizeSeedanceVideosDuration(value string) int {
|
||||
return normalizeSeedanceDuration(value)
|
||||
}
|
||||
|
||||
func normalizeSeedanceRatio(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || value == "auto" || value == "adaptive" {
|
||||
return "adaptive"
|
||||
}
|
||||
switch value {
|
||||
case "16:9", "9:16", "1:1", "4:3", "3:4", "21:9":
|
||||
return value
|
||||
default:
|
||||
return "adaptive"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeSeedanceVideosRatio(value string) string {
|
||||
ratio := normalizeSeedanceRatio(value)
|
||||
if ratio == "adaptive" {
|
||||
return "16:9"
|
||||
}
|
||||
return ratio
|
||||
}
|
||||
|
||||
func normalizeSeedanceResolution(value string, model string) string {
|
||||
resolution := strings.TrimSuffix(strings.TrimSpace(value), "p")
|
||||
if strings.EqualFold(resolution, "4k") {
|
||||
resolution = "2160"
|
||||
}
|
||||
switch resolution {
|
||||
case "480", "720", "1080", "2160":
|
||||
default:
|
||||
if value == "low" {
|
||||
resolution = "480"
|
||||
} else {
|
||||
resolution = "720"
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(model), "fast") && (resolution == "1080" || resolution == "2160") {
|
||||
resolution = "720"
|
||||
}
|
||||
return resolution + "p"
|
||||
}
|
||||
|
||||
func parseBool(value string, fallback bool) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
func parseFloat(value string, fallback float64) float64 {
|
||||
number, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
if err != nil || number == 0 {
|
||||
return fallback
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
func sleepContext(ctx context.Context, duration time.Duration) error {
|
||||
timer := time.NewTimer(duration)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-timer.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -164,20 +164,20 @@ type agentStoryboardPlan struct {
|
||||
}
|
||||
|
||||
type agentStoryboardShot struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Duration int `json:"durationSeconds"`
|
||||
Dialogue string `json:"dialogue"`
|
||||
ShotSize string `json:"shotSize"`
|
||||
Emotion string `json:"emotion"`
|
||||
Lighting string `json:"lightingAndAtmosphere"`
|
||||
AudioEffects string `json:"audioEffects"`
|
||||
VisualPrompt string `json:"visualPrompt"`
|
||||
VideoPrompt string `json:"videoPrompt"`
|
||||
Camera string `json:"camera"`
|
||||
Motion string `json:"motion"`
|
||||
TimeBeats string `json:"timeBeats"`
|
||||
Negative string `json:"negativePrompt"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Duration int `json:"durationSeconds"`
|
||||
Dialogue string `json:"dialogue"`
|
||||
ShotSize string `json:"shotSize"`
|
||||
Emotion string `json:"emotion"`
|
||||
Lighting string `json:"lightingAndAtmosphere"`
|
||||
AudioEffects string `json:"audioEffects"`
|
||||
VisualPrompt string `json:"visualPrompt"`
|
||||
VideoPrompt string `json:"videoPrompt"`
|
||||
Camera string `json:"camera"`
|
||||
Motion string `json:"motion"`
|
||||
TimeBeats string `json:"timeBeats"`
|
||||
Negative string `json:"negativePrompt"`
|
||||
AssetTags []string `json:"assetTags"`
|
||||
CharacterIDs []string `json:"characterIds"`
|
||||
Intent string `json:"narrativeIntent"`
|
||||
@@ -196,6 +196,7 @@ func New(repo *repository.Repository, dataDir string) *Service {
|
||||
func (s *Service) StartWorker() {
|
||||
s.startTextReplayCleanup()
|
||||
s.startProviderCancellationReconciliation()
|
||||
s.startBillingReviewAudit()
|
||||
go func() {
|
||||
slots := make(chan struct{}, maxChannelConcurrencyLimit)
|
||||
dispatch := func() {
|
||||
@@ -1763,10 +1764,10 @@ func shotDescription(shot agentStoryboardShot) string {
|
||||
func storyboardImagePromptValues(projectStyle string, styleGuide string, shot agentStoryboardShot) map[string]string {
|
||||
negative := defaultString(strings.TrimSpace(shot.Negative), "禁止换脸、服装变化、手部畸形、乱码、风格突变和塑料材质")
|
||||
return map[string]string{
|
||||
"项目视觉": storyboardProjectVisualSummary(projectStyle, styleGuide),
|
||||
"首帧构图": compactPromptText(shot.VisualPrompt+";光影:"+shot.Lighting, 360),
|
||||
"项目视觉": storyboardProjectVisualSummary(projectStyle, styleGuide),
|
||||
"首帧构图": compactPromptText(shot.VisualPrompt+";光影:"+shot.Lighting, 360),
|
||||
"表演起始状态": compactPromptText(shot.Performance, 180),
|
||||
"负面要求": compactPromptText(negative, 140),
|
||||
"负面要求": compactPromptText(negative, 140),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1787,15 +1788,15 @@ func storyboardVideoPromptValues(projectStyle string, styleGuide string, shot ag
|
||||
timeBeats := defaultString(strings.TrimSpace(shot.TimeBeats), fmt.Sprintf("0-%d秒:%s", shot.Duration, strings.TrimSpace(shot.Description)))
|
||||
negative := defaultString(strings.TrimSpace(shot.Negative), "禁止换脸、服装变化、手部畸形、乱码、闪烁、风格突变和动作僵硬")
|
||||
values := map[string]string{
|
||||
"项目视觉": storyboardProjectVisualSummary(projectStyle, styleGuide),
|
||||
"镜头意图": compactPromptText(shot.Intent+";观众视点:"+shot.ViewerPOV+";情绪:"+shot.Emotion, 150),
|
||||
"首帧构图": compactPromptText(shot.VisualPrompt+";光影:"+shot.Lighting, 280),
|
||||
"项目视觉": storyboardProjectVisualSummary(projectStyle, styleGuide),
|
||||
"镜头意图": compactPromptText(shot.Intent+";观众视点:"+shot.ViewerPOV+";情绪:"+shot.Emotion, 150),
|
||||
"首帧构图": compactPromptText(shot.VisualPrompt+";光影:"+shot.Lighting, 280),
|
||||
"表演与调度": compactPromptText(shot.Performance, 180),
|
||||
"摄影机": compactPromptText(strings.TrimSpace(shot.ShotSize)+";"+camera+";主运镜:"+motion, 220),
|
||||
"时间节拍": compactPromptText(timeBeats, 240),
|
||||
"摄影机": compactPromptText(strings.TrimSpace(shot.ShotSize)+";"+camera+";主运镜:"+motion, 220),
|
||||
"时间节拍": compactPromptText(timeBeats, 240),
|
||||
"运动与结尾": compactPromptText(shot.VideoPrompt+";连续性结尾:"+shot.ContinuityOut, 240),
|
||||
"声音": compactPromptText(strings.TrimSpace(shot.Dialogue)+";音效:"+strings.TrimSpace(shot.AudioEffects), 160),
|
||||
"负面要求": compactPromptText(negative, 160),
|
||||
"声音": compactPromptText(strings.TrimSpace(shot.Dialogue)+";音效:"+strings.TrimSpace(shot.AudioEffects), 160),
|
||||
"负面要求": compactPromptText(negative, 160),
|
||||
}
|
||||
if len(shot.MustHave) > 0 {
|
||||
priority := "必须完成:" + strings.Join(shot.MustHave, ";")
|
||||
@@ -1945,65 +1946,6 @@ func (s *Service) markSessionFailed(task model.Task, message string) error {
|
||||
}
|
||||
return s.repo.Create(&model.Message{ID: newID(), UserID: task.UserID, SessionID: task.SessionID, Role: "assistant", Content: defaultString(message, "会话任务失败。")})
|
||||
}
|
||||
|
||||
func buildAgentResult(task model.Task) (map[string]any, []map[string]any) {
|
||||
title := strings.TrimSpace(task.Prompt)
|
||||
if len([]rune(title)) > 28 {
|
||||
title = string([]rune(title)[:28]) + "..."
|
||||
}
|
||||
result := map[string]any{
|
||||
"taskId": task.ID,
|
||||
"operation": task.Operation,
|
||||
"provider": defaultString(task.Provider, "internal-agent"),
|
||||
"model": defaultString(task.Model, "workflow-router"),
|
||||
"plan": []map[string]any{
|
||||
{"kind": "script", "title": "创意脚本", "content": task.Prompt},
|
||||
{"kind": "scene", "title": "主场景", "content": "根据用户输入拆解为可生成的视频场景。"},
|
||||
{"kind": "shot", "title": "镜头 1", "content": "建立画面、主体、风格和运镜。"},
|
||||
{"kind": "final", "title": "成片", "content": "等待视频生成 Provider 回填成片结果。"},
|
||||
},
|
||||
}
|
||||
ops := []map[string]any{
|
||||
nodeOp("script-"+task.ID, "text", "剧本 · "+title, 0, 0, "script", task.Prompt),
|
||||
nodeOp("scene-"+task.ID, "text", "场景 · 主场景", 380, 0, "scene", "主场景设定、角色关系、视觉风格。"),
|
||||
nodeOpWithMetadata("shot-"+task.ID, "video", "分镜 · 镜头 1", 760, 0, map[string]any{"workflowKind": "shot", "status": "idle", "generationMode": "video", "prompt": task.Prompt, "composerContent": task.Prompt, "videoEditOperation": "text_to_video"}),
|
||||
nodeOp("final-"+task.ID, "video", "成片 · 待生成", 1140, 0, "final", ""),
|
||||
connectOp("script-"+task.ID, "scene-"+task.ID),
|
||||
connectOp("scene-"+task.ID, "shot-"+task.ID),
|
||||
connectOp("shot-"+task.ID, "final-"+task.ID),
|
||||
}
|
||||
return result, ops
|
||||
}
|
||||
|
||||
func buildVideoWorkflowResult(task model.Task) (map[string]any, []map[string]any) {
|
||||
title := strings.TrimSpace(task.Prompt)
|
||||
if len([]rune(title)) > 28 {
|
||||
title = string([]rune(title)[:28]) + "..."
|
||||
}
|
||||
operation := defaultString(task.Operation, strings.TrimPrefix(task.Type, "video_"))
|
||||
result := map[string]any{
|
||||
"taskId": task.ID,
|
||||
"operation": operation,
|
||||
"provider": defaultString(task.Provider, "internal-agent"),
|
||||
"model": defaultString(task.Model, "workflow-router"),
|
||||
"plan": []map[string]any{
|
||||
{"kind": "reference_set", "title": "参考素材组", "content": "收集原视频、参考图、参考音频和版本样片。"},
|
||||
{"kind": "shot", "title": "编辑镜头", "content": task.Prompt},
|
||||
{"kind": "final", "title": "结果版本", "content": "等待 provider 生成或人工确认后回填版本结果。"},
|
||||
},
|
||||
}
|
||||
ops := []map[string]any{
|
||||
nodeOp("video-brief-"+task.ID, "text", "编辑需求 · "+title, 0, 0, "script", task.Prompt),
|
||||
nodeOpWithMetadata("video-ref-"+task.ID, "text", "参考素材组", 380, 0, map[string]any{"workflowKind": "reference_set", "status": "idle", "content": "原片、参考图、参考音频、风格板或历史版本。", "videoEditOperation": operation}),
|
||||
nodeOpWithMetadata("video-shot-"+task.ID, "video", "视频任务 · "+operation, 760, 0, map[string]any{"workflowKind": "shot", "status": "idle", "generationMode": "video", "prompt": task.Prompt, "composerContent": task.Prompt, "videoEditOperation": operation}),
|
||||
nodeOpWithMetadata("video-result-"+task.ID, "video", "结果版本 · 待回填", 1140, 0, map[string]any{"workflowKind": "final", "status": "idle", "videoEditOperation": operation, "versionLabel": "v1"}),
|
||||
connectOp("video-brief-"+task.ID, "video-ref-"+task.ID),
|
||||
connectOp("video-ref-"+task.ID, "video-shot-"+task.ID),
|
||||
connectOp("video-shot-"+task.ID, "video-result-"+task.ID),
|
||||
}
|
||||
return result, ops
|
||||
}
|
||||
|
||||
func nodeOp(id string, nodeType string, title string, x int, y int, workflowKind string, content string) map[string]any {
|
||||
return nodeOpWithMetadata(id, nodeType, title, x, y, map[string]any{"content": content, "workflowKind": workflowKind, "status": "idle"})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
func buildAgentResult(task model.Task) (map[string]any, []map[string]any) {
|
||||
title := strings.TrimSpace(task.Prompt)
|
||||
if len([]rune(title)) > 28 {
|
||||
title = string([]rune(title)[:28]) + "..."
|
||||
}
|
||||
result := map[string]any{
|
||||
"taskId": task.ID,
|
||||
"operation": task.Operation,
|
||||
"provider": defaultString(task.Provider, "internal-agent"),
|
||||
"model": defaultString(task.Model, "workflow-router"),
|
||||
"plan": []map[string]any{
|
||||
{"kind": "script", "title": "创意脚本", "content": task.Prompt},
|
||||
{"kind": "scene", "title": "主场景", "content": "根据用户输入拆解为可生成的视频场景。"},
|
||||
{"kind": "shot", "title": "镜头 1", "content": "建立画面、主体、风格和运镜。"},
|
||||
{"kind": "final", "title": "成片", "content": "等待视频生成 Provider 回填成片结果。"},
|
||||
},
|
||||
}
|
||||
ops := []map[string]any{
|
||||
nodeOp("script-"+task.ID, "text", "剧本 · "+title, 0, 0, "script", task.Prompt),
|
||||
nodeOp("scene-"+task.ID, "text", "场景 · 主场景", 380, 0, "scene", "主场景设定、角色关系、视觉风格。"),
|
||||
nodeOpWithMetadata("shot-"+task.ID, "video", "分镜 · 镜头 1", 760, 0, map[string]any{"workflowKind": "shot", "status": "idle", "generationMode": "video", "prompt": task.Prompt, "composerContent": task.Prompt, "videoEditOperation": "text_to_video"}),
|
||||
nodeOp("final-"+task.ID, "video", "成片 · 待生成", 1140, 0, "final", ""),
|
||||
connectOp("script-"+task.ID, "scene-"+task.ID),
|
||||
connectOp("scene-"+task.ID, "shot-"+task.ID),
|
||||
connectOp("shot-"+task.ID, "final-"+task.ID),
|
||||
}
|
||||
return result, ops
|
||||
}
|
||||
|
||||
func buildVideoWorkflowResult(task model.Task) (map[string]any, []map[string]any) {
|
||||
title := strings.TrimSpace(task.Prompt)
|
||||
if len([]rune(title)) > 28 {
|
||||
title = string([]rune(title)[:28]) + "..."
|
||||
}
|
||||
operation := defaultString(task.Operation, strings.TrimPrefix(task.Type, "video_"))
|
||||
result := map[string]any{
|
||||
"taskId": task.ID,
|
||||
"operation": operation,
|
||||
"provider": defaultString(task.Provider, "internal-agent"),
|
||||
"model": defaultString(task.Model, "workflow-router"),
|
||||
"plan": []map[string]any{
|
||||
{"kind": "reference_set", "title": "参考素材组", "content": "收集原视频、参考图、参考音频和版本样片。"},
|
||||
{"kind": "shot", "title": "编辑镜头", "content": task.Prompt},
|
||||
{"kind": "final", "title": "结果版本", "content": "等待 provider 生成或人工确认后回填版本结果。"},
|
||||
},
|
||||
}
|
||||
ops := []map[string]any{
|
||||
nodeOp("video-brief-"+task.ID, "text", "编辑需求 · "+title, 0, 0, "script", task.Prompt),
|
||||
nodeOpWithMetadata("video-ref-"+task.ID, "text", "参考素材组", 380, 0, map[string]any{"workflowKind": "reference_set", "status": "idle", "content": "原片、参考图、参考音频、风格板或历史版本。", "videoEditOperation": operation}),
|
||||
nodeOpWithMetadata("video-shot-"+task.ID, "video", "视频任务 · "+operation, 760, 0, map[string]any{"workflowKind": "shot", "status": "idle", "generationMode": "video", "prompt": task.Prompt, "composerContent": task.Prompt, "videoEditOperation": operation}),
|
||||
nodeOpWithMetadata("video-result-"+task.ID, "video", "结果版本 · 待回填", 1140, 0, map[string]any{"workflowKind": "final", "status": "idle", "videoEditOperation": operation, "versionLabel": "v1"}),
|
||||
connectOp("video-brief-"+task.ID, "video-ref-"+task.ID),
|
||||
connectOp("video-ref-"+task.ID, "video-shot-"+task.ID),
|
||||
connectOp("video-shot-"+task.ID, "video-result-"+task.ID),
|
||||
}
|
||||
return result, ops
|
||||
}
|
||||
Reference in new issue
Block a user