committtttt
This commit is contained in:
127
backend/internal/models/ai.go
Normal file
127
backend/internal/models/ai.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AIModel represents an AI model configuration
|
||||
type AIModel struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Type string `json:"type" gorm:"not null"` // 'openai', 'local', 'custom'
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKey string `json:"apiKey"` // Encrypted in database
|
||||
Model string `json:"model"` // e.g., 'gpt-4', 'llama2', etc.
|
||||
MaxTokens int `json:"maxTokens" gorm:"default:1000"`
|
||||
Temperature float64 `json:"temperature" gorm:"default:0.7"`
|
||||
TopP float64 `json:"topP" gorm:"default:1.0"`
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
Priority int `json:"priority" gorm:"default:1"` // Higher number = higher priority
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
// AIInteraction represents an interaction with an AI model
|
||||
type AIInteraction struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
ConversationID uint `json:"conversationId"`
|
||||
MessageID uint `json:"messageId"`
|
||||
AIModelID uint `json:"aiModelId"`
|
||||
Prompt string `json:"prompt" gorm:"not null"`
|
||||
Response string `json:"response" gorm:"not null"`
|
||||
TokensUsed int `json:"tokensUsed"`
|
||||
ResponseTime int64 `json:"responseTime"` // in milliseconds
|
||||
Cost float64 `json:"cost"`
|
||||
Success bool `json:"success"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
// Relationships
|
||||
Conversation Conversation `json:"conversation" gorm:"foreignKey:ConversationID"`
|
||||
Message Message `json:"message" gorm:"foreignKey:MessageID"`
|
||||
AIModel AIModel `json:"aiModel" gorm:"foreignKey:AIModelID"`
|
||||
}
|
||||
|
||||
// AIFallback represents a fallback event when an AI model fails
|
||||
type AIFallback struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
ConversationID uint `json:"conversationId"`
|
||||
MessageID uint `json:"messageId"`
|
||||
FromAIModelID uint `json:"fromAiModelId"`
|
||||
ToAIModelID uint `json:"toAiModelId"`
|
||||
Reason string `json:"reason" gorm:"not null"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
// Relationships
|
||||
Conversation Conversation `json:"conversation" gorm:"foreignKey:ConversationID"`
|
||||
Message Message `json:"message" gorm:"foreignKey:MessageID"`
|
||||
FromAIModel AIModel `json:"fromAiModel" gorm:"foreignKey:FromAIModelID"`
|
||||
ToAIModel AIModel `json:"toAiModel" gorm:"foreignKey:ToAIModelID"`
|
||||
}
|
||||
|
||||
|
||||
// BeforeCreate is a GORM hook that validates the AI model before creation
|
||||
func (a *AIModel) BeforeCreate(tx *gorm.DB) error {
|
||||
if a.Priority < 1 {
|
||||
a.Priority = 1
|
||||
}
|
||||
if a.MaxTokens < 1 {
|
||||
a.MaxTokens = 1000
|
||||
}
|
||||
if a.Temperature < 0 {
|
||||
a.Temperature = 0
|
||||
} else if a.Temperature > 2 {
|
||||
a.Temperature = 2
|
||||
}
|
||||
if a.TopP < 0 {
|
||||
a.TopP = 0
|
||||
} else if a.TopP > 1 {
|
||||
a.TopP = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate is a GORM hook that validates the AI model before update
|
||||
func (a *AIModel) BeforeUpdate(tx *gorm.DB) error {
|
||||
if a.Priority < 1 {
|
||||
a.Priority = 1
|
||||
}
|
||||
if a.MaxTokens < 1 {
|
||||
a.MaxTokens = 1000
|
||||
}
|
||||
if a.Temperature < 0 {
|
||||
a.Temperature = 0
|
||||
} else if a.Temperature > 2 {
|
||||
a.Temperature = 2
|
||||
}
|
||||
if a.TopP < 0 {
|
||||
a.TopP = 0
|
||||
} else if a.TopP > 1 {
|
||||
a.TopP = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsActive checks if the AI model is active
|
||||
func (a *AIModel) IsActive() bool {
|
||||
return a.Active
|
||||
}
|
||||
|
||||
// IsOpenAI checks if the AI model is an OpenAI model
|
||||
func (a *AIModel) IsOpenAI() bool {
|
||||
return a.Type == "openai"
|
||||
}
|
||||
|
||||
// IsLocal checks if the AI model is a local model
|
||||
func (a *AIModel) IsLocal() bool {
|
||||
return a.Type == "local"
|
||||
}
|
||||
|
||||
// IsCustom checks if the AI model is a custom model
|
||||
func (a *AIModel) IsCustom() bool {
|
||||
return a.Type == "custom"
|
||||
}
|
63
backend/internal/models/conversation.go
Normal file
63
backend/internal/models/conversation.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Conversation represents a chat conversation between users and/or agents
|
||||
type Conversation struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Title string `json:"title"`
|
||||
UserID uint `json:"userId"`
|
||||
AgentID *uint `json:"agentId,omitempty"`
|
||||
Status string `json:"status" gorm:"default:'active'"` // 'active', 'closed', 'escalated'
|
||||
Department string `json:"department"`
|
||||
Priority string `json:"priority" gorm:"default:'medium'"` // 'low', 'medium', 'high', 'urgent'
|
||||
Tags string `json:"tags"` // Comma-separated tags
|
||||
LastMessageAt time.Time `json:"lastMessageAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
|
||||
// Relationships
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
Agent *User `json:"agent,omitempty" gorm:"foreignKey:AgentID"`
|
||||
Messages []Message `json:"messages" gorm:"foreignKey:ConversationID"`
|
||||
}
|
||||
|
||||
// Message represents a single message in a conversation
|
||||
type Message struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
ConversationID uint `json:"conversationId"`
|
||||
UserID uint `json:"userId"`
|
||||
Content string `json:"content" gorm:"not null"`
|
||||
Type string `json:"type" gorm:"default:'text'"` // 'text', 'image', 'file', 'system'
|
||||
Status string `json:"status" gorm:"default:'sent'"` // 'sent', 'delivered', 'read'
|
||||
Sentiment float64 `json:"sentiment"` // Sentiment score from -1 (negative) to 1 (positive)
|
||||
IsAI bool `json:"isAI" gorm:"default:false"`
|
||||
AIModel string `json:"aiModel,omitempty"` // Which AI model generated this message
|
||||
ReadAt *time.Time `json:"readAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
|
||||
// Relationships
|
||||
Conversation Conversation `json:"conversation" gorm:"foreignKey:ConversationID"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
|
||||
// BeforeCreate is a GORM hook that sets the LastMessageAt field when creating a conversation
|
||||
func (c *Conversation) BeforeCreate(tx *gorm.DB) error {
|
||||
c.LastMessageAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeCreate is a GORM hook that updates the conversation's LastMessageAt when creating a message
|
||||
func (m *Message) BeforeCreate(tx *gorm.DB) error {
|
||||
// Update the conversation's LastMessageAt
|
||||
tx.Model(&Conversation{}).Where("id = ?", m.ConversationID).Update("last_message_at", time.Now())
|
||||
return nil
|
||||
}
|
85
backend/internal/models/knowledge.go
Normal file
85
backend/internal/models/knowledge.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// KnowledgeBase represents a knowledge base entry (FAQ)
|
||||
type KnowledgeBase struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Question string `json:"question" gorm:"not null"`
|
||||
Answer string `json:"answer" gorm:"not null"`
|
||||
Category string `json:"category" gorm:"not null"`
|
||||
Tags string `json:"tags"` // Comma-separated tags
|
||||
Priority int `json:"priority" gorm:"default:1"` // Higher number = higher priority
|
||||
ViewCount int `json:"viewCount" gorm:"default:0"`
|
||||
Helpful int `json:"helpful" gorm:"default:0"` // Number of times marked as helpful
|
||||
NotHelpful int `json:"notHelpful" gorm:"default:0"` // Number of times marked as not helpful
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
CreatedBy uint `json:"createdBy"`
|
||||
UpdatedBy uint `json:"updatedBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
|
||||
// Relationships
|
||||
Creator User `json:"creator" gorm:"foreignKey:CreatedBy"`
|
||||
Updater User `json:"updater" gorm:"foreignKey:UpdatedBy"`
|
||||
}
|
||||
|
||||
// KnowledgeBaseFeedback represents user feedback on a knowledge base entry
|
||||
type KnowledgeBaseFeedback struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
KnowledgeBaseID uint `json:"knowledgeBaseId" gorm:"not null"`
|
||||
UserID uint `json:"userId" gorm:"not null"`
|
||||
Helpful bool `json:"helpful"` // true if helpful, false if not helpful
|
||||
Comment string `json:"comment"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
// Relationships
|
||||
KnowledgeBase KnowledgeBase `json:"knowledgeBase" gorm:"foreignKey:KnowledgeBaseID"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
|
||||
// BeforeCreate is a GORM hook that validates the knowledge base entry before creation
|
||||
func (k *KnowledgeBase) BeforeCreate(tx *gorm.DB) error {
|
||||
if k.Priority < 1 {
|
||||
k.Priority = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate is a GORM hook that validates the knowledge base entry before update
|
||||
func (k *KnowledgeBase) BeforeUpdate(tx *gorm.DB) error {
|
||||
if k.Priority < 1 {
|
||||
k.Priority = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IncrementViewCount increments the view count of a knowledge base entry
|
||||
func (k *KnowledgeBase) IncrementViewCount(tx *gorm.DB) error {
|
||||
return tx.Model(k).UpdateColumn("view_count", gorm.Expr("view_count + ?", 1)).Error
|
||||
}
|
||||
|
||||
// MarkHelpful marks a knowledge base entry as helpful
|
||||
func (k *KnowledgeBase) MarkHelpful(tx *gorm.DB) error {
|
||||
return tx.Model(k).UpdateColumn("helpful", gorm.Expr("helpful + ?", 1)).Error
|
||||
}
|
||||
|
||||
// MarkNotHelpful marks a knowledge base entry as not helpful
|
||||
func (k *KnowledgeBase) MarkNotHelpful(tx *gorm.DB) error {
|
||||
return tx.Model(k).UpdateColumn("not_helpful", gorm.Expr("not_helpful + ?", 1)).Error
|
||||
}
|
||||
|
||||
// GetHelpfulnessPercentage returns the helpfulness percentage
|
||||
func (k *KnowledgeBase) GetHelpfulnessPercentage() float64 {
|
||||
total := k.Helpful + k.NotHelpful
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(k.Helpful) / float64(total) * 100
|
||||
}
|
143
backend/internal/models/models_test.go
Normal file
143
backend/internal/models/models_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserModel(t *testing.T) {
|
||||
// Test user model fields
|
||||
user := User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
Password: "hashedpassword",
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
Role: "user",
|
||||
Active: true,
|
||||
}
|
||||
|
||||
// Verify user fields
|
||||
assert.Equal(t, uint(1), user.ID)
|
||||
assert.Equal(t, "testuser", user.Username)
|
||||
assert.Equal(t, "test@example.com", user.Email)
|
||||
assert.Equal(t, "hashedpassword", user.Password)
|
||||
assert.Equal(t, "Test", user.FirstName)
|
||||
assert.Equal(t, "User", user.LastName)
|
||||
assert.Equal(t, "user", user.Role)
|
||||
assert.True(t, user.Active)
|
||||
}
|
||||
|
||||
func TestConversationModel(t *testing.T) {
|
||||
// Test conversation model fields
|
||||
conversation := Conversation{
|
||||
ID: 1,
|
||||
UserID: 1,
|
||||
Title: "Test Conversation",
|
||||
Status: "active",
|
||||
Department: "support",
|
||||
Priority: "medium",
|
||||
Tags: "test,sample",
|
||||
}
|
||||
|
||||
// Verify conversation fields
|
||||
assert.Equal(t, uint(1), conversation.ID)
|
||||
assert.Equal(t, uint(1), conversation.UserID)
|
||||
assert.Equal(t, "Test Conversation", conversation.Title)
|
||||
assert.Equal(t, "active", conversation.Status)
|
||||
assert.Equal(t, "support", conversation.Department)
|
||||
assert.Equal(t, "medium", conversation.Priority)
|
||||
assert.Equal(t, "test,sample", conversation.Tags)
|
||||
}
|
||||
|
||||
func TestMessageModel(t *testing.T) {
|
||||
// Test message model fields
|
||||
message := Message{
|
||||
ID: 1,
|
||||
ConversationID: 1,
|
||||
UserID: 1,
|
||||
Content: "Test message content",
|
||||
Type: "text",
|
||||
Status: "sent",
|
||||
Sentiment: 0.5,
|
||||
IsAI: false,
|
||||
AIModel: "",
|
||||
}
|
||||
|
||||
// Verify message fields
|
||||
assert.Equal(t, uint(1), message.ID)
|
||||
assert.Equal(t, uint(1), message.ConversationID)
|
||||
assert.Equal(t, uint(1), message.UserID)
|
||||
assert.Equal(t, "Test message content", message.Content)
|
||||
assert.Equal(t, "text", message.Type)
|
||||
assert.Equal(t, "sent", message.Status)
|
||||
assert.Equal(t, 0.5, message.Sentiment)
|
||||
assert.False(t, message.IsAI)
|
||||
assert.Equal(t, "", message.AIModel)
|
||||
}
|
||||
|
||||
func TestKnowledgeModel(t *testing.T) {
|
||||
// Test knowledge model fields
|
||||
knowledge := KnowledgeBase{
|
||||
ID: 1,
|
||||
Question: "Test Question",
|
||||
Answer: "Test knowledge content",
|
||||
Category: "general",
|
||||
Tags: "test,sample",
|
||||
Priority: 1,
|
||||
ViewCount: 10,
|
||||
Helpful: 5,
|
||||
NotHelpful: 2,
|
||||
Active: true,
|
||||
CreatedBy: 1,
|
||||
UpdatedBy: 1,
|
||||
}
|
||||
|
||||
// Verify knowledge fields
|
||||
assert.Equal(t, uint(1), knowledge.ID)
|
||||
assert.Equal(t, "Test Question", knowledge.Question)
|
||||
assert.Equal(t, "Test knowledge content", knowledge.Answer)
|
||||
assert.Equal(t, "general", knowledge.Category)
|
||||
assert.Equal(t, "test,sample", knowledge.Tags)
|
||||
assert.Equal(t, 1, knowledge.Priority)
|
||||
assert.Equal(t, 10, knowledge.ViewCount)
|
||||
assert.Equal(t, 5, knowledge.Helpful)
|
||||
assert.Equal(t, 2, knowledge.NotHelpful)
|
||||
assert.True(t, knowledge.Active)
|
||||
assert.Equal(t, uint(1), knowledge.CreatedBy)
|
||||
assert.Equal(t, uint(1), knowledge.UpdatedBy)
|
||||
}
|
||||
|
||||
func TestAIModel(t *testing.T) {
|
||||
// Test AI model fields
|
||||
aiModel := AIModel{
|
||||
ID: 1,
|
||||
Name: "Test AI Model",
|
||||
Type: "openai",
|
||||
Endpoint: "https://api.openai.com/v1/chat/completions",
|
||||
APIKey: "test-api-key",
|
||||
Model: "gpt-4",
|
||||
MaxTokens: 4000,
|
||||
Temperature: 0.7,
|
||||
TopP: 1.0,
|
||||
Active: true,
|
||||
Priority: 1,
|
||||
Description: "Test AI model description",
|
||||
}
|
||||
|
||||
// Verify AI model fields
|
||||
assert.Equal(t, uint(1), aiModel.ID)
|
||||
assert.Equal(t, "Test AI Model", aiModel.Name)
|
||||
assert.Equal(t, "openai", aiModel.Type)
|
||||
assert.Equal(t, "https://api.openai.com/v1/chat/completions", aiModel.Endpoint)
|
||||
assert.Equal(t, "test-api-key", aiModel.APIKey)
|
||||
assert.Equal(t, "gpt-4", aiModel.Model)
|
||||
assert.Equal(t, 4000, aiModel.MaxTokens)
|
||||
assert.Equal(t, 0.7, aiModel.Temperature)
|
||||
assert.Equal(t, 1.0, aiModel.TopP)
|
||||
assert.True(t, aiModel.Active)
|
||||
assert.Equal(t, 1, aiModel.Priority)
|
||||
assert.Equal(t, "Test AI model description", aiModel.Description)
|
||||
}
|
261
backend/internal/models/requests.go
Normal file
261
backend/internal/models/requests.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// User Request/Response types
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
User SafeUser `json:"user"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Active *bool `json:"active"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
CurrentPassword string `json:"currentPassword" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
// Conversation Request/Response types
|
||||
type CreateConversationRequest struct {
|
||||
Title string `json:"title" binding:"required,min=1,max=100"`
|
||||
Department string `json:"department" binding:"required"`
|
||||
Priority string `json:"priority" binding:"oneof=low medium high urgent"`
|
||||
Tags string `json:"tags"`
|
||||
}
|
||||
|
||||
type UpdateConversationRequest struct {
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status" binding:"oneof=active closed escalated"`
|
||||
Department string `json:"department"`
|
||||
Priority string `json:"priority" binding:"oneof=low medium high urgent"`
|
||||
Tags string `json:"tags"`
|
||||
AgentID *uint `json:"agentId"`
|
||||
}
|
||||
|
||||
type CreateMessageRequest struct {
|
||||
ConversationID uint `json:"conversationId" binding:"required"`
|
||||
Content string `json:"content" binding:"required,min=1,max=5000"`
|
||||
Type string `json:"type" binding:"oneof=text image file system"`
|
||||
}
|
||||
|
||||
type UpdateMessageRequest struct {
|
||||
Content string `json:"content" binding:"omitempty,min=1,max=5000"`
|
||||
Status string `json:"status" binding:"oneof=sent delivered read"`
|
||||
}
|
||||
|
||||
type ConversationListResponse struct {
|
||||
Conversations []Conversation `json:"conversations"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type MessageListResponse struct {
|
||||
Messages []Message `json:"messages"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type ConversationStats struct {
|
||||
TotalMessages int64 `json:"totalMessages"`
|
||||
AverageSentiment float64 `json:"averageSentiment"`
|
||||
ResponseTime int64 `json:"responseTime"` // in seconds
|
||||
FirstMessageAt time.Time `json:"firstMessageAt"`
|
||||
LastMessageAt time.Time `json:"lastMessageAt"`
|
||||
}
|
||||
|
||||
// Knowledge Base Request/Response types
|
||||
type CreateKnowledgeBaseRequest struct {
|
||||
Question string `json:"question" binding:"required,min=1,max=500"`
|
||||
Answer string `json:"answer" binding:"required,min=1,max=5000"`
|
||||
Category string `json:"category" binding:"required,min=1,max=100"`
|
||||
Tags string `json:"tags"`
|
||||
Priority int `json:"priority" binding:"min=1,max=10"`
|
||||
}
|
||||
|
||||
type UpdateKnowledgeBaseRequest struct {
|
||||
Question string `json:"question" binding:"omitempty,min=1,max=500"`
|
||||
Answer string `json:"answer" binding:"omitempty,min=1,max=5000"`
|
||||
Category string `json:"category" binding:"omitempty,min=1,max=100"`
|
||||
Tags string `json:"tags"`
|
||||
Priority int `json:"priority" binding:"omitempty,min=1,max=10"`
|
||||
Active *bool `json:"active"`
|
||||
}
|
||||
|
||||
type CreateKnowledgeBaseFeedbackRequest struct {
|
||||
KnowledgeBaseID uint `json:"knowledgeBaseId" binding:"required"`
|
||||
Helpful bool `json:"helpful"`
|
||||
Comment string `json:"comment" binding:"max=500"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseSearchRequest struct {
|
||||
Query string `json:"query" binding:"required,min=1,max=100"`
|
||||
Category string `json:"category"`
|
||||
Tags string `json:"tags"`
|
||||
Page int `json:"page" binding:"min=1"`
|
||||
PageSize int `json:"pageSize" binding:"min=1,max=100"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseListResponse struct {
|
||||
Entries []KnowledgeBase `json:"entries"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseSearchResponse struct {
|
||||
Results []KnowledgeBaseSearchResult `json:"results"`
|
||||
Total int64 `json:"total"`
|
||||
Query string `json:"query"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseSearchResult struct {
|
||||
KnowledgeBase
|
||||
RelevanceScore float64 `json:"relevanceScore"`
|
||||
MatchedFields []string `json:"matchedFields"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseStats struct {
|
||||
TotalEntries int64 `json:"totalEntries"`
|
||||
TotalViews int64 `json:"totalViews"`
|
||||
AverageHelpful float64 `json:"averageHelpful"` // Average helpful rating
|
||||
TopCategories []CategoryStat `json:"topCategories"`
|
||||
TopTags []TagStat `json:"topTags"`
|
||||
}
|
||||
|
||||
type CategoryStat struct {
|
||||
Category string `json:"category"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
type TagStat struct {
|
||||
Tag string `json:"tag"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// AI Model Request/Response types
|
||||
type CreateAIModelRequest struct {
|
||||
Name string `json:"name" binding:"required,min=1,max=100"`
|
||||
Type string `json:"type" binding:"required,oneof=openai local custom"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Model string `json:"model" binding:"required,min=1,max=100"`
|
||||
MaxTokens int `json:"maxTokens" binding:"min=1,max=100000"`
|
||||
Temperature float64 `json:"temperature" binding:"min=0,max=2"`
|
||||
TopP float64 `json:"topP" binding:"min=0,max=1"`
|
||||
Priority int `json:"priority" binding:"min=1,max=10"`
|
||||
Description string `json:"description" binding:"max=500"`
|
||||
}
|
||||
|
||||
type UpdateAIModelRequest struct {
|
||||
Name string `json:"name" binding:"omitempty,min=1,max=100"`
|
||||
Type string `json:"type" binding:"omitempty,oneof=openai local custom"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Model string `json:"model" binding:"omitempty,min=1,max=100"`
|
||||
MaxTokens int `json:"maxTokens" binding:"omitempty,min=1,max=100000"`
|
||||
Temperature float64 `json:"temperature" binding:"omitempty,min=0,max=2"`
|
||||
TopP float64 `json:"topP" binding:"omitempty,min=0,max=1"`
|
||||
Active *bool `json:"active"`
|
||||
Priority int `json:"priority" binding:"omitempty,min=1,max=10"`
|
||||
Description string `json:"description" binding:"omitempty,max=500"`
|
||||
}
|
||||
|
||||
type AIModelListResponse struct {
|
||||
Models []AIModel `json:"models"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type AIInteractionListResponse struct {
|
||||
Interactions []AIInteraction `json:"interactions"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type AIFallbackListResponse struct {
|
||||
Fallbacks []AIFallback `json:"fallbacks"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type AIStats struct {
|
||||
TotalInteractions int64 `json:"totalInteractions"`
|
||||
TotalFallbacks int64 `json:"totalFallbacks"`
|
||||
AverageResponseTime float64 `json:"averageResponseTime"` // in milliseconds
|
||||
TotalTokensUsed int64 `json:"totalTokensUsed"`
|
||||
TotalCost float64 `json:"totalCost"`
|
||||
ModelStats []AIModelStats `json:"modelStats"`
|
||||
SuccessRate float64 `json:"successRate"`
|
||||
}
|
||||
|
||||
type AIModelStats struct {
|
||||
AIModel AIModel `json:"aiModel"`
|
||||
InteractionsCount int64 `json:"interactionsCount"`
|
||||
FallbacksCount int64 `json:"fallbacksCount"`
|
||||
AverageResponseTime float64 `json:"averageResponseTime"`
|
||||
SuccessRate float64 `json:"successRate"`
|
||||
TokensUsed int64 `json:"tokensUsed"`
|
||||
Cost float64 `json:"cost"`
|
||||
}
|
||||
|
||||
// Common Response types
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type SuccessResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// Response helpers
|
||||
func NewErrorResponse(c *gin.Context, message string, statusCode int) {
|
||||
c.JSON(statusCode, ErrorResponse{
|
||||
Error: http.StatusText(statusCode),
|
||||
Message: message,
|
||||
Status: statusCode,
|
||||
})
|
||||
}
|
||||
|
||||
func NewSuccessResponse(c *gin.Context, message string, data interface{}, statusCode int) {
|
||||
c.JSON(statusCode, SuccessResponse{
|
||||
Message: message,
|
||||
Data: data,
|
||||
Status: statusCode,
|
||||
})
|
||||
}
|
69
backend/internal/models/user.go
Normal file
69
backend/internal/models/user.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// User represents a user in the system
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Username string `json:"username" gorm:"uniqueIndex;not null"`
|
||||
Email string `json:"email" gorm:"uniqueIndex;not null"`
|
||||
Password string `json:"-" gorm:"not null"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Role string `json:"role" gorm:"default:'user'"` // 'user', 'agent', 'admin'
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
}
|
||||
|
||||
// BeforeSave is a GORM hook that hashes the password before saving
|
||||
func (u *User) BeforeSave(tx *gorm.DB) error {
|
||||
if u.Password != "" {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = string(hashedPassword)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComparePassword compares a plaintext password with the user's hashed password
|
||||
func (u *User) ComparePassword(password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ToSafeUser returns a user object without sensitive information
|
||||
func (u *User) ToSafeUser() SafeUser {
|
||||
return SafeUser{
|
||||
ID: u.ID,
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Role: u.Role,
|
||||
Active: u.Active,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// SafeUser represents user information that can be safely exposed to clients
|
||||
type SafeUser struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Role string `json:"role"`
|
||||
Active bool `json:"active"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
Reference in New Issue
Block a user