Files
2026-06-10 17:53:09 +08:00

321 lines
12 KiB
Go

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,
}
}