package model import ( "encoding/json" "github.com/pgvector/pgvector-go" ) // --- Captain Enums (M10 P10) --- type AssistantStatus string const ( AssistantStatusActive AssistantStatus = "active" AssistantStatusDraft AssistantStatus = "draft" AssistantStatusArchived AssistantStatus = "archived" ) type DocumentStatus string const ( DocumentStatusPending DocumentStatus = "pending" DocumentStatusInProgress DocumentStatus = "in_progress" DocumentStatusCompleted DocumentStatus = "completed" DocumentStatusFailed DocumentStatus = "failed" ) type DocumentSyncStatus string const ( DocumentSyncStatusPending DocumentSyncStatus = "pending" DocumentSyncStatusSynced DocumentSyncStatus = "synced" DocumentSyncStatusStale DocumentSyncStatus = "stale" DocumentSyncStatusFailed DocumentSyncStatus = "failed" ) type ResponseStatus string const ( ResponseStatusApproved ResponseStatus = "approved" ResponseStatusPending ResponseStatus = "pending" ResponseStatusRejected ResponseStatus = "rejected" ) type CopilotMessageType string const ( CopilotMessageTypeUser CopilotMessageType = "user" CopilotMessageTypeAssistant CopilotMessageType = "assistant" CopilotMessageTypeAssistantThinking CopilotMessageType = "assistant_thinking" ) type ToolAuthType string const ( ToolAuthTypeNone ToolAuthType = "none" ToolAuthTypeBasic ToolAuthType = "basic" ToolAuthTypeBearer ToolAuthType = "bearer" ToolAuthTypeApiKey ToolAuthType = "api_key" ) // --- Captain Assistant Model --- // Reference: Chatwoot enterprise/app/models/captain/assistant.rb // Table: captain_assistants // // Captain Assistant is the core orchestrator entity for AI features. // It holds configuration (temperature, feature flags, product name), // guardrails, and response_guidelines as JSONB. // Associated with Account, has Documents, Responses, Scenarios, Inboxes (via CaptainInbox). type CaptainAssistant struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` Name string `gorm:"size:255;not null" json:"name"` Description string `gorm:"size:1024" json:"description"` Config json.RawMessage `gorm:"type:jsonb;not null;default:'{}';serializer:json" json:"config"` Guardrails json.RawMessage `gorm:"type:jsonb;serializer:json" json:"guardrails,omitempty"` ResponseGuidelines json.RawMessage `gorm:"type:jsonb;serializer:json" json:"response_guidelines,omitempty"` Status AssistantStatus `gorm:"size:50;default:active" json:"status"` // Relationships (loaded via service layer or GORM preload) Documents []CaptainDocument `gorm:"foreignKey:AssistantID" json:"documents,omitempty"` Responses []CaptainAssistantResponse `gorm:"foreignKey:AssistantID" json:"responses,omitempty"` Scenarios []CaptainScenario `gorm:"foreignKey:AssistantID" json:"scenarios,omitempty"` CaptainInboxes []CaptainInbox `gorm:"foreignKey:AssistantID" json:"captain_inboxes,omitempty"` CopilotThreads []CopilotThread `gorm:"foreignKey:AssistantID" json:"copilot_threads,omitempty"` } func (CaptainAssistant) TableName() string { return "captain_assistants" } // DefaultAssistantConfig returns the default configuration for a new Captain Assistant. // Reference: Chatwoot Captain::Assistant default config values func DefaultAssistantConfig() map[string]interface{} { return map[string]interface{}{ "temperature": 0.7, "product_name": "", "feature_flags": map[string]bool{}, "response_guidelines": "", } } // AssistantConfig provides typed access to JSONB config fields. // Reference: Chatwoot store_accessor :config, :temperature, :feature_faq, etc. type AssistantConfig struct { Temperature float64 `json:"temperature,omitempty"` FeatureFAQ bool `json:"feature_faq,omitempty"` FeatureMemory bool `json:"feature_memory,omitempty"` FeatureContactAttributes bool `json:"feature_contact_attributes,omitempty"` ProductName string `json:"product_name,omitempty"` Instructions string `json:"instructions,omitempty"` Model string `json:"model,omitempty"` } func (a *CaptainAssistant) GetConfig() (*AssistantConfig, error) { var cfg AssistantConfig if len(a.Config) == 0 || string(a.Config) == "{}" || string(a.Config) == "null" { return &cfg, nil } if err := json.Unmarshal(a.Config, &cfg); err != nil { return nil, err } return &cfg, nil } func (a *CaptainAssistant) SetConfig(cfg *AssistantConfig) error { data, err := json.Marshal(cfg) if err != nil { return err } a.Config = data return nil } // GetResponseGuidelines unmarshals the ResponseGuidelines JSON field. // Returns nil and no error if the field is empty/null. func (a *CaptainAssistant) GetResponseGuidelines() (string, error) { if len(a.ResponseGuidelines) == 0 || string(a.ResponseGuidelines) == "{}" || string(a.ResponseGuidelines) == "null" { return "", nil } // ResponseGuidelines may be a plain string or a JSON object // Try unmarshaling as string first var s string if err := json.Unmarshal(a.ResponseGuidelines, &s); err == nil { return s, nil } // Otherwise return the raw JSON as a string return string(a.ResponseGuidelines), nil } // GetGuardrails unmarshals the Guardrails JSON field. // Returns an empty string and no error if the field is empty/null. func (a *CaptainAssistant) GetGuardrails() (string, error) { if len(a.Guardrails) == 0 || string(a.Guardrails) == "{}" || string(a.Guardrails) == "null" { return "", nil } var s string if err := json.Unmarshal(a.Guardrails, &s); err == nil { return s, nil } return string(a.Guardrails), nil } // --- Captain Document Model --- // Reference: Chatwoot enterprise/app/models/captain/document.rb // Table: captain_documents // // Captain Document represents a knowledge base entry (web page or PDF) // linked to an Assistant. Contains content, sync tracking, metadata, // and content_fingerprint for deduplication. type CaptainDocument struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` AssistantID uint `gorm:"index;not null" json:"assistant_id"` Name string `gorm:"size:255" json:"name"` ExternalLink string `gorm:"size:2048;not null" json:"external_link"` Content string `gorm:"type:text" json:"content,omitempty"` ContentFingerprint string `gorm:"size:64" json:"content_fingerprint,omitempty"` Status DocumentStatus `gorm:"size:50;default:in_progress;not null" json:"status"` SyncStatus DocumentSyncStatus `gorm:"size:50" json:"sync_status,omitempty"` LastSyncedAt *int64 `json:"last_synced_at,omitempty"` LastSyncAttemptedAt *int64 `json:"last_sync_attempted_at,omitempty"` LastSyncErrorCode string `gorm:"size:50" json:"last_sync_error_code,omitempty"` Metadata json.RawMessage `gorm:"type:jsonb;serializer:json" json:"metadata,omitempty"` // Relationships Assistant CaptainAssistant `gorm:"foreignKey:AssistantID" json:"assistant,omitempty"` Responses []CaptainAssistantResponse `gorm:"foreignKey:DocumentableID" json:"responses,omitempty"` } func (CaptainDocument) TableName() string { return "captain_documents" } // --- Captain AssistantResponse Model --- // Reference: Chatwoot enterprise/app/models/captain/assistant_response.rb // Table: captain_assistant_responses // // FAQ-style knowledge entry with question/answer pair. // Has pgvector embedding (1536 dims) for similarity search. // Polymorphic documentable association (can belong to Assistant or Document). // Status workflow: approved/pending/rejected. type CaptainAssistantResponse struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` AssistantID uint `gorm:"index;not null" json:"assistant_id"` DocumentableID *uint `gorm:"index" json:"documentable_id,omitempty"` DocumentableType string `gorm:"size:50" json:"documentable_type,omitempty"` // "CaptainAssistant" or "CaptainDocument" Question string `gorm:"size:1024;not null" json:"question"` Answer string `gorm:"type:text;not null" json:"answer"` Status ResponseStatus `gorm:"size:50;default:approved;not null" json:"status"` Edited bool `gorm:"default:false;not null" json:"edited"` // pgvector-go Vector type for 1536-dimensional embeddings Embedding pgvector.Vector `gorm:"type:vector(1536)" json:"embedding,omitempty"` Assistant CaptainAssistant `gorm:"foreignKey:AssistantID" json:"assistant,omitempty"` } func (CaptainAssistantResponse) TableName() string { return "captain_assistant_responses" } // --- Captain Scenario Model --- // Reference: Chatwoot enterprise/app/models/captain/scenario.rb // Table: captain_scenarios // // Scenario is a specialized agent configuration within an Assistant. // It can be enabled/disabled, has its own instruction text, // and declares which tools it uses (as JSONB array). // Scenario acts as a "handoff" target in the multi-agent system. type CaptainScenario struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` AssistantID uint `gorm:"index;not null" json:"assistant_id"` Title string `gorm:"size:255" json:"title"` Description string `gorm:"type:text" json:"description,omitempty"` Instruction string `gorm:"type:text" json:"instruction,omitempty"` Enabled bool `gorm:"default:true;not null;index" json:"enabled"` Tools json.RawMessage `gorm:"type:jsonb;serializer:json" json:"tools,omitempty"` // array of tool names Assistant CaptainAssistant `gorm:"foreignKey:AssistantID" json:"assistant,omitempty"` } func (CaptainScenario) TableName() string { return "captain_scenarios" } // --- Captain CustomTool Model --- // Reference: Chatwoot enterprise/app/models/captain/custom_tool.rb // Table: captain_custom_tools // // User-defined HTTP tool with configurable endpoint, method, // auth, request/response templates (Go template equivalent of Liquid), // and parameter schema. Max 15 per account. Slug is unique per account. type CaptainCustomTool struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` Title string `gorm:"size:255;not null" json:"title"` Slug string `gorm:"size:64;not null;uniqueIndex:idx_account_slug" json:"slug"` Description string `gorm:"type:text" json:"description,omitempty"` EndpointURL string `gorm:"type:text;not null" json:"endpoint_url"` HTTPMethod string `gorm:"size:10;default:GET;not null" json:"http_method"` AuthType ToolAuthType `gorm:"size:20;default:none" json:"auth_type"` AuthConfig json.RawMessage `gorm:"type:jsonb;serializer:json" json:"auth_config,omitempty"` ParamSchema json.RawMessage `gorm:"type:jsonb;serializer:json" json:"param_schema,omitempty"` RequestTemplate string `gorm:"type:text" json:"request_template,omitempty"` ResponseTemplate string `gorm:"type:text" json:"response_template,omitempty"` Enabled bool `gorm:"default:true;not null" json:"enabled"` } func (CaptainCustomTool) TableName() string { return "captain_custom_tools" } // --- Captain Inbox (join table) --- // Reference: Chatwoot CaptainInbox model // Table: captain_inboxes // // Links a Captain Assistant to an Inbox, enabling the assistant // to operate on conversations in that inbox. type CaptainInbox struct { Base AssistantID uint `gorm:"column:captain_assistant_id;index;not null" json:"captain_assistant_id"` InboxID uint `gorm:"index;not null" json:"inbox_id"` AccountID uint `gorm:"index;not null" json:"account_id"` } func (CaptainInbox) TableName() string { return "captain_inboxes" } // --- Captain Preference --- // Reference: Chatwoot enterprise/app/models/captain/preference.rb // Table: captain_preferences // // Per-account AI configuration: tone, language, response guidelines, // and feature toggles. Each account has at most one preference record. type CaptainPreference struct { Base AccountID uint `gorm:"uniqueIndex;not null" json:"account_id"` Tone string `gorm:"size:50;default:professional" json:"tone"` // professional, friendly, casual, formal Language string `gorm:"size:10;default:en" json:"language"` // ISO 639-1 language code ResponseGuidelines string `gorm:"type:text" json:"response_guidelines,omitempty"` // free-form instructions for AI responses AutoLabelEnabled bool `gorm:"default:false" json:"auto_label_enabled"` // auto-apply label suggestions AutoFollowUpEnabled bool `gorm:"default:false" json:"auto_follow_up_enabled"` // auto-create follow-up tasks AutoReplyEnabled bool `gorm:"default:false" json:"auto_reply_enabled"` // auto-send assistant responses MaxResponseLength int `gorm:"default:500" json:"max_response_length"` // max characters in AI response CustomPromptSuffix string `gorm:"type:text" json:"custom_prompt_suffix,omitempty"` // appended to system prompts } func (CaptainPreference) TableName() string { return "captain_preferences" }