feat(*): Agent工作流 - 完善审批、分镜协作与资源交互体验
This commit is contained in:
1 parent
435bc487cf
commit
298bee1fa9
38 files changed
+508
-1263
No files matched your search
@@ -69,7 +69,19 @@ func prepareCloudAgentCanvasMutation(repo *repository.Repository, userID, canvas
|
||||
}
|
||||
beforeHash := cloudAgentCanvasHash(doc)
|
||||
if beforeHash != args.SnapshotHash {
|
||||
return nil, &cloudAgentFieldArgumentError{error: &cloudAgentArgumentError{creationConflict("画布已变化,本次未写入;请重新读取并重新申请审批")}, Field: "snapshotHash", Issue: "stale_snapshot"}
|
||||
// The tool call describes concrete node/connection operations. Rebase that
|
||||
// operation list onto the current document instead of rejecting it because
|
||||
// an unrelated browser save advanced the canvas revision.
|
||||
latest, latestErr := repo.CanvasProjectForUser(userID, canvasID)
|
||||
if latestErr != nil {
|
||||
return nil, latestErr
|
||||
}
|
||||
canvas = latest
|
||||
doc, err = creationDocument(canvas.PayloadJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
beforeHash = cloudAgentCanvasHash(doc)
|
||||
}
|
||||
items, err := applyCloudAgentCanvasPlan(doc, args.Ops)
|
||||
if err != nil {
|
||||
|
||||
@@ -252,6 +252,53 @@ func TestCloudAgentImageApprovalEditsAreValidatedAndIdempotent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudAgentVideoApprovalEditsAreValidatedAndApplied(t *testing.T) {
|
||||
s, db, args := agentMediaFixture(t)
|
||||
run, _ := agentMediaRun(t, s, args, "request_approval")
|
||||
if err := s.advanceCloudAgentByID("user", run.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
run, _ = s.repo.CloudAgent("user", run.ID)
|
||||
state, err := cloudAgentDecode(run)
|
||||
if err != nil || state.Approval == nil {
|
||||
t.Fatalf("missing video approval: %v", err)
|
||||
}
|
||||
seconds := 8
|
||||
audio := false
|
||||
watermark := false
|
||||
settings := CloudAgentMediaSettings{ChannelID: args.ChannelID, ChannelModelKey: args.ChannelModelKey, Size: args.Size, Quality: args.Quality, DurationSeconds: &seconds, VideoGenerateAudio: &audio, VideoWatermark: &watermark}
|
||||
if err := s.DecideCloudAgentApproval("user", run.ID, state.Approval.ID, "approve", "", &settings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.advanceCloudAgentByID("user", run.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updated, err := s.repo.CloudAgent("user", run.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state, err = cloudAgentDecode(updated)
|
||||
if err != nil || state.MediaTaskID == "" {
|
||||
t.Fatalf("edited video approval did not submit: %v", err)
|
||||
}
|
||||
task, err := s.repo.TaskForUser("user", state.MediaTaskID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var input canvasGenerationInput
|
||||
if err := json.Unmarshal([]byte(task.InputJSON), &input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if input.Config.VideoSeconds != "8" || input.Config.VideoGenerateAudio != "false" || input.Config.VideoWatermark != "false" {
|
||||
t.Fatalf("approved video options were not applied: %+v", input.Config)
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.Task{}).Where("type = ?", "canvas_video").Count(&count)
|
||||
if count != 1 {
|
||||
t.Fatalf("expected exactly one video task, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudAgentConversationKeepsRecentRounds(t *testing.T) {
|
||||
s, db, _, _ := creationTestService(t)
|
||||
if err := db.Create(&model.CanvasProject{ID: "agent-canvas", UserID: "user", PayloadJSON: `{"nodes":[]}`}).Error; err != nil {
|
||||
|
||||
@@ -26,6 +26,7 @@ func cloudAgentGenerationSpec(a cloudAgentMediaArgs, refs map[string]any, resolv
|
||||
options.DurationSeconds = &a.Duration
|
||||
}
|
||||
options.GenerateAudio = a.VideoGenerateAudio
|
||||
options.Watermark = a.VideoWatermark
|
||||
if a.Quality != "" {
|
||||
options.Resolution = &a.Quality
|
||||
}
|
||||
|
||||
@@ -11,11 +11,15 @@ import (
|
||||
// Only user-editable image options cross the approval boundary. Targets,
|
||||
// references, prompt, snapshot and generation mode remain server-authored.
|
||||
type CloudAgentMediaSettings struct {
|
||||
LogicalModelID string `json:"logicalModelId,omitempty"`
|
||||
ChannelID string `json:"channelId,omitempty"`
|
||||
ChannelModelKey string `json:"channelModelKey,omitempty"`
|
||||
Size string `json:"size"`
|
||||
Quality string `json:"quality"`
|
||||
LogicalModelID string `json:"logicalModelId,omitempty"`
|
||||
ChannelID string `json:"channelId,omitempty"`
|
||||
ChannelModelKey string `json:"channelModelKey,omitempty"`
|
||||
Size string `json:"size"`
|
||||
Quality string `json:"quality"`
|
||||
DurationSeconds *int `json:"durationSeconds,omitempty"`
|
||||
VideoGenerateAudio *bool `json:"videoGenerateAudio,omitempty"`
|
||||
VideoWatermark *bool `json:"videoWatermark,omitempty"`
|
||||
VideoArkPrivateAssetUpload *bool `json:"videoArkPrivateAssetUpload,omitempty"`
|
||||
}
|
||||
|
||||
// updateCloudAgentMediaApproval rebuilds the complete prepared admission for an
|
||||
@@ -27,17 +31,31 @@ func (s *Service) updateCloudAgentMediaApproval(repo *repository.Repository, run
|
||||
}
|
||||
call := state.Approval.Call
|
||||
if call.Function.Name != "generate_media" {
|
||||
return BadAuthRequest("当前审批不是图片生成,不能修改生成参数")
|
||||
return BadAuthRequest("当前审批不是媒体生成,不能修改生成参数")
|
||||
}
|
||||
var args cloudAgentMediaArgs
|
||||
if err := decodeCloudAgentJSONObject(call.Function.Arguments, &args); err != nil {
|
||||
return err
|
||||
}
|
||||
if args.Mode != "image" {
|
||||
return BadAuthRequest("仅图片生成审批支持修改模型、画幅和质量")
|
||||
if args.Mode != "image" && args.Mode != "video" {
|
||||
return BadAuthRequest("仅图片或视频生成审批支持修改模型和规格")
|
||||
}
|
||||
args.LogicalModelID, args.ChannelID, args.ChannelModelKey = settings.LogicalModelID, settings.ChannelID, settings.ChannelModelKey
|
||||
args.Size, args.Quality = settings.Size, settings.Quality
|
||||
if args.Mode == "video" {
|
||||
if settings.DurationSeconds != nil {
|
||||
if *settings.DurationSeconds < 0 {
|
||||
return BadAuthRequest("视频时长不能为负数")
|
||||
}
|
||||
args.Duration = *settings.DurationSeconds
|
||||
}
|
||||
args.VideoGenerateAudio = settings.VideoGenerateAudio
|
||||
args.VideoWatermark = settings.VideoWatermark
|
||||
args.VideoArkPrivateAssetUpload = settings.VideoArkPrivateAssetUpload
|
||||
}
|
||||
if args.Mode == "image" && (settings.DurationSeconds != nil || settings.VideoGenerateAudio != nil || settings.VideoWatermark != nil || settings.VideoArkPrivateAssetUpload != nil) {
|
||||
return BadAuthRequest("视频参数仅适用于视频生成审批")
|
||||
}
|
||||
raw, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,7 +129,7 @@ func replaceCloudAgentPreparedLeases(repo *repository.Repository, userID, runID,
|
||||
}
|
||||
|
||||
func validateCloudAgentResolvedMediaOptions(requested, resolved map[string]any) error {
|
||||
for _, key := range []string{"size", "videoSeconds", "vquality", "quality", "count", "videoGenerateAudio"} {
|
||||
for _, key := range []string{"size", "videoSeconds", "vquality", "quality", "count", "videoGenerateAudio", "videoWatermark", "videoArkPrivateAssetUpload"} {
|
||||
if value := stringValue(requested[key]); value != "" && !strings.EqualFold(value, stringValue(resolved[key])) {
|
||||
return creationConflict("模型解析后的生成规格与审批参数不同,请重新读取目录并审批")
|
||||
}
|
||||
|
||||
@@ -243,13 +243,13 @@ func prepareCloudAgentStoryboardEdit(repo *repository.Repository, userID, canvas
|
||||
return nil, err
|
||||
}
|
||||
beforeHash := cloudAgentCanvasHash(doc)
|
||||
if !cloudAgentNodeSnapshotMatches(doc, args.NodeID, args.SnapshotHash) {
|
||||
return nil, cloudAgentFieldError("snapshotHash", "stale_snapshot", "这个分镜在你读取之后被修改过,本次未写入;请重新读取分镜后再改")
|
||||
}
|
||||
node, storyboard, rows, err := storyboardNodeFromDocument(doc, args.NodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cloudAgentNodeSnapshotMatches(doc, args.NodeID, args.SnapshotHash) {
|
||||
return nil, cloudAgentFieldError("snapshotHash", "stale_snapshot", "这个分镜在你读取之后被修改过,本次未写入;请重新读取分镜后再改")
|
||||
}
|
||||
index := -1
|
||||
for i, row := range rows {
|
||||
if stringValue(row["id"]) == args.RowID {
|
||||
|
||||
Reference in new issue
Block a user