package chat import ( "time" "github.com/google/uuid" ) // ===== Request DTOs ===== // CreateConversationReq is the HTTP request body for POST /conversations. type CreateConversationReq struct { ModelID uuid.UUID `json:"model_id"` TemplateID *uuid.UUID `json:"template_id,omitempty"` SystemPrompt *string `json:"system_prompt,omitempty"` ProjectID *uuid.UUID `json:"project_id,omitempty"` WorkspaceID *uuid.UUID `json:"workspace_id,omitempty"` IssueID *uuid.UUID `json:"issue_id,omitempty"` RequirementID *uuid.UUID `json:"requirement_id,omitempty"` } // UpdateConversationTitleReq is the HTTP request body for PATCH /conversations/{id}. type UpdateConversationTitleReq struct { Title string `json:"title"` } // SendMessageReq is the HTTP request body for POST /conversations/{id}/messages. type SendMessageReq struct { Content string `json:"content"` AttachmentIDs []uuid.UUID `json:"attachment_ids,omitempty"` } // CreateTemplateReq is the HTTP request body for POST /prompt-templates. type CreateTemplateReq struct { Name string `json:"name"` Content string `json:"content"` Scope string `json:"scope"` // "user" | "system" } // UpdateTemplateReq is the HTTP request body for PATCH /prompt-templates/{id}. type UpdateTemplateReq struct { Name string `json:"name"` Content string `json:"content"` } // CreateEndpointReq is the HTTP request body for POST /admin/llm-endpoints. type CreateEndpointReq struct { Provider string `json:"provider"` DisplayName string `json:"display_name"` BaseURL string `json:"base_url"` APIKey string `json:"api_key"` } // UpdateEndpointReq is the HTTP request body for PATCH /admin/llm-endpoints/{id}. type UpdateEndpointReq struct { DisplayName *string `json:"display_name,omitempty"` BaseURL *string `json:"base_url,omitempty"` APIKey *string `json:"api_key,omitempty"` } // CreateModelReq is the HTTP request body for POST /admin/llm-models. type CreateModelReq struct { EndpointID uuid.UUID `json:"endpoint_id"` ModelID string `json:"model_id"` DisplayName string `json:"display_name"` Capabilities ModelCapabilities `json:"capabilities"` ContextWindow int `json:"context_window"` MaxOutputTokens int `json:"max_output_tokens"` PromptPricePerMillionUSD float64 `json:"prompt_price_per_million_usd"` CompletionPricePerMillionUSD float64 `json:"completion_price_per_million_usd"` ThinkingPricePerMillionUSD float64 `json:"thinking_price_per_million_usd"` IsTitleGenerator bool `json:"is_title_generator"` Enabled bool `json:"enabled"` SortOrder int `json:"sort_order"` } // UpdateModelReq is the HTTP request body for PATCH /admin/llm-models/{id}. type UpdateModelReq struct { DisplayName *string `json:"display_name,omitempty"` Capabilities *ModelCapabilities `json:"capabilities,omitempty"` ContextWindow *int `json:"context_window,omitempty"` MaxOutputTokens *int `json:"max_output_tokens,omitempty"` PromptPricePerMillionUSD *float64 `json:"prompt_price_per_million_usd,omitempty"` CompletionPricePerMillionUSD *float64 `json:"completion_price_per_million_usd,omitempty"` ThinkingPricePerMillionUSD *float64 `json:"thinking_price_per_million_usd,omitempty"` IsTitleGenerator *bool `json:"is_title_generator,omitempty"` Enabled *bool `json:"enabled,omitempty"` SortOrder *int `json:"sort_order,omitempty"` } // ===== Response DTOs ===== // ConversationDTO is the JSON representation of a Conversation. type ConversationDTO struct { ID uuid.UUID `json:"id"` UserID uuid.UUID `json:"user_id"` ModelID uuid.UUID `json:"model_id"` SystemPrompt string `json:"system_prompt"` Title *string `json:"title,omitempty"` ProjectID *uuid.UUID `json:"project_id,omitempty"` WorkspaceID *uuid.UUID `json:"workspace_id,omitempty"` IssueID *uuid.UUID `json:"issue_id,omitempty"` RequirementID *uuid.UUID `json:"requirement_id,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // MessageDTO is the JSON representation of a Message. type MessageDTO struct { ID int64 `json:"id"` ConversationID uuid.UUID `json:"conversation_id"` Role string `json:"role"` Content string `json:"content"` Thinking *string `json:"thinking,omitempty"` Status string `json:"status"` ErrorMessage *string `json:"error_message,omitempty"` PromptTokens *int `json:"prompt_tokens,omitempty"` CompletionTokens *int `json:"completion_tokens,omitempty"` ThinkingTokens *int `json:"thinking_tokens,omitempty"` Attachments []AttachmentDTO `json:"attachments"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // AttachmentDTO is the JSON representation of an Attachment. type AttachmentDTO struct { ID uuid.UUID `json:"id"` Filename string `json:"filename"` MimeType string `json:"mime_type"` SizeBytes int64 `json:"size_bytes"` SHA256 string `json:"sha256"` CreatedAt time.Time `json:"created_at"` } // SendMessageResp is the response body for POST /conversations/{id}/messages. type SendMessageResp struct { UserMessageID int64 `json:"user_message_id"` AssistantMessageID int64 `json:"assistant_message_id"` StreamURL string `json:"stream_url"` } // TemplateDTO is the JSON representation of a PromptTemplate. type TemplateDTO struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Content string `json:"content"` Scope string `json:"scope"` OwnerID *uuid.UUID `json:"owner_id,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // EndpointDTO is the JSON representation of a LLMEndpoint. // APIKeyEncrypted is intentionally excluded to prevent leaking secrets. type EndpointDTO struct { ID uuid.UUID `json:"id"` Provider string `json:"provider"` DisplayName string `json:"display_name"` BaseURL string `json:"base_url"` CreatedBy uuid.UUID `json:"created_by"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ModelDTO is the JSON representation of a LLMModel. type ModelDTO struct { ID uuid.UUID `json:"id"` EndpointID uuid.UUID `json:"endpoint_id"` ModelID string `json:"model_id"` DisplayName string `json:"display_name"` Capabilities ModelCapabilities `json:"capabilities"` ContextWindow int `json:"context_window"` MaxOutputTokens int `json:"max_output_tokens"` PromptPricePerMillionUSD float64 `json:"prompt_price_per_million_usd"` CompletionPricePerMillionUSD float64 `json:"completion_price_per_million_usd"` ThinkingPricePerMillionUSD float64 `json:"thinking_price_per_million_usd"` IsTitleGenerator bool `json:"is_title_generator"` Enabled bool `json:"enabled"` SortOrder int `json:"sort_order"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // DayUsageDTO is the per-day usage row returned to both regular users and admins. type DayUsageDTO struct { Day time.Time `json:"day"` PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` ThinkingTokens int `json:"thinking_tokens"` CostUSD float64 `json:"cost_usd"` } // UserUsageDTO is the per-user aggregate usage row returned to admins. type UserUsageDTO struct { UserID uuid.UUID `json:"user_id"` PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` ThinkingTokens int `json:"thinking_tokens"` CostUSD float64 `json:"cost_usd"` } // ModelUsageDTO is the per-model aggregate usage row returned to admins. type ModelUsageDTO struct { ModelID uuid.UUID `json:"model_id"` PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` ThinkingTokens int `json:"thinking_tokens"` CostUSD float64 `json:"cost_usd"` } // AdminUsageItem is a single aggregated usage row keyed by user/model/day. type AdminUsageItem struct { Key string `json:"key"` PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` ThinkingTokens int `json:"thinking_tokens"` CostUSD float64 `json:"cost_usd"` } // AdminUsageResp is the response body for GET /admin/usage. type AdminUsageResp struct { Items []AdminUsageItem `json:"items"` } // ===== Conversion helpers ===== func toConversationDTO(c *Conversation) ConversationDTO { return ConversationDTO{ ID: c.ID, UserID: c.UserID, ModelID: c.ModelID, SystemPrompt: c.SystemPrompt, Title: c.Title, ProjectID: c.ProjectID, WorkspaceID: c.WorkspaceID, IssueID: c.IssueID, RequirementID: c.RequirementID, CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, } } func toMessageDTO(m *Message) MessageDTO { return MessageDTO{ ID: m.ID, ConversationID: m.ConversationID, Role: string(m.Role), Content: m.Content, Thinking: m.Thinking, Status: string(m.Status), ErrorMessage: m.ErrorMessage, PromptTokens: m.PromptTokens, CompletionTokens: m.CompletionTokens, ThinkingTokens: m.ThinkingTokens, Attachments: toAttachmentDTOs(m.Attachments), CreatedAt: m.CreatedAt, UpdatedAt: m.UpdatedAt, } } // toAttachmentDTOs maps a slice of Attachment to DTOs, always returning a // non-nil (possibly empty) slice so the JSON field serializes as []. func toAttachmentDTOs(atts []Attachment) []AttachmentDTO { out := make([]AttachmentDTO, 0, len(atts)) for _, a := range atts { out = append(out, AttachmentDTO{ ID: a.ID, Filename: a.Filename, MimeType: a.MimeType, SizeBytes: a.SizeBytes, SHA256: a.SHA256, CreatedAt: a.CreatedAt, }) } return out } func toTemplateDTO(t *PromptTemplate) TemplateDTO { return TemplateDTO{ ID: t.ID, Name: t.Name, Content: t.Content, Scope: string(t.Scope), OwnerID: t.OwnerID, CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, } } func toEndpointDTO(ep *LLMEndpoint) EndpointDTO { return EndpointDTO{ ID: ep.ID, Provider: string(ep.Provider), DisplayName: ep.DisplayName, BaseURL: ep.BaseURL, CreatedBy: ep.CreatedBy, CreatedAt: ep.CreatedAt, UpdatedAt: ep.UpdatedAt, } } func toModelDTO(m *LLMModel) ModelDTO { return ModelDTO{ ID: m.ID, EndpointID: m.EndpointID, ModelID: m.ModelID, DisplayName: m.DisplayName, Capabilities: m.Capabilities, ContextWindow: m.ContextWindow, MaxOutputTokens: m.MaxOutputTokens, PromptPricePerMillionUSD: m.PromptPricePerMillionUSD, CompletionPricePerMillionUSD: m.CompletionPricePerMillionUSD, ThinkingPricePerMillionUSD: m.ThinkingPricePerMillionUSD, IsTitleGenerator: m.IsTitleGenerator, Enabled: m.Enabled, SortOrder: m.SortOrder, CreatedAt: m.CreatedAt, UpdatedAt: m.UpdatedAt, } }