Files
gochat/internal/model/captain_models.go
T

307 lines
13 KiB
Go

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;uniqueIndex:idx_captain_custom_tools_account_slug,priority:1;not null" json:"account_id"`
Title string `gorm:"size:255;not null" json:"title"`
Slug string `gorm:"size:64;not null;uniqueIndex:idx_captain_custom_tools_account_slug,priority:2" 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" }