Changeset 0.9.0 (#50)
This commit is contained in:
@@ -1,77 +1,320 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BaseModel contains fields common to all models
|
||||
// ── Base ────────────────────────────────────
|
||||
|
||||
type BaseModel struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// User represents a user in the system
|
||||
type User struct {
|
||||
BaseModel
|
||||
Email string `json:"email" db:"email"`
|
||||
PasswordHash string `json:"-" db:"password_hash"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Role string `json:"role" db:"role"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
}
|
||||
// ── Scope Constants ─────────────────────────
|
||||
|
||||
const (
|
||||
ScopeGlobal = "global"
|
||||
ScopeTeam = "team"
|
||||
ScopePersonal = "personal"
|
||||
)
|
||||
|
||||
// ── Visibility Constants ────────────────────
|
||||
|
||||
const (
|
||||
VisibilityVisible = "visible"
|
||||
VisibilityHidden = "hidden"
|
||||
)
|
||||
|
||||
// ── Role Constants ──────────────────────────
|
||||
|
||||
// UserRole constants
|
||||
const (
|
||||
UserRoleUser = "user"
|
||||
UserRoleAdmin = "admin"
|
||||
|
||||
TeamRoleAdmin = "admin"
|
||||
TeamRoleMember = "member"
|
||||
)
|
||||
|
||||
// ── Channel Types ───────────────────────────
|
||||
// ── Grant Type Constants ────────────────────
|
||||
|
||||
const (
|
||||
ChannelTypeDirect = "direct" // 1:1 AI chat (legacy "chat")
|
||||
ChannelTypeGroup = "group" // multi-model conversation
|
||||
ChannelTypeChannel = "channel" // named, persistent, membered
|
||||
GrantTypeTool = "tool"
|
||||
GrantTypeKnowledgeBase = "knowledge_base"
|
||||
GrantTypeAPIEndpoint = "api_endpoint"
|
||||
)
|
||||
|
||||
// Channel represents a conversation channel (unified: chats + channels).
|
||||
type Channel struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Title string `json:"title" db:"title"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
Type string `json:"type" db:"type"`
|
||||
Model string `json:"model,omitempty" db:"model"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
APIConfigID *string `json:"api_config_id,omitempty" db:"api_config_id"`
|
||||
IsArchived bool `json:"is_archived" db:"is_archived"`
|
||||
IsPinned bool `json:"is_pinned" db:"is_pinned"`
|
||||
FolderID *string `json:"folder_id,omitempty" db:"folder_id"`
|
||||
}
|
||||
// ── Channel Type Constants ──────────────────
|
||||
|
||||
// Message represents a message in a channel
|
||||
type Message struct {
|
||||
BaseModel
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
Role string `json:"role" db:"role"`
|
||||
Content string `json:"content" db:"content"`
|
||||
Tokens int `json:"tokens,omitempty" db:"tokens"`
|
||||
Model string `json:"model,omitempty" db:"model"`
|
||||
FinishReason string `json:"finish_reason,omitempty" db:"finish_reason"`
|
||||
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
||||
ParticipantType string `json:"participant_type,omitempty" db:"participant_type"`
|
||||
ParticipantID string `json:"participant_id,omitempty" db:"participant_id"`
|
||||
}
|
||||
const (
|
||||
ChannelTypeDirect = "direct"
|
||||
ChannelTypeGroup = "group"
|
||||
ChannelTypeChannel = "channel"
|
||||
)
|
||||
|
||||
// ── Message Role Constants ──────────────────
|
||||
|
||||
const (
|
||||
MessageRoleUser = "user"
|
||||
MessageRoleAssistant = "assistant"
|
||||
MessageRoleSystem = "system"
|
||||
MessageRoleTool = "tool"
|
||||
)
|
||||
|
||||
// ── Channel Members & Models ────────────────
|
||||
// =========================================
|
||||
// USERS
|
||||
// =========================================
|
||||
|
||||
type User struct {
|
||||
BaseModel
|
||||
Username string `json:"username" db:"username"`
|
||||
Email string `json:"email" db:"email"`
|
||||
PasswordHash string `json:"-" db:"password_hash"`
|
||||
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
||||
AvatarURL string `json:"avatar_url,omitempty" db:"avatar_url"`
|
||||
Role string `json:"role" db:"role"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// TEAMS
|
||||
// =========================================
|
||||
|
||||
type Team struct {
|
||||
BaseModel
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
||||
MemberCount int `json:"member_count,omitempty"` // computed
|
||||
}
|
||||
|
||||
type TeamMember struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
TeamID string `json:"team_id" db:"team_id"`
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Role string `json:"role" db:"role"`
|
||||
JoinedAt string `json:"joined_at" db:"joined_at"`
|
||||
// Joined fields from users table
|
||||
Email string `json:"email,omitempty"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
UserRole string `json:"user_role,omitempty"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// PROVIDER CONFIGS (replaces APIConfig)
|
||||
// =========================================
|
||||
|
||||
type ProviderConfig struct {
|
||||
BaseModel
|
||||
Scope string `json:"scope" db:"scope"`
|
||||
OwnerID *string `json:"owner_id,omitempty" db:"owner_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Provider string `json:"provider" db:"provider"`
|
||||
Endpoint string `json:"endpoint" db:"endpoint"`
|
||||
APIKeyEnc string `json:"-" db:"api_key_enc"`
|
||||
ModelDefault string `json:"model_default,omitempty" db:"model_default"`
|
||||
Config JSONMap `json:"config,omitempty" db:"config"`
|
||||
Headers JSONMap `json:"headers,omitempty" db:"headers"`
|
||||
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
IsPrivate bool `json:"is_private" db:"is_private"`
|
||||
}
|
||||
|
||||
type ProviderConfigPatch struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Endpoint *string `json:"endpoint,omitempty"`
|
||||
APIKeyEnc *string `json:"-"`
|
||||
ModelDefault *string `json:"model_default,omitempty"`
|
||||
Config JSONMap `json:"config,omitempty"`
|
||||
Headers JSONMap `json:"headers,omitempty"`
|
||||
Settings JSONMap `json:"settings,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
IsPrivate *bool `json:"is_private,omitempty"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// MODEL CATALOG (replaces model_configs)
|
||||
// =========================================
|
||||
|
||||
type CatalogEntry struct {
|
||||
BaseModel
|
||||
ProviderConfigID string `json:"provider_config_id" db:"provider_config_id"`
|
||||
ModelID string `json:"model_id" db:"model_id"`
|
||||
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
||||
Capabilities ModelCapabilities `json:"capabilities" db:"capabilities"`
|
||||
Pricing *ModelPricing `json:"pricing,omitempty" db:"pricing"`
|
||||
Visibility string `json:"visibility" db:"visibility"`
|
||||
LastSyncedAt *time.Time `json:"last_synced_at,omitempty" db:"last_synced_at"`
|
||||
}
|
||||
|
||||
type ModelCapabilities struct {
|
||||
Streaming bool `json:"streaming"`
|
||||
ToolCalling bool `json:"tool_calling"`
|
||||
Vision bool `json:"vision"`
|
||||
Thinking bool `json:"thinking"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
CodeOptimized bool `json:"code_optimized"`
|
||||
WebSearch bool `json:"web_search"`
|
||||
MaxContext int `json:"max_context"`
|
||||
MaxOutputTokens int `json:"max_output_tokens"`
|
||||
}
|
||||
|
||||
func (c ModelCapabilities) HasProviderData() bool {
|
||||
return c.ToolCalling || c.Vision || c.Thinking || c.Reasoning ||
|
||||
c.CodeOptimized || c.WebSearch || c.MaxContext > 0 || c.MaxOutputTokens > 0
|
||||
}
|
||||
|
||||
type ModelPricing struct {
|
||||
InputPerM float64 `json:"input_per_m,omitempty"`
|
||||
OutputPerM float64 `json:"output_per_m,omitempty"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// PERSONAS (replaces ModelPreset)
|
||||
// =========================================
|
||||
|
||||
type Persona struct {
|
||||
BaseModel
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
Icon string `json:"icon,omitempty" db:"icon"`
|
||||
Avatar string `json:"avatar,omitempty" db:"avatar"`
|
||||
|
||||
BaseModelID string `json:"base_model_id" db:"base_model_id"`
|
||||
ProviderConfigID *string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
||||
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
Temperature *float64 `json:"temperature,omitempty" db:"temperature"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty" db:"max_tokens"`
|
||||
ThinkingBudget *int `json:"thinking_budget,omitempty" db:"thinking_budget"`
|
||||
TopP *float64 `json:"top_p,omitempty" db:"top_p"`
|
||||
|
||||
Scope string `json:"scope" db:"scope"`
|
||||
OwnerID *string `json:"owner_id,omitempty" db:"owner_id"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
IsShared bool `json:"is_shared" db:"is_shared"`
|
||||
|
||||
// Loaded from persona_grants, not stored in personas table
|
||||
Grants []Grant `json:"grants,omitempty" db:"-"`
|
||||
}
|
||||
|
||||
type PersonaPatch struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Icon *string `json:"icon,omitempty"`
|
||||
Avatar *string `json:"avatar,omitempty"`
|
||||
BaseModelID *string `json:"base_model_id,omitempty"`
|
||||
ProviderConfigID *string `json:"provider_config_id,omitempty"`
|
||||
SystemPrompt *string `json:"system_prompt,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty"`
|
||||
ThinkingBudget *int `json:"thinking_budget,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
IsShared *bool `json:"is_shared,omitempty"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// GRANTS
|
||||
// =========================================
|
||||
|
||||
type Grant struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
PersonaID string `json:"persona_id" db:"persona_id"`
|
||||
GrantType string `json:"grant_type" db:"grant_type"`
|
||||
GrantRef string `json:"grant_ref" db:"grant_ref"`
|
||||
Config JSONMap `json:"config,omitempty" db:"config"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// PLATFORM POLICIES
|
||||
// =========================================
|
||||
|
||||
var PolicyDefaults = map[string]string{
|
||||
"allow_user_byok": "false",
|
||||
"allow_user_personas": "false",
|
||||
"allow_raw_model_access": "false",
|
||||
"allow_registration": "true",
|
||||
"default_user_active": "false",
|
||||
"allow_team_providers": "true",
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// USER MODEL SETTINGS
|
||||
// =========================================
|
||||
|
||||
type UserModelSetting struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
ModelID string `json:"model_id" db:"model_id"`
|
||||
Hidden bool `json:"hidden" db:"hidden"`
|
||||
PreferredTemperature *float64 `json:"preferred_temperature,omitempty" db:"preferred_temperature"`
|
||||
PreferredMaxTokens *int `json:"preferred_max_tokens,omitempty" db:"preferred_max_tokens"`
|
||||
SortOrder int `json:"sort_order" db:"sort_order"`
|
||||
}
|
||||
|
||||
type UserModelSettingPatch struct {
|
||||
Hidden *bool `json:"hidden,omitempty"`
|
||||
PreferredTemperature *float64 `json:"preferred_temperature,omitempty"`
|
||||
PreferredMaxTokens *int `json:"preferred_max_tokens,omitempty"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// CHANNELS
|
||||
// =========================================
|
||||
|
||||
type Channel struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Title string `json:"title" db:"title"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
Type string `json:"type" db:"type"`
|
||||
Model string `json:"model,omitempty" db:"model"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
ProviderConfigID *string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
||||
IsArchived bool `json:"is_archived" db:"is_archived"`
|
||||
IsPinned bool `json:"is_pinned" db:"is_pinned"`
|
||||
FolderID *string `json:"folder_id,omitempty" db:"folder_id"`
|
||||
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
||||
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// MESSAGES
|
||||
// =========================================
|
||||
|
||||
type Message struct {
|
||||
BaseModel
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
Role string `json:"role" db:"role"`
|
||||
Content string `json:"content" db:"content"`
|
||||
Model string `json:"model,omitempty" db:"model"`
|
||||
TokensUsed int `json:"tokens_used,omitempty" db:"tokens_used"`
|
||||
ToolCalls JSONMap `json:"tool_calls,omitempty" db:"tool_calls"`
|
||||
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
||||
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
||||
SiblingIndex int `json:"sibling_index" db:"sibling_index"`
|
||||
ParticipantType string `json:"participant_type,omitempty" db:"participant_type"`
|
||||
ParticipantID string `json:"participant_id,omitempty" db:"participant_id"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty" db:"deleted_at"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// CHANNEL MEMBERS, MODELS, CURSORS
|
||||
// =========================================
|
||||
|
||||
type ChannelMember struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
@@ -83,13 +326,13 @@ type ChannelMember struct {
|
||||
}
|
||||
|
||||
type ChannelModel struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
ModelID string `json:"model_id" db:"model_id"`
|
||||
APIConfigID string `json:"api_config_id,omitempty" db:"api_config_id"`
|
||||
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
IsDefault bool `json:"is_default" db:"is_default"`
|
||||
ID string `json:"id" db:"id"`
|
||||
ChannelID string `json:"channel_id" db:"channel_id"`
|
||||
ModelID string `json:"model_id" db:"model_id"`
|
||||
ProviderConfigID string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
||||
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
IsDefault bool `json:"is_default" db:"is_default"`
|
||||
}
|
||||
|
||||
type ChannelCursor struct {
|
||||
@@ -100,13 +343,16 @@ type ChannelCursor struct {
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// ── Organization ────────────────────────────
|
||||
// =========================================
|
||||
// ORGANIZATION
|
||||
// =========================================
|
||||
|
||||
type Folder struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
||||
SortOrder int `json:"sort_order" db:"sort_order"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
@@ -117,114 +363,134 @@ type Project struct {
|
||||
Color string `json:"color,omitempty" db:"color"`
|
||||
}
|
||||
|
||||
// ── API Config ──────────────────────────────
|
||||
|
||||
type APIConfig struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Provider string `json:"provider" db:"provider"`
|
||||
APIKey string `json:"-" db:"api_key"`
|
||||
BaseURL string `json:"base_url,omitempty" db:"base_url"`
|
||||
Model string `json:"model" db:"model"`
|
||||
IsDefault bool `json:"is_default" db:"is_default"`
|
||||
}
|
||||
|
||||
// ── Notes (future Phase 2) ──────────────────
|
||||
// =========================================
|
||||
// NOTES
|
||||
// =========================================
|
||||
|
||||
type Note struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Title string `json:"title" db:"title"`
|
||||
Content string `json:"content" db:"content"`
|
||||
Tags []string `json:"tags,omitempty" db:"tags"`
|
||||
FolderID string `json:"folder_id,omitempty" db:"folder_id"`
|
||||
IsShared bool `json:"is_shared" db:"is_shared"`
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Title string `json:"title" db:"title"`
|
||||
Content string `json:"content" db:"content"`
|
||||
FolderPath string `json:"folder_path" db:"folder_path"`
|
||||
Tags []string `json:"tags,omitempty" db:"tags"`
|
||||
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
||||
SourceChannelID *string `json:"source_channel_id,omitempty" db:"source_channel_id"`
|
||||
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
||||
}
|
||||
|
||||
type KnowledgeBase struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Source string `json:"source" db:"source"`
|
||||
Settings string `json:"settings,omitempty" db:"settings"`
|
||||
// =========================================
|
||||
// AUDIT LOG
|
||||
// =========================================
|
||||
|
||||
type AuditEntry struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
ActorID *string `json:"actor_id,omitempty" db:"actor_id"`
|
||||
Action string `json:"action" db:"action"`
|
||||
ResourceType string `json:"resource_type" db:"resource_type"`
|
||||
ResourceID string `json:"resource_id,omitempty" db:"resource_id"`
|
||||
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
||||
IPAddress string `json:"ip_address,omitempty" db:"ip_address"`
|
||||
UserAgent string `json:"user_agent,omitempty" db:"user_agent"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
}
|
||||
|
||||
// ── Model Presets ──────────────────────────
|
||||
// =========================================
|
||||
// VIEW MODELS (computed, not stored)
|
||||
// =========================================
|
||||
|
||||
const (
|
||||
PresetScopeGlobal = "global"
|
||||
PresetScopeTeam = "team"
|
||||
PresetScopePersonal = "personal"
|
||||
)
|
||||
// UserModel is the view model returned by the capability resolver.
|
||||
// Combines catalog entries + Personas for the frontend.
|
||||
type UserModel struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ModelID string `json:"model_id"`
|
||||
Source string `json:"source"` // "catalog", "persona", "live"
|
||||
|
||||
type ModelPreset struct {
|
||||
BaseModel
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
BaseModelID string `json:"base_model_id" db:"base_model_id"`
|
||||
APIConfigID *string `json:"api_config_id,omitempty" db:"api_config_id"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
Temperature *float64 `json:"temperature,omitempty" db:"temperature"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty" db:"max_tokens"`
|
||||
ToolsEnabled string `json:"tools_enabled,omitempty" db:"tools_enabled"` // JSON array
|
||||
Scope string `json:"scope" db:"scope"`
|
||||
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
IsShared bool `json:"is_shared" db:"is_shared"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
Icon string `json:"icon,omitempty" db:"icon"`
|
||||
Avatar string `json:"avatar,omitempty" db:"avatar"`
|
||||
ProviderConfigID string `json:"provider_config_id"`
|
||||
ConfigID string `json:"config_id"` // Alias of ProviderConfigID for frontend compat
|
||||
ProviderName string `json:"provider_name"`
|
||||
ProviderType string `json:"provider_type"`
|
||||
|
||||
Capabilities ModelCapabilities `json:"capabilities"`
|
||||
|
||||
// Preset fields — always emitted so frontend can branch on is_preset.
|
||||
IsPreset bool `json:"is_preset"`
|
||||
PresetID string `json:"preset_id,omitempty"`
|
||||
PresetScope string `json:"preset_scope,omitempty"`
|
||||
PresetAvatar string `json:"preset_avatar,omitempty"`
|
||||
PresetTeamName string `json:"preset_team_name,omitempty"`
|
||||
|
||||
PersonaID string `json:"persona_id,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty"`
|
||||
ToolGrants []string `json:"tool_grants,omitempty"`
|
||||
|
||||
Pricing *ModelPricing `json:"pricing,omitempty"`
|
||||
|
||||
Scope string `json:"scope"`
|
||||
OwnerID *string `json:"owner_id,omitempty"`
|
||||
TeamName string `json:"team_name,omitempty"`
|
||||
|
||||
Hidden bool `json:"hidden"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
// ── Teams ───────────────────────────────────
|
||||
// =========================================
|
||||
// JSON HELPERS
|
||||
// =========================================
|
||||
|
||||
const (
|
||||
TeamRoleAdmin = "admin"
|
||||
TeamRoleMember = "member"
|
||||
)
|
||||
// JSONMap scans from/to JSONB columns.
|
||||
type JSONMap map[string]interface{}
|
||||
|
||||
type Team struct {
|
||||
BaseModel
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description,omitempty" db:"description"`
|
||||
CreatedBy string `json:"created_by" db:"created_by"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
Settings string `json:"settings,omitempty" db:"settings"` // JSON
|
||||
MemberCount int `json:"member_count,omitempty"` // computed, not stored
|
||||
func (m *JSONMap) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*m = nil
|
||||
return nil
|
||||
}
|
||||
var source []byte
|
||||
switch v := src.(type) {
|
||||
case []byte:
|
||||
source = v
|
||||
case string:
|
||||
source = []byte(v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
result := make(JSONMap)
|
||||
if err := json.Unmarshal(source, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = result
|
||||
return nil
|
||||
}
|
||||
|
||||
type TeamMember struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
TeamID string `json:"team_id" db:"team_id"`
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Role string `json:"role" db:"role"`
|
||||
JoinedAt string `json:"joined_at" db:"joined_at"`
|
||||
// Joined fields (from user)
|
||||
Email string `json:"email,omitempty"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
UserRole string `json:"user_role,omitempty"` // system role (user/admin)
|
||||
func NullString(s *string) sql.NullString {
|
||||
if s == nil {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: *s, Valid: true}
|
||||
}
|
||||
|
||||
// ── Settings ────────────────────────────────
|
||||
|
||||
type Settings struct {
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Theme string `json:"theme" db:"theme"`
|
||||
Language string `json:"language" db:"language"`
|
||||
Model string `json:"model" db:"model"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
||||
MaxTokens int `json:"max_tokens" db:"max_tokens"`
|
||||
Temperature float64 `json:"temperature" db:"temperature"`
|
||||
DefaultAPIConfigID string `json:"default_api_config_id,omitempty" db:"default_api_config_id"`
|
||||
func NullFloat(f *float64) sql.NullFloat64 {
|
||||
if f == nil {
|
||||
return sql.NullFloat64{}
|
||||
}
|
||||
return sql.NullFloat64{Float64: *f, Valid: true}
|
||||
}
|
||||
|
||||
type APIToken struct {
|
||||
BaseModel
|
||||
UserID string `json:"user_id" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Token string `json:"-" db:"token"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty" db:"expires_at"`
|
||||
LastUsedAt *time.Time `json:"last_used_at,omitempty" db:"last_used_at"`
|
||||
IsActive bool `json:"is_active" db:"is_active"`
|
||||
func NullInt(i *int) sql.NullInt64 {
|
||||
if i == nil {
|
||||
return sql.NullInt64{}
|
||||
}
|
||||
return sql.NullInt64{Int64: int64(*i), Valid: true}
|
||||
}
|
||||
|
||||
func StringPtr(s string) *string { return &s }
|
||||
func Float64Ptr(f float64) *float64 { return &f }
|
||||
func IntPtr(i int) *int { return &i }
|
||||
func BoolPtr(b bool) *bool { return &b }
|
||||
|
||||
Reference in New Issue
Block a user