Files
ttstd-infinite-canvas/backend/internal/service/session_creation.go
QAyong 5b7f173102 feat: 完善创作工作台与用户诊断支持 (#295)
* feat(canvas): 参考素材 - 增加移除按钮并删除对应连线

* feat(canvas): 完善节点面板交互与拖拽反馈

* fix(canvas): 画布交互与小地图 - 统一浮层层级并完善节点识别

* feat(prompt-optimizer): 增加 AI 提示词优化器及宿主接入

* feat(canvas): 画布 - 增加自动整理节点按钮并补充文档

* chore(web): 前端 - 修正提示词优化器格式

* chore(backend): 后端 - 修正 Go 格式检查

* feat(workspace): 完善诊断导出与本地排障支持

* chore(web): 格式化诊断导出文件

* fix(web-test): 兼容 Bun 测试运行时

* fix(web-test): 对齐模型目录协议断言
2026-08-25 13:49:15 +08:00

93 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"encoding/json"
"errors"
"fmt"
"strings"
"infinite-canvas/backend/internal/model"
)
// sessionCreationCoordinator 负责会话草稿、首条消息和首个 storyboard 任务的一致性补偿。
// 会话详情读取与文件上传不放在这里,避免创建流程继续膨胀成通用 session service。
type sessionCreationCoordinator struct {
service *Service
}
func newSessionCreationCoordinator(service *Service) *sessionCreationCoordinator {
return &sessionCreationCoordinator{service: service}
}
func (s *Service) sessionCreation() *sessionCreationCoordinator {
if s.sessionCreationCoordinator != nil {
return s.sessionCreationCoordinator
}
// 部分单元测试直接构造 Service 字面量;延迟创建保持内部测试和工具兼容。
return newSessionCreationCoordinator(s)
}
func (w *sessionCreationCoordinator) create(userID string, req CreateSessionRequest) (*SessionDetail, error) {
s := w.service
prompt := strings.TrimSpace(req.Prompt)
if prompt == "" {
return nil, errors.New("prompt is required")
}
if err := validateStoryboardContext(req.ProjectStyle, req.Characters); err != nil {
return nil, err
}
compactedSnapshot := compactPersistedValue(req.CanvasSnapshot)
snapshotJSON, err := json.Marshal(compactedSnapshot)
if err != nil {
return nil, fmt.Errorf("序列化画布快照失败:%w", err)
}
session := model.Session{ID: newID(), UserID: userID, ProjectID: req.ProjectID, Status: model.SessionStatusActive, Prompt: prompt, CanvasSnapshotJSON: string(snapshotJSON)}
policy, err := s.RuntimePolicy()
if err != nil {
return nil, err
}
if err := w.persistDraft(userID, prompt, snapshotJSON, session, policy); err != nil {
return nil, err
}
taskReq := CreateTaskRequest{SessionID: session.ID, ProjectID: req.ProjectID, TraceID: req.TraceID, RequestID: req.RequestID, Type: "agent_storyboard", Operation: "storyboard", Prompt: prompt, Provider: "openai-compatible", Model: req.Config.Model, LogicalModelID: req.LogicalModelID, Input: map[string]any{"mode": "text", "references": req.References, "canvasSnapshot": compactedSnapshot, "requirements": req.Requirements, "canvasAssets": req.CanvasAssets, "projectStyle": req.ProjectStyle, "characters": req.Characters, "config": req.Config}}
if _, err := s.CreateTask(userID, taskReq); err != nil {
if cleanupErr := w.deleteDraft(userID, session.ID); cleanupErr != nil {
return nil, fmt.Errorf("创建会话任务失败:%v;清理会话失败:%w", err, cleanupErr)
}
return nil, err
}
s.recordActivity(userID, "agent_message", 1)
return s.SessionDetail(userID, session.ID)
}
func (w *sessionCreationCoordinator) persistDraft(userID string, prompt string, snapshotJSON []byte, session model.Session, policy RuntimePolicySetting) error {
s := w.service
s.storageMu.Lock()
defer s.storageMu.Unlock()
usage, err := s.repo.UserStorageUsage(userID)
if err != nil {
return err
}
incomingBytes := int64(len([]byte(prompt))*2 + len(snapshotJSON))
if err := validateStructuredStorageQuotaWithPolicy(usage, "session", true, incomingBytes, policy.Resource); err != nil {
return err
}
if err := s.repo.Create(&session); err != nil {
return err
}
if err := s.repo.Create(&model.Message{ID: newID(), UserID: userID, SessionID: session.ID, Role: "user", Content: prompt}); err != nil {
if cleanupErr := s.repo.DeleteSessionDraft(userID, session.ID); cleanupErr != nil {
return fmt.Errorf("创建会话消息失败:%v;清理会话失败:%w", err, cleanupErr)
}
return err
}
return nil
}
func (w *sessionCreationCoordinator) deleteDraft(userID string, sessionID string) error {
s := w.service
s.storageMu.Lock()
defer s.storageMu.Unlock()
return s.repo.DeleteSessionDraft(userID, sessionID)
}