package logic import ( "net/url" "time" "img_infinite_canvas/internal/domain/design" "img_infinite_canvas/internal/types" ) func toProjectResponse(project design.Project) *types.ProjectResponse { return &types.ProjectResponse{Project: toAPIProject(project)} } func toGeneratorTaskResponse(task design.GeneratorTask) *types.GeneratorTaskResponse { data := types.GeneratorTaskData{ GeneratorTaskId: task.GeneratorTaskID, GeneratorName: task.GeneratorName, Status: task.Status, OriginalInputArgs: toAPIGeneratorTaskInputArgs(task.OriginalInputArgs), Artifacts: toAPIGeneratorTaskArtifacts(task.Artifacts), } if task.Status != "completed" { queueInfo := toAPIGeneratorTaskQueueInfo(task.QueueInfo) data.QueueInfo = &queueInfo } return &types.GeneratorTaskResponse{ Code: 0, Message: "success", Data: data, } } func toAPIGeneratorTaskInputArgs(input design.GeneratorTaskInputArgs) types.GeneratorTaskInputArgs { return types.GeneratorTaskInputArgs{ AspectRatio: input.AspectRatio, Image: append([]string(nil), input.Image...), ImageUrl: input.ImageURL, Prompt: input.Prompt, Resolution: input.Resolution, SrcBox: toAPINormalizedBoxPtr(input.SrcBox), DstBox: toAPINormalizedBoxPtr(input.DstBox), } } func toDomainGeneratorTaskInputArgs(input types.GeneratorTaskInputArgs) design.GeneratorTaskInputArgs { return design.GeneratorTaskInputArgs{ AspectRatio: input.AspectRatio, Image: append([]string(nil), input.Image...), ImageURL: input.ImageUrl, Prompt: input.Prompt, Resolution: input.Resolution, SrcBox: toDomainNormalizedBoxPtr(input.SrcBox), DstBox: toDomainNormalizedBoxPtr(input.DstBox), } } func toDomainNormalizedBox(box types.NormalizedBox) design.NormalizedBox { return design.NormalizedBox{ X: box.X, Y: box.Y, Width: box.Width, Height: box.Height, } } func toDomainNormalizedBoxPtr(box *types.NormalizedBox) *design.NormalizedBox { if box == nil { return nil } next := toDomainNormalizedBox(*box) return &next } func toAPINormalizedBox(box design.NormalizedBox) types.NormalizedBox { return types.NormalizedBox{ X: box.X, Y: box.Y, Width: box.Width, Height: box.Height, } } func toAPINormalizedBoxPtr(box *design.NormalizedBox) *types.NormalizedBox { if box == nil { return nil } next := toAPINormalizedBox(*box) return &next } func toRecognizeObjectResponse(result design.ObjectRecognition) *types.RecognizeObjectResponse { return &types.RecognizeObjectResponse{ Id: result.ID, Label: result.Label, Confidence: result.Confidence, BBox: toAPINormalizedBox(result.BBox), } } func toAPIGeneratorTaskQueueInfo(queue design.GeneratorTaskQueueInfo) types.GeneratorTaskQueueInfo { return types.GeneratorTaskQueueInfo{ InQueue: queue.InQueue, Position: queue.Position, TotalQueueCount: queue.TotalQueueCount, EstimatedEndTime: queue.EstimatedEndTime, RemainingTimeSeconds: queue.RemainingTimeSeconds, DisableUnlimitedPrice: types.GeneratorTaskDisableUnlimitedPrice{ Balance: queue.DisableUnlimitedPrice.Balance, Price: queue.DisableUnlimitedPrice.Price, DisableUnlimited: queue.DisableUnlimitedPrice.DisableUnlimited, PriceDetail: types.GeneratorTaskPriceDetail{ BasePrice: queue.DisableUnlimitedPrice.PriceDetail.BasePrice, ExtInfoForUnitCount: queue.DisableUnlimitedPrice.PriceDetail.ExtInfoForUnitCount, GeneratorName: queue.DisableUnlimitedPrice.PriceDetail.GeneratorName, InputArgs: toAPIGeneratorTaskInputArgs(queue.DisableUnlimitedPrice.PriceDetail.InputArgs), OutputArgs: queue.DisableUnlimitedPrice.PriceDetail.OutputArgs, SearchKey: queue.DisableUnlimitedPrice.PriceDetail.SearchKey, TimeVariantPeriod: queue.DisableUnlimitedPrice.PriceDetail.TimeVariantPeriod, TotalPrice: queue.DisableUnlimitedPrice.PriceDetail.TotalPrice, UnitCount: queue.DisableUnlimitedPrice.PriceDetail.UnitCount, UnitName: queue.DisableUnlimitedPrice.PriceDetail.UnitName, UnitPrice: queue.DisableUnlimitedPrice.PriceDetail.UnitPrice, }, }, } } func toAPIGeneratorTaskArtifacts(items []design.GeneratorTaskArtifact) []types.GeneratorTaskArtifact { resp := make([]types.GeneratorTaskArtifact, 0, len(items)) for _, item := range items { resp = append(resp, types.GeneratorTaskArtifact{ Type: item.Type, Content: item.Content, Metadata: types.GeneratorTaskArtifactMetadata{ ArtifactId: item.Metadata.ArtifactID, Format: item.Metadata.Format, Height: item.Metadata.Height, Label: item.Metadata.Label, NodeId: item.Metadata.NodeID, NodeName: item.Metadata.NodeName, SizeBytes: item.Metadata.SizeBytes, Width: item.Metadata.Width, }, }) } return resp } func toExtractNodeTextResponse(extraction design.TextExtraction) *types.ExtractNodeTextResponse { items := make([]types.ExtractedTextLayer, 0, len(extraction.Layers)) for _, item := range extraction.Layers { originalText := item.OriginalText if originalText == "" { originalText = item.Text } items = append(items, types.ExtractedTextLayer{ Text: item.Text, OriginalText: originalText, X: item.X, Y: item.Y, Width: item.Width, Height: item.Height, FontSize: item.FontSize, FontFamily: item.FontFamily, FontWeight: item.FontWeight, Color: item.Color, TextAlign: item.TextAlign, LineHeight: item.LineHeight, LetterSpacing: item.LetterSpacing, StrokeColor: item.StrokeColor, StrokeWidth: item.StrokeWidth, Opacity: item.Opacity, Rotation: item.Rotation, Confidence: item.Confidence, }) } return &types.ExtractNodeTextResponse{Items: items} } func toAPIProject(project design.Project) types.Project { return types.Project{ Id: project.ID, UserId: project.UserID, Title: project.Title, Brief: project.Brief, Status: string(project.Status), Thumbnail: project.Thumbnail, UpdatedAt: formatTime(project.UpdatedAt), Version: project.Version, SnapshotId: project.SnapshotID, Canvas: project.Canvas, LastThreadId: project.LastThreadID, Viewport: toAPIView(project.Viewport), Nodes: toAPINodes(project.Nodes), Connections: toAPIConnections(project.Connections), Messages: toAPIMessages(project.Messages), } } func toAPISummaries(items []design.ProjectSummary) []types.ProjectSummary { resp := make([]types.ProjectSummary, 0, len(items)) for _, item := range items { resp = append(resp, types.ProjectSummary{ Id: item.ID, UserId: item.UserID, Title: item.Title, Brief: item.Brief, Status: string(item.Status), Thumbnail: item.Thumbnail, UpdatedAt: formatTime(item.UpdatedAt), }) } return resp } func toAPIQuickActions(items []design.QuickAction) []types.QuickAction { resp := make([]types.QuickAction, 0, len(items)) for _, item := range items { resp = append(resp, types.QuickAction{ Id: item.ID, Label: item.Label, Prompt: item.Prompt, Icon: item.Icon, }) } return resp } func toAPIInspirations(items []design.InspirationItem) []types.InspirationItem { resp := make([]types.InspirationItem, 0, len(items)) for _, item := range items { resp = append(resp, types.InspirationItem{ Id: item.ID, Title: item.Title, Category: item.Category, Description: item.Description, Accent: item.Accent, }) } return resp } func toAPINodes(items []design.Node) []types.CanvasNode { resp := make([]types.CanvasNode, 0, len(items)) for _, item := range items { resp = append(resp, types.CanvasNode{ Id: item.ID, Type: string(item.Type), Title: item.Title, X: item.X, Y: item.Y, Width: item.Width, Height: item.Height, Content: item.Content, Tone: item.Tone, Status: item.Status, ParentId: item.ParentID, LayerRole: item.LayerRole, FontSize: item.FontSize, FontFamily: item.FontFamily, FontWeight: item.FontWeight, Color: item.Color, TextAlign: item.TextAlign, LineHeight: item.LineHeight, LetterSpacing: item.LetterSpacing, TextDecoration: item.TextDecoration, TextTransform: item.TextTransform, Opacity: item.Opacity, FillColor: item.FillColor, StrokeColor: item.StrokeColor, StrokeWidth: item.StrokeWidth, StrokeStyle: item.StrokeStyle, FlipX: item.FlipX, FlipY: item.FlipY, Rotation: item.Rotation, ImageAdjustments: item.ImageAdjustments, MockupSurface: toAPIMockupSurface(item.MockupSurface), MockupModel: toAPIMockupModel(item.MockupModel), MockupSourceContent: item.MockupSourceContent, }) } return resp } func toDomainNodes(items []types.CanvasNode) []design.Node { resp := make([]design.Node, 0, len(items)) for _, item := range items { resp = append(resp, design.Node{ ID: item.Id, Type: design.NodeType(item.Type), Title: item.Title, X: item.X, Y: item.Y, Width: item.Width, Height: item.Height, Content: item.Content, Tone: item.Tone, Status: item.Status, ParentID: item.ParentId, LayerRole: item.LayerRole, FontSize: item.FontSize, FontFamily: item.FontFamily, FontWeight: item.FontWeight, Color: item.Color, TextAlign: item.TextAlign, LineHeight: item.LineHeight, LetterSpacing: item.LetterSpacing, TextDecoration: item.TextDecoration, TextTransform: item.TextTransform, Opacity: item.Opacity, FillColor: item.FillColor, StrokeColor: item.StrokeColor, StrokeWidth: item.StrokeWidth, StrokeStyle: item.StrokeStyle, FlipX: item.FlipX, FlipY: item.FlipY, Rotation: item.Rotation, ImageAdjustments: item.ImageAdjustments, MockupSurface: toDomainMockupSurface(item.MockupSurface), MockupModel: toDomainMockupModel(item.MockupModel), MockupSourceContent: item.MockupSourceContent, }) } return resp } func toAPIMockupSurface(surface design.MockupSurface) types.MockupSurface { return types.MockupSurface{ TargetId: surface.TargetID, Polygon: toAPIMockupPoints(surface.Polygon), Mesh: types.MockupMesh{ Columns: int64(surface.Mesh.Columns), Rows: int64(surface.Mesh.Rows), Points: toAPIMockupPoints(surface.Mesh.Points), }, } } func toDomainMockupSurface(surface types.MockupSurface) design.MockupSurface { return design.MockupSurface{ TargetID: surface.TargetId, Polygon: toDomainMockupPoints(surface.Polygon), Mesh: design.MockupMesh{ Columns: int(surface.Mesh.Columns), Rows: int(surface.Mesh.Rows), Points: toDomainMockupPoints(surface.Mesh.Points), }, } } func toAPIMockupPlacement(placement design.MockupPlacement) types.MockupPlacement { return types.MockupPlacement{ X: placement.X, Y: placement.Y, Width: placement.Width, Height: placement.Height, } } func toAPIMockupModel(model design.MockupModel) types.MockupModel { return types.MockupModel{ Version: int64(model.Version), DepthMap: toAPIMockupModelMap(model.DepthMap), DepthScaleMin: model.DepthScaleMin, DepthScaleMax: model.DepthScaleMax, ShadeMap: toAPIMockupModelMap(model.ShadeMap), SegmentationMap: toAPIMockupModelMap(model.SegmentationMap), XOffsetMap: toAPIMockupModelMap(model.XOffsetMap), YOffsetMap: toAPIMockupModelMap(model.YOffsetMap), CameraIntrinsics: types.MockupCameraIntrinsics{ Matrix: append([]float64(nil), model.CameraIntrinsics.Matrix...), }, ColorCorrection: model.ColorCorrection, ForegroundImage: model.ForegroundImage, } } func toAPIMockupModelMap(modelMap design.MockupModelMap) types.MockupModelMap { return types.MockupModelMap{ DataId: modelMap.DataID, Width: int64(modelMap.Width), Height: int64(modelMap.Height), Url: modelMap.URL, Data: append([]float64(nil), modelMap.Data...), } } func toDomainMockupModel(model types.MockupModel) design.MockupModel { return design.MockupModel{ Version: int(model.Version), DepthMap: toDomainMockupModelMap(model.DepthMap), DepthScaleMin: model.DepthScaleMin, DepthScaleMax: model.DepthScaleMax, ShadeMap: toDomainMockupModelMap(model.ShadeMap), SegmentationMap: toDomainMockupModelMap(model.SegmentationMap), XOffsetMap: toDomainMockupModelMap(model.XOffsetMap), YOffsetMap: toDomainMockupModelMap(model.YOffsetMap), CameraIntrinsics: design.MockupCameraIntrinsics{ Matrix: append([]float64(nil), model.CameraIntrinsics.Matrix...), }, ColorCorrection: model.ColorCorrection, ForegroundImage: model.ForegroundImage, } } func toDomainMockupModelMap(modelMap types.MockupModelMap) design.MockupModelMap { return design.MockupModelMap{ DataID: modelMap.DataId, Width: int(modelMap.Width), Height: int(modelMap.Height), URL: modelMap.Url, Data: append([]float64(nil), modelMap.Data...), } } func toAPIMockupPoints(points []design.MockupPoint) []types.MockupPoint { if len(points) == 0 { return nil } resp := make([]types.MockupPoint, 0, len(points)) for _, point := range points { resp = append(resp, types.MockupPoint{X: point.X, Y: point.Y}) } return resp } func toDomainMockupPoints(points []types.MockupPoint) []design.MockupPoint { if len(points) == 0 { return nil } resp := make([]design.MockupPoint, 0, len(points)) for _, point := range points { resp = append(resp, design.MockupPoint{X: point.X, Y: point.Y}) } return resp } func toAPIConnections(items []design.Connection) []types.CanvasConnection { resp := make([]types.CanvasConnection, 0, len(items)) for _, item := range items { resp = append(resp, types.CanvasConnection{ Id: item.ID, FromNodeId: item.FromNodeID, ToNodeId: item.ToNodeID, }) } return resp } func toDomainConnections(items []types.CanvasConnection) []design.Connection { resp := make([]design.Connection, 0, len(items)) for _, item := range items { resp = append(resp, design.Connection{ ID: item.Id, FromNodeID: item.FromNodeId, ToNodeID: item.ToNodeId, }) } return resp } func toAPIMessages(items []design.Message) []types.AgentMessage { resp := make([]types.AgentMessage, 0, len(items)) for _, item := range items { resp = append(resp, toAPIMessage(item)) } return resp } func isHiddenAgentMessage(item design.Message) bool { return item.Type == "text_extraction_result" || item.Name == "text_extraction" || item.Name == "canvas_action" || item.ToolHint == "canvas_action" } func hiddenAgentThreadIDs(items []design.Message) map[string]bool { ids := make(map[string]bool) for _, item := range items { if item.ThreadID == "" || !isHiddenAgentMessage(item) { continue } ids[item.ThreadID] = true } return ids } func toAPIMessage(item design.Message) types.AgentMessage { return types.AgentMessage{ Id: item.ID, Role: item.Role, Type: item.Type, Title: item.Title, Content: item.Content, ThreadId: item.ThreadID, ActionId: item.ActionID, StepId: item.StepID, ToolCallId: item.ToolCallID, Name: item.Name, ToolHint: item.ToolHint, Status: item.Status, CreatedAt: formatTime(item.CreatedAt), } } func toAPIView(item design.Viewport) types.CanvasViewport { return types.CanvasViewport{X: item.X, Y: item.Y, K: item.K} } func toDomainView(item types.CanvasViewport) design.Viewport { return design.Viewport{X: item.X, Y: item.Y, K: item.K} } func toDomainAgentChat(req *types.AgentChatRequest) design.AgentChatRequest { messages := make([]design.AgentChatMessage, 0, len(req.Messages)) for _, message := range req.Messages { contents := make([]design.AgentContent, 0, len(message.Contents)) for _, content := range message.Contents { contents = append(contents, design.AgentContent{ Type: content.Type, Text: content.Text, TextSource: content.TextSource, ImageURL: content.ImageUrl, ImageName: content.ImageName, ImageWidth: content.ImageWidth, ImageHeight: content.ImageHeight, }) } messages = append(messages, design.AgentChatMessage{ Role: message.Role, Contents: contents, }) } return design.AgentChatRequest{ Messages: messages, ToolConfig: design.AgentToolConfig{ PreferToolCategories: map[string][]string{ "SEARCH": req.ToolConfig.PreferToolCategories.Search, "IMAGE": req.ToolConfig.PreferToolCategories.Image, }, }, ThreadID: req.ThreadId, ThreadIDType: req.ThreadIdType, EnableMultimodalInput: req.EnableMultimodalInput, EnableWebSearch: req.EnableWebSearch || hasPreferredToolCategory(req.ToolConfig.PreferToolCategories.Search), ImageModel: req.ImageModel, ImageSize: req.ImageSize, Mode: req.Mode, } } func hasPreferredToolCategory(items []string) bool { for _, item := range items { if item != "" { return true } } return false } func toAPIAgentThread(thread design.AgentThread) *types.AgentThreadResponse { return &types.AgentThreadResponse{ ThreadId: thread.ThreadID, Status: thread.Status, ThreadIdType: thread.ThreadIDType, EventsUrl: "/api/projects/" + thread.Project.ID + "/events?threadId=" + url.QueryEscape(thread.ThreadID), Project: toAPIProject(thread.Project), } } func toAPIAgentThreadStatus(thread design.AgentThread) *types.AgentThreadStatusResponse { return &types.AgentThreadStatusResponse{ ThreadId: thread.ThreadID, Status: thread.Status, ThreadIdType: thread.ThreadIDType, } } func toAPIAgentThreadSummaries(items []design.AgentThreadSummary) []types.AgentThreadSummary { resp := make([]types.AgentThreadSummary, 0, len(items)) for _, item := range items { resp = append(resp, types.AgentThreadSummary{ AgentThreadId: item.AgentThreadID, CoverImageUrl: item.CoverImageURL, Text: item.Text, ThreadIdType: item.ThreadIDType, UpdateTimestamp: item.UpdateTimestamp, }) } return resp } func formatTime(value time.Time) string { if value.IsZero() { return "" } return value.UTC().Format(time.RFC3339) }