feat(canvas): 创作助手 - 合并创作对话、能力发现与画布接续
This commit is contained in:
168
backend/internal/repository/creation.go
Normal file
168
backend/internal/repository/creation.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
var ErrCreationConflict = errors.New("creation state changed; reload before continuing")
|
||||
|
||||
func (r *Repository) CreationStorageUsage(userID string) (int64, int64, error) {
|
||||
var usage struct {
|
||||
Count int64
|
||||
Bytes int64
|
||||
}
|
||||
query := `SELECT (SELECT COUNT(*) FROM creation_runs WHERE user_id = ?) AS count,
|
||||
(SELECT COALESCE(SUM(length(CAST(COALESCE(state_json,'') AS BLOB))+length(CAST(COALESCE(approved_operations_json,'') AS BLOB))+length(CAST(COALESCE(approved_canvas_json,'') AS BLOB))),0) FROM creation_runs WHERE user_id = ?)
|
||||
+ (SELECT COALESCE(SUM(length(CAST(COALESCE(request_json,'') AS BLOB))+length(CAST(COALESCE(quote_json,'') AS BLOB))+length(CAST(COALESCE(price_signature,'') AS BLOB))),0) FROM creation_submissions WHERE user_id = ?) AS bytes`
|
||||
if r.Dialect() == "postgres" {
|
||||
query = strings.ReplaceAll(query, "length(CAST(COALESCE(", "octet_length(COALESCE(")
|
||||
query = strings.ReplaceAll(query, ",'') AS BLOB))", ",''))")
|
||||
}
|
||||
err := r.db.Raw(query, userID, userID, userID).Scan(&usage).Error
|
||||
return usage.Count, usage.Bytes, err
|
||||
}
|
||||
|
||||
func (r *Repository) CreationRun(userID, id string) (*model.CreationRun, error) {
|
||||
var run model.CreationRun
|
||||
err := r.db.First(&run, "id = ? AND user_id = ?", id, userID).Error
|
||||
return &run, err
|
||||
}
|
||||
|
||||
func (r *Repository) CreationRunByClientKey(userID, key string) (*model.CreationRun, error) {
|
||||
var run model.CreationRun
|
||||
err := r.db.First(&run, "user_id = ? AND client_key = ?", userID, key).Error
|
||||
return &run, err
|
||||
}
|
||||
func (r *Repository) CreationRuns(userID string) ([]model.CreationRun, error) {
|
||||
items := []model.CreationRun{}
|
||||
err := r.db.Where("user_id = ?", userID).Order("updated_at DESC").Limit(100).Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
func (r *Repository) CreateCreationRun(run *model.CreationRun) error {
|
||||
var old model.CreationRun
|
||||
if err := r.db.First(&old, "user_id = ? AND client_key = ?", run.UserID, run.ClientKey).Error; err == nil {
|
||||
if old.CreateHash != run.CreateHash {
|
||||
return ErrCreationConflict
|
||||
}
|
||||
*run = old
|
||||
return nil
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if err := r.db.Create(run).Error; err != nil {
|
||||
if e := r.db.First(&old, "user_id = ? AND client_key = ?", run.UserID, run.ClientKey).Error; e == nil && old.CreateHash == run.CreateHash {
|
||||
*run = old
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A conditional write obtains the run row's write lock in both SQLite and PostgreSQL.
|
||||
// All submission consumption and task reservation use the returned transaction repository.
|
||||
func (r *Repository) MutateCreationRun(userID, id string, fn func(*model.CreationRun, *Repository) error) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.CreationRun{}).Where("id = ? AND user_id = ?", id, userID).UpdateColumn("revision", gorm.Expr("revision"))
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
scoped := New(tx)
|
||||
run, err := scoped.CreationRun(userID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn(run, scoped); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Save(run).Error
|
||||
})
|
||||
}
|
||||
func (r *Repository) CreationSubmissions(userID, runID string) ([]model.CreationSubmission, error) {
|
||||
items := []model.CreationSubmission{}
|
||||
err := r.db.Where("user_id = ? AND run_id = ?", userID, runID).Order("created_at").Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
func (r *Repository) CreationSubmission(userID, runID, id string) (*model.CreationSubmission, error) {
|
||||
var item model.CreationSubmission
|
||||
err := r.db.First(&item, "id = ? AND user_id = ? AND run_id = ?", id, userID, runID).Error
|
||||
return &item, err
|
||||
}
|
||||
func (r *Repository) SaveCreationSubmission(item *model.CreationSubmission) error {
|
||||
return r.db.Save(item).Error
|
||||
}
|
||||
func (r *Repository) RevokeCreationSubmissions(runID string) error {
|
||||
return r.db.Model(&model.CreationSubmission{}).Where("run_id = ? AND task_id IS NULL AND revoked_at IS NULL", runID).Update("revoked_at", time.Now()).Error
|
||||
}
|
||||
func (r *Repository) CreateCreationCanvas(canvas *model.CanvasProject) error {
|
||||
return r.db.Create(canvas).Error
|
||||
}
|
||||
func (r *Repository) CompareSaveCreationCanvas(canvas *model.CanvasProject, previous string) error {
|
||||
result := r.db.Model(&model.CanvasProject{}).Where("id = ? AND user_id = ? AND payload_json = ?", canvas.ID, canvas.UserID, previous).Updates(map[string]any{"payload_json": canvas.PayloadJSON, "title": canvas.Title, "updated_at": time.Now()})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
return ErrCreationConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Capture server-side configuration versions, never channel credentials.
|
||||
func (r *Repository) CreationPriceSignature(task *model.Task, channelID, modelKey string) (string, error) {
|
||||
db := r.db.Clauses(clause.Locking{Strength: "SHARE"}).Session(&gorm.Session{})
|
||||
values := map[string]any{}
|
||||
var cm model.ChannelModel
|
||||
query := db.Where("channel_id = ? AND model_key = ?", channelID, modelKey)
|
||||
if task.ChannelModelID != "" {
|
||||
query = db.Where("id = ?", task.ChannelModelID)
|
||||
}
|
||||
if err := query.First(&cm).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["channelModel"] = cm
|
||||
values["capabilityConfig"] = cm.CapabilityConfigJSON
|
||||
var channel struct {
|
||||
ID string
|
||||
Enabled bool
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
if err := db.Model(&model.ModelChannel{}).Select("id, enabled, updated_at").First(&channel, "id = ?", cm.ChannelID).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["channel"] = channel
|
||||
var tiers []model.ChannelModelPriceTier
|
||||
if err := db.Where("channel_model_id = ?", cm.ID).Order("id").Find(&tiers).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["tiers"] = tiers
|
||||
if task.LogicalModelID != "" {
|
||||
var logical model.LogicalModel
|
||||
if err := db.First(&logical, "id = ?", task.LogicalModelID).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["logical"] = logical
|
||||
var route model.LogicalModelRoute
|
||||
if err := db.First(&route, "id = ?", task.RouteID).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["route"] = route
|
||||
}
|
||||
var settings []model.SystemSetting
|
||||
if err := db.Where("key IN ?", []string{"credit_policy", "feature_availability"}).Order("key").Find(&settings).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
values["pricingSettings"] = settings
|
||||
b, err := json.Marshal(values)
|
||||
return string(b), err
|
||||
}
|
||||
@@ -118,6 +118,20 @@ func (r *Repository) ResourceReferenceSnapshot(userID string, excludingAssetID s
|
||||
for _, session := range sessions {
|
||||
snapshot.Documents = append(snapshot.Documents, ResourceReferenceDocument{Kind: "会话", ID: session.ID, Title: session.Prompt, PrimaryJSON: session.CanvasSnapshotJSON, SecondaryJSON: session.CanvasOpsJSON})
|
||||
}
|
||||
var runs []model.CreationRun
|
||||
if err := r.db.Where("user_id = ?", userID).Find(&runs).Error; err != nil {
|
||||
return snapshot, err
|
||||
}
|
||||
for _, run := range runs {
|
||||
snapshot.Documents = append(snapshot.Documents, ResourceReferenceDocument{Kind: "创作会话", ID: run.ID, Title: "智能创作", PrimaryJSON: run.StateJSON, SecondaryJSON: run.ApprovedOperationsJSON})
|
||||
}
|
||||
var submissions []model.CreationSubmission
|
||||
if err := r.db.Where("user_id = ? AND revoked_at IS NULL", userID).Find(&submissions).Error; err != nil {
|
||||
return snapshot, err
|
||||
}
|
||||
for _, submission := range submissions {
|
||||
snapshot.Documents = append(snapshot.Documents, ResourceReferenceDocument{Kind: "创作执行项", ID: submission.ID, Title: submission.ItemKey, PrimaryJSON: submission.RequestJSON})
|
||||
}
|
||||
|
||||
var messages []model.Message
|
||||
if err := r.db.Select("id", "content", "payload").Where("user_id = ?", userID).Find(&messages).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user