316
server/service/app/ai_config.go
Normal file
316
server/service/app/ai_config.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"git.echol.cn/loser/st/server/model/app/request"
|
||||
"git.echol.cn/loser/st/server/model/app/response"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AIConfigService struct{}
|
||||
|
||||
// CreateAIConfig 创建AI配置
|
||||
func (s *AIConfigService) CreateAIConfig(req *request.CreateAIConfigRequest) (*response.AIConfigResponse, error) {
|
||||
// 序列化 JSON 字段
|
||||
settingsJSON, _ := json.Marshal(req.Settings)
|
||||
|
||||
config := app.AIConfig{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
BaseURL: req.BaseURL,
|
||||
APIKey: req.APIKey,
|
||||
DefaultModel: req.DefaultModel,
|
||||
Settings: datatypes.JSON(settingsJSON),
|
||||
Models: datatypes.JSON("[]"),
|
||||
IsActive: true,
|
||||
IsDefault: false,
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Create(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToAIConfigResponse(&config)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetAIConfigList 获取AI配置列表
|
||||
func (s *AIConfigService) GetAIConfigList() (*response.AIConfigListResponse, error) {
|
||||
var configs []app.AIConfig
|
||||
var total int64
|
||||
|
||||
db := global.GVA_DB.Model(&app.AIConfig{})
|
||||
|
||||
err := db.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Order("is_default DESC, created_at DESC").Find(&configs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]response.AIConfigResponse, len(configs))
|
||||
for i, config := range configs {
|
||||
list[i] = response.ToAIConfigResponse(&config)
|
||||
}
|
||||
|
||||
return &response.AIConfigListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateAIConfig 更新AI配置
|
||||
func (s *AIConfigService) UpdateAIConfig(id uint, req *request.UpdateAIConfigRequest) error {
|
||||
var config app.AIConfig
|
||||
|
||||
err := global.GVA_DB.First(&config, id).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("配置不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.BaseURL != "" {
|
||||
updates["base_url"] = req.BaseURL
|
||||
}
|
||||
// 只有当 API Key 不是脱敏格式时才更新
|
||||
// 脱敏格式: xxxx****xxxx
|
||||
if req.APIKey != "" && !isMaskedAPIKey(req.APIKey) {
|
||||
updates["api_key"] = req.APIKey
|
||||
}
|
||||
if req.DefaultModel != "" {
|
||||
updates["default_model"] = req.DefaultModel
|
||||
}
|
||||
if req.Settings != nil {
|
||||
settingsJSON, _ := json.Marshal(req.Settings)
|
||||
updates["settings"] = datatypes.JSON(settingsJSON)
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
updates["is_active"] = *req.IsActive
|
||||
}
|
||||
if req.IsDefault != nil && *req.IsDefault {
|
||||
// 如果设置为默认,先取消其他配置的默认状态
|
||||
global.GVA_DB.Model(&app.AIConfig{}).Where("id != ?", id).Update("is_default", false)
|
||||
updates["is_default"] = true
|
||||
}
|
||||
|
||||
return global.GVA_DB.Model(&config).Updates(updates).Error
|
||||
}
|
||||
|
||||
// DeleteAIConfig 删除AI配置
|
||||
func (s *AIConfigService) DeleteAIConfig(id uint) error {
|
||||
result := global.GVA_DB.Delete(&app.AIConfig{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("配置不存在")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModels 获取可用模型列表
|
||||
func (s *AIConfigService) GetModels(req *request.GetModelsRequest) (*response.GetModelsResponse, error) {
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
|
||||
// 构建请求
|
||||
modelsURL := req.BaseURL + "/models"
|
||||
httpReq, err := http.NewRequest("GET", modelsURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置认证头
|
||||
if req.Provider == "openai" || req.Provider == "custom" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
|
||||
} else if req.Provider == "anthropic" {
|
||||
httpReq.Header.Set("x-api-key", req.APIKey)
|
||||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API返回错误 %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var result struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
OwnedBy string `json:"owned_by"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 转换为模型信息
|
||||
models := make([]response.ModelInfo, 0, len(result.Data))
|
||||
for _, model := range result.Data {
|
||||
models = append(models, response.ModelInfo{
|
||||
ID: model.ID,
|
||||
Name: model.ID,
|
||||
OwnedBy: model.OwnedBy,
|
||||
})
|
||||
}
|
||||
|
||||
return &response.GetModelsResponse{Models: models}, nil
|
||||
}
|
||||
|
||||
// GetModelsByID 通过配置ID获取模型列表(使用数据库中的完整API Key)
|
||||
func (s *AIConfigService) GetModelsByID(id uint) (*response.GetModelsResponse, error) {
|
||||
var config app.AIConfig
|
||||
err := global.GVA_DB.First(&config, id).Error
|
||||
if err != nil {
|
||||
return nil, errors.New("配置不存在")
|
||||
}
|
||||
|
||||
// 使用数据库中的完整 API Key
|
||||
req := &request.GetModelsRequest{
|
||||
Provider: config.Provider,
|
||||
BaseURL: config.BaseURL,
|
||||
APIKey: config.APIKey,
|
||||
}
|
||||
|
||||
return s.GetModels(req)
|
||||
}
|
||||
|
||||
// TestAIConfig 测试AI配置
|
||||
func (s *AIConfigService) TestAIConfig(req *request.TestAIConfigRequest) (*response.TestAIConfigResponse, error) {
|
||||
startTime := time.Now()
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
// 构建测试请求
|
||||
var requestBody map[string]interface{}
|
||||
var endpoint string
|
||||
|
||||
if req.Provider == "openai" || req.Provider == "custom" {
|
||||
endpoint = req.BaseURL + "/chat/completions"
|
||||
model := req.Model
|
||||
if model == "" {
|
||||
model = "gpt-3.5-turbo"
|
||||
}
|
||||
requestBody = map[string]interface{}{
|
||||
"model": model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hello"},
|
||||
},
|
||||
"max_tokens": 10,
|
||||
}
|
||||
} else if req.Provider == "anthropic" {
|
||||
endpoint = req.BaseURL + "/messages"
|
||||
model := req.Model
|
||||
if model == "" {
|
||||
model = "claude-3-haiku-20240307"
|
||||
}
|
||||
requestBody = map[string]interface{}{
|
||||
"model": model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hello"},
|
||||
},
|
||||
"max_tokens": 10,
|
||||
}
|
||||
}
|
||||
|
||||
bodyBytes, _ := json.Marshal(requestBody)
|
||||
httpReq, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return &response.TestAIConfigResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("创建请求失败: %v", err),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if req.Provider == "openai" || req.Provider == "custom" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
|
||||
} else if req.Provider == "anthropic" {
|
||||
httpReq.Header.Set("x-api-key", req.APIKey)
|
||||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := client.Do(httpReq)
|
||||
latency := time.Since(startTime).Milliseconds()
|
||||
|
||||
if err != nil {
|
||||
return &response.TestAIConfigResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("连接失败: %v", err),
|
||||
Latency: latency,
|
||||
}, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return &response.TestAIConfigResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("API返回错误 %d: %s", resp.StatusCode, string(body)),
|
||||
Latency: latency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &response.TestAIConfigResponse{
|
||||
Success: true,
|
||||
Message: "连接成功,AI响应正常",
|
||||
Latency: latency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TestAIConfigByID 通过ID测试AI配置(使用数据库中的完整API Key)
|
||||
func (s *AIConfigService) TestAIConfigByID(id uint) (*response.TestAIConfigResponse, error) {
|
||||
var config app.AIConfig
|
||||
err := global.GVA_DB.First(&config, id).Error
|
||||
if err != nil {
|
||||
return nil, errors.New("配置不存在")
|
||||
}
|
||||
|
||||
// 使用数据库中的完整 API Key 进行测试
|
||||
req := &request.TestAIConfigRequest{
|
||||
Provider: config.Provider,
|
||||
BaseURL: config.BaseURL,
|
||||
APIKey: config.APIKey, // 使用完整的 API Key,而不是脱敏后的
|
||||
Model: config.DefaultModel,
|
||||
}
|
||||
|
||||
return s.TestAIConfig(req)
|
||||
}
|
||||
|
||||
// isMaskedAPIKey 检查是否是脱敏的 API Key
|
||||
func isMaskedAPIKey(apiKey string) bool {
|
||||
// 脱敏格式: xxxx****xxxx 或 ****
|
||||
return len(apiKey) > 0 && (apiKey == "****" || (len(apiKey) > 8 && apiKey[4:len(apiKey)-4] == "****"))
|
||||
}
|
||||
254
server/service/app/auth.go
Normal file
254
server/service/app/auth.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"git.echol.cn/loser/st/server/model/app/request"
|
||||
"git.echol.cn/loser/st/server/model/app/response"
|
||||
"git.echol.cn/loser/st/server/utils"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AuthService struct{}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *AuthService) Register(req *request.RegisterRequest) error {
|
||||
// 检查用户名是否已存在
|
||||
var count int64
|
||||
err := global.GVA_DB.Model(&app.AppUser{}).Where("username = ?", req.Username).Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("用户名已存在")
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if req.Email != "" {
|
||||
err = global.GVA_DB.Model(&app.AppUser{}).Where("email = ?", req.Email).Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("邮箱已被使用")
|
||||
}
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return errors.New("密码加密失败")
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := app.AppUser{
|
||||
UUID: uuid.New().String(),
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
NickName: req.NickName,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Status: "active",
|
||||
Enable: true,
|
||||
}
|
||||
|
||||
if user.NickName == "" {
|
||||
user.NickName = req.Username
|
||||
}
|
||||
|
||||
return global.GVA_DB.Create(&user).Error
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *AuthService) Login(req *request.LoginRequest, ip string) (*response.LoginResponse, error) {
|
||||
// 查询用户
|
||||
var user app.AppUser
|
||||
err := global.GVA_DB.Where("username = ?", req.Username).First(&user).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if !user.Enable {
|
||||
return nil, errors.New("账户已被禁用")
|
||||
}
|
||||
if user.Status != "active" {
|
||||
return nil, errors.New("账户状态异常")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
|
||||
if err != nil {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 生成 Token
|
||||
token, expiresAt, err := utils.CreateAppToken(user.ID, user.Username)
|
||||
if err != nil {
|
||||
return nil, errors.New("Token 生成失败")
|
||||
}
|
||||
|
||||
// 生成刷新 Token
|
||||
refreshToken, refreshExpiresAt, err := utils.CreateAppRefreshToken(user.ID, user.Username)
|
||||
if err != nil {
|
||||
return nil, errors.New("刷新 Token 生成失败")
|
||||
}
|
||||
|
||||
// 更新最后登录信息
|
||||
now := time.Now()
|
||||
global.GVA_DB.Model(&user).Updates(map[string]interface{}{
|
||||
"last_login_at": now,
|
||||
"last_login_ip": ip,
|
||||
})
|
||||
|
||||
// 保存会话信息(可选)
|
||||
session := app.AppUserSession{
|
||||
UserID: user.ID,
|
||||
SessionToken: token,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: time.Unix(expiresAt, 0),
|
||||
RefreshExpiresAt: func() *time.Time { t := time.Unix(refreshExpiresAt, 0); return &t }(),
|
||||
IPAddress: ip,
|
||||
}
|
||||
global.GVA_DB.Create(&session)
|
||||
|
||||
return &response.LoginResponse{
|
||||
User: response.ToAppUserResponse(&user),
|
||||
Token: token,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RefreshToken 刷新 Token
|
||||
func (s *AuthService) RefreshToken(req *request.RefreshTokenRequest) (*response.LoginResponse, error) {
|
||||
// 解析刷新 Token
|
||||
claims, err := utils.ParseAppToken(req.RefreshToken)
|
||||
if err != nil {
|
||||
return nil, errors.New("刷新 Token 无效")
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
var user app.AppUser
|
||||
err = global.GVA_DB.Where("id = ?", claims.UserID).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, errors.New("用户不存在")
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if !user.Enable {
|
||||
return nil, errors.New("账户已被禁用")
|
||||
}
|
||||
|
||||
// 生成新的 Token
|
||||
token, expiresAt, err := utils.CreateAppToken(user.ID, user.Username)
|
||||
if err != nil {
|
||||
return nil, errors.New("Token 生成失败")
|
||||
}
|
||||
|
||||
// 生成新的刷新 Token
|
||||
refreshToken, _, err := utils.CreateAppRefreshToken(user.ID, user.Username)
|
||||
if err != nil {
|
||||
return nil, errors.New("刷新 Token 生成失败")
|
||||
}
|
||||
|
||||
return &response.LoginResponse{
|
||||
User: response.ToAppUserResponse(&user),
|
||||
Token: token,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Logout 用户登出
|
||||
func (s *AuthService) Logout(userID uint, token string) error {
|
||||
// 删除会话记录
|
||||
return global.GVA_DB.Where("user_id = ? AND session_token = ?", userID, token).
|
||||
Delete(&app.AppUserSession{}).Error
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (s *AuthService) GetUserInfo(userID uint) (*response.AppUserResponse, error) {
|
||||
var user app.AppUser
|
||||
err := global.GVA_DB.Where("id = ?", userID).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToAppUserResponse(&user)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// UpdateProfile 更新用户信息
|
||||
func (s *AuthService) UpdateProfile(userID uint, req *request.UpdateProfileRequest) error {
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
if req.NickName != "" {
|
||||
updates["nick_name"] = req.NickName
|
||||
}
|
||||
if req.Email != "" {
|
||||
// 检查邮箱是否已被其他用户使用
|
||||
var count int64
|
||||
err := global.GVA_DB.Model(&app.AppUser{}).
|
||||
Where("email = ? AND id != ?", req.Email, userID).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("邮箱已被使用")
|
||||
}
|
||||
updates["email"] = req.Email
|
||||
}
|
||||
if req.Phone != "" {
|
||||
updates["phone"] = req.Phone
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updates["avatar"] = req.Avatar
|
||||
}
|
||||
if req.Preferences != "" {
|
||||
updates["preferences"] = req.Preferences
|
||||
}
|
||||
if req.AISettings != "" {
|
||||
updates["ai_settings"] = req.AISettings
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return global.GVA_DB.Model(&app.AppUser{}).Where("id = ?", userID).Updates(updates).Error
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *AuthService) ChangePassword(userID uint, req *request.ChangePasswordRequest) error {
|
||||
// 查询用户
|
||||
var user app.AppUser
|
||||
err := global.GVA_DB.Where("id = ?", userID).First(&user).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword))
|
||||
if err != nil {
|
||||
return errors.New("原密码错误")
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return errors.New("密码加密失败")
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
return global.GVA_DB.Model(&user).Update("password", string(hashedPassword)).Error
|
||||
}
|
||||
366
server/service/app/character.go
Normal file
366
server/service/app/character.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"git.echol.cn/loser/st/server/model/app/request"
|
||||
"git.echol.cn/loser/st/server/model/app/response"
|
||||
"git.echol.cn/loser/st/server/utils"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CharacterService struct{}
|
||||
|
||||
// CreateCharacter 创建角色卡
|
||||
func (s *CharacterService) CreateCharacter(userID uint, req *request.CreateCharacterRequest) (*response.CharacterResponse, error) {
|
||||
// 序列化 JSON 字段
|
||||
tagsJSON, _ := json.Marshal(req.Tags)
|
||||
alternateGreetingsJSON, _ := json.Marshal(req.AlternateGreetings)
|
||||
characterBookJSON, _ := json.Marshal(req.CharacterBook)
|
||||
extensionsJSON, _ := json.Marshal(req.Extensions)
|
||||
|
||||
character := app.AICharacter{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Avatar: req.Avatar,
|
||||
Creator: req.Creator,
|
||||
Version: req.Version,
|
||||
Description: req.Description,
|
||||
Personality: req.Personality,
|
||||
Scenario: req.Scenario,
|
||||
FirstMes: req.FirstMes,
|
||||
MesExample: req.MesExample,
|
||||
CreatorNotes: req.CreatorNotes,
|
||||
SystemPrompt: req.SystemPrompt,
|
||||
PostHistoryInstructions: req.PostHistoryInstructions,
|
||||
Tags: datatypes.JSON(tagsJSON),
|
||||
AlternateGreetings: datatypes.JSON(alternateGreetingsJSON),
|
||||
CharacterBook: datatypes.JSON(characterBookJSON),
|
||||
Extensions: datatypes.JSON(extensionsJSON),
|
||||
IsPublic: req.IsPublic,
|
||||
Spec: "chara_card_v2",
|
||||
SpecVersion: "2.0",
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Create(&character).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToCharacterResponse(&character)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetCharacterList 获取角色卡列表
|
||||
func (s *CharacterService) GetCharacterList(userID uint, req *request.GetCharacterListRequest) (*response.CharacterListResponse, error) {
|
||||
var characters []app.AICharacter
|
||||
var total int64
|
||||
|
||||
db := global.GVA_DB.Model(&app.AICharacter{})
|
||||
|
||||
// 筛选条件
|
||||
if req.IsPublic != nil {
|
||||
if *req.IsPublic {
|
||||
db = db.Where("is_public = ?", true)
|
||||
} else {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
} else {
|
||||
db = db.Where("user_id = ? OR is_public = ?", userID, true)
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
db = db.Where("name LIKE ? OR description LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if req.Tag != "" {
|
||||
db = db.Where("tags @> ?", datatypes.JSON(`["`+req.Tag+`"]`))
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
err := db.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
err = db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&characters).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换响应
|
||||
list := make([]response.CharacterResponse, len(characters))
|
||||
for i, char := range characters {
|
||||
list[i] = response.ToCharacterResponse(&char)
|
||||
}
|
||||
|
||||
return &response.CharacterListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCharacterByID 获取角色卡详情
|
||||
func (s *CharacterService) GetCharacterByID(userID, characterID uint) (*response.CharacterResponse, error) {
|
||||
var character app.AICharacter
|
||||
|
||||
err := global.GVA_DB.Where("id = ? AND (user_id = ? OR is_public = ?)", characterID, userID, true).
|
||||
First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在或无权访问")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := response.ToCharacterResponse(&character)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// UpdateCharacter 更新角色卡
|
||||
func (s *CharacterService) UpdateCharacter(userID, characterID uint, req *request.UpdateCharacterRequest) error {
|
||||
var character app.AICharacter
|
||||
|
||||
// 检查权限
|
||||
err := global.GVA_DB.Where("id = ? AND user_id = ?", characterID, userID).First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("角色卡不存在或无权修改")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
updates := map[string]interface{}{}
|
||||
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updates["avatar"] = req.Avatar
|
||||
}
|
||||
if req.Creator != "" {
|
||||
updates["creator"] = req.Creator
|
||||
}
|
||||
if req.Version != "" {
|
||||
updates["version"] = req.Version
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if req.Personality != "" {
|
||||
updates["personality"] = req.Personality
|
||||
}
|
||||
if req.Scenario != "" {
|
||||
updates["scenario"] = req.Scenario
|
||||
}
|
||||
if req.FirstMes != "" {
|
||||
updates["first_mes"] = req.FirstMes
|
||||
}
|
||||
if req.MesExample != "" {
|
||||
updates["mes_example"] = req.MesExample
|
||||
}
|
||||
if req.CreatorNotes != "" {
|
||||
updates["creator_notes"] = req.CreatorNotes
|
||||
}
|
||||
if req.SystemPrompt != "" {
|
||||
updates["system_prompt"] = req.SystemPrompt
|
||||
}
|
||||
if req.PostHistoryInstructions != "" {
|
||||
updates["post_history_instructions"] = req.PostHistoryInstructions
|
||||
}
|
||||
|
||||
if req.Tags != nil {
|
||||
tagsJSON, _ := json.Marshal(req.Tags)
|
||||
updates["tags"] = datatypes.JSON(tagsJSON)
|
||||
}
|
||||
if req.AlternateGreetings != nil {
|
||||
alternateGreetingsJSON, _ := json.Marshal(req.AlternateGreetings)
|
||||
updates["alternate_greetings"] = datatypes.JSON(alternateGreetingsJSON)
|
||||
}
|
||||
if req.CharacterBook != nil {
|
||||
characterBookJSON, _ := json.Marshal(req.CharacterBook)
|
||||
updates["character_book"] = datatypes.JSON(characterBookJSON)
|
||||
}
|
||||
if req.Extensions != nil {
|
||||
extensionsJSON, _ := json.Marshal(req.Extensions)
|
||||
updates["extensions"] = datatypes.JSON(extensionsJSON)
|
||||
}
|
||||
|
||||
updates["is_public"] = req.IsPublic
|
||||
|
||||
return global.GVA_DB.Model(&character).Updates(updates).Error
|
||||
}
|
||||
|
||||
// DeleteCharacter 删除角色卡
|
||||
func (s *CharacterService) DeleteCharacter(userID, characterID uint) error {
|
||||
result := global.GVA_DB.Where("id = ? AND user_id = ?", characterID, userID).Delete(&app.AICharacter{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("角色卡不存在或无权删除")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportCharacterFromPNG 从 PNG 文件导入角色卡
|
||||
func (s *CharacterService) ImportCharacterFromPNG(userID uint, file *multipart.FileHeader) (*response.CharacterResponse, error) {
|
||||
// 读取文件内容
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, errors.New("打开文件失败")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 读取文件数据
|
||||
fileData := make([]byte, file.Size)
|
||||
_, err = src.Read(fileData)
|
||||
if err != nil {
|
||||
return nil, errors.New("读取文件失败")
|
||||
}
|
||||
|
||||
// 提取角色卡数据
|
||||
card, err := utils.ExtractCharacterFromPNG(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 上传 PNG 图片到 OSS(替代 Base64)
|
||||
var uploadService UploadService
|
||||
avatarURL, err := uploadService.UploadImage(file)
|
||||
if err != nil {
|
||||
// 如果上传失败,回退到 Base64(向后兼容)
|
||||
avatarURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fileData)
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
req := &request.CreateCharacterRequest{
|
||||
Name: card.Data.Name,
|
||||
Avatar: avatarURL,
|
||||
Creator: card.Data.Creator,
|
||||
Version: card.Data.CharacterVersion,
|
||||
Description: card.Data.Description,
|
||||
Personality: card.Data.Personality,
|
||||
Scenario: card.Data.Scenario,
|
||||
FirstMes: card.Data.FirstMes,
|
||||
MesExample: card.Data.MesExample,
|
||||
CreatorNotes: card.Data.CreatorNotes,
|
||||
SystemPrompt: card.Data.SystemPrompt,
|
||||
PostHistoryInstructions: card.Data.PostHistoryInstructions,
|
||||
Tags: card.Data.Tags,
|
||||
AlternateGreetings: card.Data.AlternateGreetings,
|
||||
CharacterBook: card.Data.CharacterBook,
|
||||
Extensions: card.Data.Extensions,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
return s.CreateCharacter(userID, req)
|
||||
}
|
||||
|
||||
// ImportCharacterFromJSON 从 JSON 文件导入角色卡
|
||||
func (s *CharacterService) ImportCharacterFromJSON(userID uint, file *multipart.FileHeader) (*response.CharacterResponse, error) {
|
||||
// 读取文件内容
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, errors.New("打开文件失败")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 读取文件数据
|
||||
fileData := make([]byte, file.Size)
|
||||
_, err = src.Read(fileData)
|
||||
if err != nil {
|
||||
return nil, errors.New("读取文件失败")
|
||||
}
|
||||
|
||||
// 解析 JSON
|
||||
card, err := utils.ParseCharacterCardJSON(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
req := &request.CreateCharacterRequest{
|
||||
Name: card.Data.Name,
|
||||
Creator: card.Data.Creator,
|
||||
Version: card.Data.CharacterVersion,
|
||||
Description: card.Data.Description,
|
||||
Personality: card.Data.Personality,
|
||||
Scenario: card.Data.Scenario,
|
||||
FirstMes: card.Data.FirstMes,
|
||||
MesExample: card.Data.MesExample,
|
||||
CreatorNotes: card.Data.CreatorNotes,
|
||||
SystemPrompt: card.Data.SystemPrompt,
|
||||
PostHistoryInstructions: card.Data.PostHistoryInstructions,
|
||||
Tags: card.Data.Tags,
|
||||
AlternateGreetings: card.Data.AlternateGreetings,
|
||||
CharacterBook: card.Data.CharacterBook,
|
||||
Extensions: card.Data.Extensions,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
return s.CreateCharacter(userID, req)
|
||||
}
|
||||
|
||||
// ExportCharacterToJSON 导出角色卡为 JSON
|
||||
func (s *CharacterService) ExportCharacterToJSON(userID, characterID uint) (*utils.CharacterCardV2, error) {
|
||||
var character app.AICharacter
|
||||
|
||||
err := global.GVA_DB.Where("id = ? AND (user_id = ? OR is_public = ?)", characterID, userID, true).
|
||||
First(&character).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("角色卡不存在或无权访问")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析 JSON 字段
|
||||
var tags []string
|
||||
var alternateGreetings []string
|
||||
var characterBook map[string]interface{}
|
||||
var extensions map[string]interface{}
|
||||
|
||||
json.Unmarshal(character.Tags, &tags)
|
||||
json.Unmarshal(character.AlternateGreetings, &alternateGreetings)
|
||||
json.Unmarshal(character.CharacterBook, &characterBook)
|
||||
json.Unmarshal(character.Extensions, &extensions)
|
||||
|
||||
// 构建 V2 格式
|
||||
card := &utils.CharacterCardV2{
|
||||
Spec: character.Spec,
|
||||
SpecVersion: character.SpecVersion,
|
||||
Data: utils.CharacterCardV2Data{
|
||||
Name: character.Name,
|
||||
Description: character.Description,
|
||||
Personality: character.Personality,
|
||||
Scenario: character.Scenario,
|
||||
FirstMes: character.FirstMes,
|
||||
MesExample: character.MesExample,
|
||||
CreatorNotes: character.CreatorNotes,
|
||||
SystemPrompt: character.SystemPrompt,
|
||||
PostHistoryInstructions: character.PostHistoryInstructions,
|
||||
Tags: tags,
|
||||
Creator: character.Creator,
|
||||
CharacterVersion: character.Version,
|
||||
AlternateGreetings: alternateGreetings,
|
||||
CharacterBook: characterBook,
|
||||
Extensions: extensions,
|
||||
},
|
||||
}
|
||||
|
||||
return card, nil
|
||||
}
|
||||
1172
server/service/app/conversation.go
Normal file
1172
server/service/app/conversation.go
Normal file
File diff suppressed because it is too large
Load Diff
10
server/service/app/enter.go
Normal file
10
server/service/app/enter.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package app
|
||||
|
||||
type AppServiceGroup struct {
|
||||
AuthService
|
||||
CharacterService
|
||||
ConversationService
|
||||
AIConfigService
|
||||
PresetService
|
||||
UploadService
|
||||
}
|
||||
353
server/service/app/preset.go
Normal file
353
server/service/app/preset.go
Normal file
@@ -0,0 +1,353 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"git.echol.cn/loser/st/server/model/app/request"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PresetService struct{}
|
||||
|
||||
// CreatePreset 创建预设
|
||||
func (s *PresetService) CreatePreset(userID uint, req *request.CreatePresetRequest) (*app.AIPreset, error) {
|
||||
// 序列化 StopSequences
|
||||
var stopSequencesJSON datatypes.JSON
|
||||
if len(req.StopSequences) > 0 {
|
||||
data, err := json.Marshal(req.StopSequences)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化 StopSequences 失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
stopSequencesJSON = data
|
||||
}
|
||||
|
||||
// 序列化 Extensions
|
||||
var extensionsJSON datatypes.JSON
|
||||
if len(req.Extensions) > 0 {
|
||||
data, err := json.Marshal(req.Extensions)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化 Extensions 失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
extensionsJSON = data
|
||||
}
|
||||
|
||||
preset := &app.AIPreset{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
IsPublic: req.IsPublic,
|
||||
Temperature: req.Temperature,
|
||||
TopP: req.TopP,
|
||||
TopK: req.TopK,
|
||||
FrequencyPenalty: req.FrequencyPenalty,
|
||||
PresencePenalty: req.PresencePenalty,
|
||||
MaxTokens: req.MaxTokens,
|
||||
RepetitionPenalty: req.RepetitionPenalty,
|
||||
MinP: req.MinP,
|
||||
TopA: req.TopA,
|
||||
SystemPrompt: req.SystemPrompt,
|
||||
StopSequences: stopSequencesJSON,
|
||||
Extensions: extensionsJSON,
|
||||
}
|
||||
|
||||
if err := global.GVA_DB.Create(preset).Error; err != nil {
|
||||
global.GVA_LOG.Error("创建预设失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return preset, nil
|
||||
}
|
||||
|
||||
// GetPresetList 获取预设列表
|
||||
func (s *PresetService) GetPresetList(userID uint, req *request.GetPresetListRequest) ([]app.AIPreset, int64, error) {
|
||||
var presets []app.AIPreset
|
||||
var total int64
|
||||
|
||||
db := global.GVA_DB.Model(&app.AIPreset{})
|
||||
|
||||
// 权限过滤:只能看到自己的预设或公开的预设
|
||||
db = db.Where("user_id = ? OR is_public = ?", userID, true)
|
||||
|
||||
// 关键词搜索
|
||||
if req.Keyword != "" {
|
||||
db = db.Where("name LIKE ? OR description LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
// 公开/私有过滤
|
||||
if req.IsPublic != nil {
|
||||
db = db.Where("is_public = ?", *req.IsPublic)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取预设总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := db.Order("is_default DESC, updated_at DESC").
|
||||
Offset(offset).
|
||||
Limit(req.PageSize).
|
||||
Find(&presets).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取预设列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return presets, total, nil
|
||||
}
|
||||
|
||||
// GetPresetByID 根据ID获取预设
|
||||
func (s *PresetService) GetPresetByID(userID uint, id uint) (*app.AIPreset, error) {
|
||||
var preset app.AIPreset
|
||||
|
||||
// 权限检查:只能访问自己的预设或公开的预设
|
||||
if err := global.GVA_DB.Where("id = ? AND (user_id = ? OR is_public = ?)", id, userID, true).
|
||||
First(&preset).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("预设不存在或无权访问")
|
||||
}
|
||||
global.GVA_LOG.Error("获取预设失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &preset, nil
|
||||
}
|
||||
|
||||
// UpdatePreset 更新预设
|
||||
func (s *PresetService) UpdatePreset(userID uint, id uint, req *request.UpdatePresetRequest) error {
|
||||
var preset app.AIPreset
|
||||
|
||||
// 权限检查:只能更新自己的预设
|
||||
if err := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).First(&preset).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("预设不存在或无权修改")
|
||||
}
|
||||
global.GVA_LOG.Error("查询预设失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 构建更新数据
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if req.IsPublic != nil {
|
||||
updates["is_public"] = *req.IsPublic
|
||||
}
|
||||
if req.Temperature != nil {
|
||||
updates["temperature"] = *req.Temperature
|
||||
}
|
||||
if req.TopP != nil {
|
||||
updates["top_p"] = *req.TopP
|
||||
}
|
||||
if req.TopK != nil {
|
||||
updates["top_k"] = *req.TopK
|
||||
}
|
||||
if req.FrequencyPenalty != nil {
|
||||
updates["frequency_penalty"] = *req.FrequencyPenalty
|
||||
}
|
||||
if req.PresencePenalty != nil {
|
||||
updates["presence_penalty"] = *req.PresencePenalty
|
||||
}
|
||||
if req.MaxTokens != nil {
|
||||
updates["max_tokens"] = *req.MaxTokens
|
||||
}
|
||||
if req.RepetitionPenalty != nil {
|
||||
updates["repetition_penalty"] = *req.RepetitionPenalty
|
||||
}
|
||||
if req.MinP != nil {
|
||||
updates["min_p"] = *req.MinP
|
||||
}
|
||||
if req.TopA != nil {
|
||||
updates["top_a"] = *req.TopA
|
||||
}
|
||||
if req.SystemPrompt != nil {
|
||||
updates["system_prompt"] = *req.SystemPrompt
|
||||
}
|
||||
|
||||
// 更新 StopSequences
|
||||
if req.StopSequences != nil {
|
||||
data, err := json.Marshal(req.StopSequences)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化 StopSequences 失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
updates["stop_sequences"] = datatypes.JSON(data)
|
||||
}
|
||||
|
||||
// 更新 Extensions
|
||||
if req.Extensions != nil {
|
||||
data, err := json.Marshal(req.Extensions)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化 Extensions 失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
updates["extensions"] = datatypes.JSON(data)
|
||||
}
|
||||
|
||||
if err := global.GVA_DB.Model(&preset).Updates(updates).Error; err != nil {
|
||||
global.GVA_LOG.Error("更新预设失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePreset 删除预设
|
||||
func (s *PresetService) DeletePreset(userID uint, id uint) error {
|
||||
// 权限检查:只能删除自己的预设
|
||||
result := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).Delete(&app.AIPreset{})
|
||||
if result.Error != nil {
|
||||
global.GVA_LOG.Error("删除预设失败", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("预设不存在或无权删除")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDefaultPreset 设置默认预设
|
||||
func (s *PresetService) SetDefaultPreset(userID uint, id uint) error {
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查预设是否存在且属于当前用户
|
||||
var preset app.AIPreset
|
||||
if err := tx.Where("id = ? AND user_id = ?", id, userID).First(&preset).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("预设不存在或无权访问")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 取消当前用户的所有默认预设
|
||||
if err := tx.Model(&app.AIPreset{}).
|
||||
Where("user_id = ? AND is_default = ?", userID, true).
|
||||
Update("is_default", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置新的默认预设
|
||||
if err := tx.Model(&preset).Update("is_default", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ImportPresetFromJSON 从JSON导入预设
|
||||
func (s *PresetService) ImportPresetFromJSON(userID uint, jsonData []byte, filename string) (*app.AIPreset, error) {
|
||||
// 尝试解析为 SillyTavern 格式
|
||||
var stPreset struct {
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"top_p"`
|
||||
TopK int `json:"top_k"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty"`
|
||||
PresencePenalty float64 `json:"presence_penalty"`
|
||||
MaxTokens int `json:"openai_max_tokens"`
|
||||
RepetitionPenalty float64 `json:"repetition_penalty"`
|
||||
MinP float64 `json:"min_p"`
|
||||
TopA float64 `json:"top_a"`
|
||||
StopSequences []string `json:"stop_sequences"`
|
||||
Prompts []map[string]interface{} `json:"prompts"`
|
||||
PromptOrder []map[string]interface{} `json:"prompt_order"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonData, &stPreset); err != nil {
|
||||
global.GVA_LOG.Error("解析预设JSON失败", zap.Error(err))
|
||||
return nil, errors.New("无效的预设格式")
|
||||
}
|
||||
|
||||
// 从文件名提取预设名称(去掉 .json 后缀)
|
||||
name := filename
|
||||
if len(name) > 5 && name[len(name)-5:] == ".json" {
|
||||
name = name[:len(name)-5]
|
||||
}
|
||||
|
||||
// 构建 extensions 对象,包含 prompts 和 prompt_order
|
||||
extensions := map[string]interface{}{
|
||||
"prompts": stPreset.Prompts,
|
||||
"prompt_order": stPreset.PromptOrder,
|
||||
}
|
||||
|
||||
// 转换为创建请求
|
||||
req := &request.CreatePresetRequest{
|
||||
Name: name,
|
||||
Description: "从 SillyTavern 导入",
|
||||
Temperature: stPreset.Temperature,
|
||||
TopP: stPreset.TopP,
|
||||
TopK: stPreset.TopK,
|
||||
FrequencyPenalty: stPreset.FrequencyPenalty,
|
||||
PresencePenalty: stPreset.PresencePenalty,
|
||||
MaxTokens: stPreset.MaxTokens,
|
||||
RepetitionPenalty: stPreset.RepetitionPenalty,
|
||||
MinP: stPreset.MinP,
|
||||
TopA: stPreset.TopA,
|
||||
SystemPrompt: "",
|
||||
StopSequences: stPreset.StopSequences,
|
||||
Extensions: extensions,
|
||||
}
|
||||
|
||||
return s.CreatePreset(userID, req)
|
||||
}
|
||||
|
||||
// ExportPresetToJSON 导出预设为JSON
|
||||
func (s *PresetService) ExportPresetToJSON(userID uint, id uint) ([]byte, error) {
|
||||
preset, err := s.GetPresetByID(userID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析 StopSequences
|
||||
var stopSequences []string
|
||||
if len(preset.StopSequences) > 0 {
|
||||
json.Unmarshal(preset.StopSequences, &stopSequences)
|
||||
}
|
||||
|
||||
// 解析 Extensions
|
||||
var extensions map[string]interface{}
|
||||
if len(preset.Extensions) > 0 {
|
||||
json.Unmarshal(preset.Extensions, &extensions)
|
||||
}
|
||||
|
||||
// 转换为 SillyTavern 格式
|
||||
stPreset := map[string]interface{}{
|
||||
"name": preset.Name,
|
||||
"description": preset.Description,
|
||||
"temperature": preset.Temperature,
|
||||
"top_p": preset.TopP,
|
||||
"top_k": preset.TopK,
|
||||
"frequency_penalty": preset.FrequencyPenalty,
|
||||
"presence_penalty": preset.PresencePenalty,
|
||||
"max_tokens": preset.MaxTokens,
|
||||
"repetition_penalty": preset.RepetitionPenalty,
|
||||
"min_p": preset.MinP,
|
||||
"top_a": preset.TopA,
|
||||
"system_prompt": preset.SystemPrompt,
|
||||
"stop_sequences": stopSequences,
|
||||
"extensions": extensions,
|
||||
}
|
||||
|
||||
return json.MarshalIndent(stPreset, "", " ")
|
||||
}
|
||||
|
||||
// IncrementUseCount 增加使用次数
|
||||
func (s *PresetService) IncrementUseCount(id uint) error {
|
||||
return global.GVA_DB.Model(&app.AIPreset{}).
|
||||
Where("id = ?", id).
|
||||
Update("use_count", gorm.Expr("use_count + ?", 1)).Error
|
||||
}
|
||||
49
server/service/app/upload.go
Normal file
49
server/service/app/upload.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
"strings"
|
||||
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"git.echol.cn/loser/st/server/utils/upload"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type UploadService struct{}
|
||||
|
||||
// UploadImage 上传图片到 OSS
|
||||
// 返回图片的访问 URL
|
||||
func (s *UploadService) UploadImage(header *multipart.FileHeader) (string, error) {
|
||||
// 验证文件类型
|
||||
if !isImageFile(header.Filename) {
|
||||
return "", errors.New("只支持图片格式:jpg, jpeg, png, gif, webp")
|
||||
}
|
||||
|
||||
// 验证文件大小(限制 10MB)
|
||||
if header.Size > 10*1024*1024 {
|
||||
return "", errors.New("图片大小不能超过 10MB")
|
||||
}
|
||||
|
||||
// 使用 OSS 上传
|
||||
oss := upload.NewOss()
|
||||
filePath, _, uploadErr := oss.UploadFile(header)
|
||||
if uploadErr != nil {
|
||||
global.GVA_LOG.Error("图片上传失败", zap.Error(uploadErr))
|
||||
return "", errors.New("图片上传失败")
|
||||
}
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// isImageFile 检查是否为图片文件
|
||||
func isImageFile(filename string) bool {
|
||||
ext := strings.ToLower(filename[strings.LastIndex(filename, ".")+1:])
|
||||
imageExts := []string{"jpg", "jpeg", "png", "gif", "webp"}
|
||||
for _, validExt := range imageExts {
|
||||
if ext == validExt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user