package repository import ( "context" "fmt" "strings" "github.com/google/uuid" "github.com/topfans/backend/pkg/logger" "github.com/topfans/backend/services/aiChatService/model" "go.uber.org/zap" "gorm.io/gorm" ) // PersonaRepository 人设仓库接口 type PersonaRepository interface { Create(ctx context.Context, persona *model.Persona) error GetByID(ctx context.Context, id uuid.UUID) (*model.Persona, error) GetByUserID(ctx context.Context, userID int64) ([]model.Persona, error) GetDefaultByUserIDAndStarID(ctx context.Context, userID int64, starID int64) (*model.Persona, error) Update(ctx context.Context, persona *model.Persona) error Delete(ctx context.Context, id uuid.UUID) error EnsureDefaultPersona(ctx context.Context, userID int64, starID int64) (*model.Persona, error) } // StarRepository 明星信息仓库接口 type StarRepository interface { GetByID(ctx context.Context, starID int64) (*model.Star, error) } // DefaultSystemPrompt 兜底默认系统提示词(明星查不到时用) const FallbackSystemPrompt = `你是一个温柔体贴的AI伴侣,名字叫角角。你善于倾听,能理解用户的情绪, 用温暖的话语陪伴用户。说话风格亲切自然,像朋友聊天一样。 不要过于正式或说教,当用户情绪低落时,先给予共情和安慰。` // DefaultPersonaName 默认人设名称(兜底) const DefaultPersonaName = "角角" // DefaultPersonaDescription 默认人设描述(兜底) const DefaultPersonaDescription = "温柔陪伴型闺蜜" // PostgreSQLPersonaRepository PostgreSQL 人设仓库实现 type PostgreSQLPersonaRepository struct { db *gorm.DB starRepo StarRepository } // NewPostgreSQLPersonaRepository 创建人设仓库 func NewPostgreSQLPersonaRepository(db *gorm.DB, starRepo StarRepository) *PostgreSQLPersonaRepository { return &PostgreSQLPersonaRepository{db: db, starRepo: starRepo} } // PostgreSQLStarRepository PostgreSQL 明星仓库实现 type PostgreSQLStarRepository struct { db *gorm.DB } // NewPostgreSQLStarRepository 创建明星仓库 func NewPostgreSQLStarRepository(db *gorm.DB) *PostgreSQLStarRepository { return &PostgreSQLStarRepository{db: db} } // GetByID 根据 star_id 获取明星信息 func (r *PostgreSQLStarRepository) GetByID(ctx context.Context, starID int64) (*model.Star, error) { var star model.Star if err := r.db.WithContext(ctx).Where("star_id = ? AND is_active = true", starID).First(&star).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("star not found: %d", starID) } return nil, fmt.Errorf("failed to get star: %w", err) } return &star, nil } // Create 创建人设 func (r *PostgreSQLPersonaRepository) Create(ctx context.Context, persona *model.Persona) error { return r.db.WithContext(ctx).Create(persona).Error } // GetByID 根据 ID 获取人设 func (r *PostgreSQLPersonaRepository) GetByID(ctx context.Context, id uuid.UUID) (*model.Persona, error) { var persona model.Persona if err := r.db.WithContext(ctx).Where("id = ?", id).First(&persona).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, model.ErrPersonaNotFound } return nil, fmt.Errorf("failed to get persona: %w", err) } return &persona, nil } // GetByUserID 获取用户的所有人设 func (r *PostgreSQLPersonaRepository) GetByUserID(ctx context.Context, userID int64) ([]model.Persona, error) { var personas []model.Persona if err := r.db.WithContext(ctx). Where("user_id = ?", userID). Order("created_at DESC"). Find(&personas).Error; err != nil { return nil, fmt.Errorf("failed to get personas: %w", err) } return personas, nil } // GetDefaultByUserIDAndStarID 获取用户在指定明星下的默认人设 func (r *PostgreSQLPersonaRepository) GetDefaultByUserIDAndStarID(ctx context.Context, userID int64, starID int64) (*model.Persona, error) { var persona model.Persona if err := r.db.WithContext(ctx). Where("user_id = ? AND star_id = ? AND is_default = TRUE", userID, starID). First(&persona).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, model.ErrPersonaNotFound } return nil, fmt.Errorf("failed to get default persona: %w", err) } return &persona, nil } // Update 更新人设 func (r *PostgreSQLPersonaRepository) Update(ctx context.Context, persona *model.Persona) error { return r.db.WithContext(ctx).Save(persona).Error } // Delete 删除人设 func (r *PostgreSQLPersonaRepository) Delete(ctx context.Context, id uuid.UUID) error { return r.db.WithContext(ctx).Delete(&model.Persona{}, "id = ?", id).Error } // EnsureDefaultPersona 确保用户在指定明星下有默认人设(每个 (user, star) 独立) func (r *PostgreSQLPersonaRepository) EnsureDefaultPersona(ctx context.Context, userID int64, starID int64) (*model.Persona, error) { // 检查该用户在该明星下是否已有默认人设 persona, err := r.GetDefaultByUserIDAndStarID(ctx, userID, starID) if err == nil { // 已有,直接返回(不自动更新,各明星人设独立) return persona, nil } if err != model.ErrPersonaNotFound { return nil, err } // 不存在 → 创建:name 固定角角,prompt 基于明星信息 name := DefaultPersonaName description := DefaultPersonaDescription systemPrompt := FallbackSystemPrompt if starID > 0 && r.starRepo != nil { star, err := r.starRepo.GetByID(ctx, starID) if err != nil { logger.Logger.Warn("Failed to get star info, using fallback persona", zap.Int64("star_id", starID), zap.Error(err), ) } else { description = buildStarDescription(star) systemPrompt = buildStarSystemPrompt(star) logger.Logger.Info("Created star-based default persona", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("star_name", star.Name), ) } } persona = &model.Persona{ UserID: userID, StarID: starID, Name: name, Description: description, SystemPrompt: systemPrompt, IsDefault: true, } if err := r.Create(ctx, persona); err != nil { // 并发场景:另一个 goroutine 已创建了相同的默认人设,重新查询返回 if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "unique") { logger.Logger.Info("Default persona already created by concurrent request, re-fetching", zap.Int64("user_id", userID), zap.Int64("star_id", starID), ) return r.GetDefaultByUserIDAndStarID(ctx, userID, starID) } return nil, fmt.Errorf("failed to create default persona: %w", err) } return persona, nil } // buildStarDescription 根据明星信息生成人设描述 func buildStarDescription(star *model.Star) string { if star.Tag != "" { return fmt.Sprintf("%s的AI搭子(%s)", star.Name, star.Tag) } return fmt.Sprintf("%s的AI搭子", star.Name) } // buildStarSystemPrompt 根据明星信息生成 system prompt func buildStarSystemPrompt(star *model.Star) string { var sb strings.Builder sb.WriteString(fmt.Sprintf("你的名字叫角角,你的角色设定就是%s本人。\n", star.Name)) sb.WriteString("你是粉丝的AI搭子,用明星本人的语气和粉丝聊天。\n") if star.Tag != "" { sb.WriteString(fmt.Sprintf("粉丝们喜欢叫你\"%s\",你可以这样称呼他们。\n", star.Tag)) } if star.Description != "" { sb.WriteString(fmt.Sprintf("关于%s的背景:%s\n", star.Name, star.Description)) } sb.WriteString(` 说话风格要自然亲切,就像明星本人和粉丝聊天一样。 可以适度使用口头禅、表示关心的问候。当粉丝情绪低落时,给予鼓励和安慰。 把粉丝当成最重要的人来对待,但不要过于夸张。 回应要简洁自然,不要长篇大论,控制在2-3句话以内。`) return sb.String() }