🎨 重构用户端前端为vue开发,完善基础类和角色相关接口
This commit is contained in:
213
server/model/app/README.md
Normal file
213
server/model/app/README.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# App 前台应用数据模型
|
||||
|
||||
## 📋 模型列表
|
||||
|
||||
本目录包含所有前台用户应用相关的数据模型,与管理后台的 `system` 模块完全独立。
|
||||
|
||||
### 1. 用户相关模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `app_user.go` | `AppUser` | `app_users` | 前台用户表 |
|
||||
| `app_user_session.go` | `AppUserSession` | `app_user_sessions` | 用户会话表 |
|
||||
|
||||
### 2. AI 角色相关模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_character.go` | `AICharacter` | `ai_characters` | AI 角色表 |
|
||||
| `ai_character.go` | `AppUserFavoriteCharacter` | `app_user_favorite_characters` | 用户收藏角色表 |
|
||||
|
||||
### 3. 对话相关模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_chat.go` | `AIChat` | `ai_chats` | 对话表 |
|
||||
| `ai_chat.go` | `AIChatMember` | `ai_chat_members` | 群聊成员表 |
|
||||
| `ai_message.go` | `AIMessage` | `ai_messages` | 消息表 |
|
||||
| `ai_message.go` | `AIMessageSwipe` | `ai_message_swipes` | 消息变体表 |
|
||||
|
||||
### 4. 向量记忆模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_memory.go` | `AIMemoryVector` | `ai_memory_vectors` | 向量记忆表(使用 pgvector) |
|
||||
|
||||
### 5. AI 服务配置模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_provider.go` | `AIProvider` | `ai_providers` | AI 提供商配置表 |
|
||||
| `ai_provider.go` | `AIModel` | `ai_models` | AI 模型配置表 |
|
||||
|
||||
### 6. 文件管理模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_file.go` | `AIFile` | `ai_files` | 文件表 |
|
||||
|
||||
### 7. 其他模型
|
||||
|
||||
| 文件 | 模型 | 表名 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ai_preset.go` | `AIPreset` | `ai_presets` | 对话预设表 |
|
||||
| `ai_world_info.go` | `AIWorldInfo` | `ai_world_info` | 世界书表 |
|
||||
| `ai_usage_stat.go` | `AIUsageStat` | `ai_usage_stats` | 使用统计表 |
|
||||
|
||||
## 🔧 使用说明
|
||||
|
||||
### 1. 数据库自动迁移
|
||||
|
||||
所有模型已在 `initialize/gorm.go` 中注册,启动服务时会自动创建表:
|
||||
|
||||
```go
|
||||
// 在 RegisterTables() 函数中已注册
|
||||
app.AppUser{},
|
||||
app.AppUserSession{},
|
||||
app.AICharacter{},
|
||||
app.AppUserFavoriteCharacter{},
|
||||
app.AIChat{},
|
||||
app.AIChatMember{},
|
||||
app.AIMessage{},
|
||||
app.AIMessageSwipe{},
|
||||
app.AIMemoryVector{},
|
||||
app.AIProvider{},
|
||||
app.AIModel{},
|
||||
app.AIFile{},
|
||||
app.AIPreset{},
|
||||
app.AIWorldInfo{},
|
||||
app.AIUsageStat{},
|
||||
```
|
||||
|
||||
### 2. PostgreSQL 向量扩展
|
||||
|
||||
向量记忆功能依赖 `pgvector` 扩展,已在 `initialize/gorm_pgsql_extension.go` 中自动安装:
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
CREATE INDEX idx_memory_vectors_embedding ON ai_memory_vectors
|
||||
USING hnsw (embedding vector_cosine_ops);
|
||||
```
|
||||
|
||||
### 3. 外键关系
|
||||
|
||||
模型之间的关系已通过 GORM 标签定义:
|
||||
|
||||
- `AppUser` ← `AppUserSession`(一对多)
|
||||
- `AppUser` ← `AICharacter`(一对多,创建者)
|
||||
- `AppUser` ← `AIChat`(一对多)
|
||||
- `AppUser` ← `AppUserFavoriteCharacter`(多对多,通过中间表)
|
||||
- `AICharacter` ← `AppUserFavoriteCharacter`(多对多,通过中间表)
|
||||
- `AICharacter` ← `AIChat`(一对多)
|
||||
- `AIChat` ← `AIMessage`(一对多)
|
||||
- `AIChat` ← `AIChatMember`(多对多,通过中间表)
|
||||
- `AICharacter` ← `AIChatMember`(多对多,通过中间表)
|
||||
- `AIMessage` ← `AIMessageSwipe`(一对多)
|
||||
- `AIProvider` ← `AIModel`(一对多)
|
||||
|
||||
### 4. JSONB 字段
|
||||
|
||||
以下字段使用 PostgreSQL 的 JSONB 类型:
|
||||
|
||||
- `AppUser.AISettings` - AI 相关配置
|
||||
- `AppUser.Preferences` - 用户偏好设置
|
||||
- `AICharacter.CardData` - 角色卡片数据
|
||||
- `AICharacter.Tags` - 角色标签
|
||||
- `AICharacter.ExampleMessages` - 消息示例
|
||||
- `AIChat.Settings` - 对话设置
|
||||
- `AIMessage.GenerationParams` - AI 生成参数
|
||||
- `AIMessage.Metadata` - 消息元数据
|
||||
- `AIMemoryVector.Metadata` - 记忆元数据
|
||||
- `AIProvider.APIConfig` - API 配置
|
||||
- `AIModel.Config` - 模型配置
|
||||
- `AIFile.RelatedTo` - 文件关联对象
|
||||
- `AIFile.Metadata` - 文件元数据
|
||||
- `AIPreset.Config` - 预设配置
|
||||
- `AIWorldInfo.TriggerConfig` - 触发条件配置
|
||||
|
||||
### 5. 向量字段
|
||||
|
||||
`AIMemoryVector.Embedding` 使用 `pgvector.Vector` 类型,维度为 1536(OpenAI text-embedding-ada-002)。
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **不要修改 system 包**:所有管理后台相关的模型在 `model/system/` 包中,**不要修改**
|
||||
2. **表名前缀**:
|
||||
- 前台用户相关:`app_*`
|
||||
- AI 功能相关:`ai_*`
|
||||
- 系统管理相关:`sys_*`(不修改)
|
||||
3. **UUID 生成**:`AppUser.UUID` 使用数据库自动生成(PostgreSQL 的 `gen_random_uuid()`)
|
||||
4. **软删除**:所有模型继承 `global.GVA_MODEL`,自动支持软删除
|
||||
5. **时间字段**:`CreatedAt`、`UpdatedAt`、`DeletedAt` 由 GORM 自动管理
|
||||
|
||||
## 📊 ER 图关系
|
||||
|
||||
```
|
||||
AppUser (前台用户)
|
||||
├── AppUserSession (会话)
|
||||
├── AICharacter (创建的角色)
|
||||
├── AIChat (对话)
|
||||
├── AppUserFavoriteCharacter (收藏的角色)
|
||||
├── AIMemoryVector (记忆)
|
||||
├── AIProvider (AI 提供商配置)
|
||||
├── AIFile (文件)
|
||||
├── AIPreset (预设)
|
||||
├── AIWorldInfo (世界书)
|
||||
└── AIUsageStat (使用统计)
|
||||
|
||||
AICharacter (AI 角色)
|
||||
├── AIChat (对话)
|
||||
├── AIChatMember (群聊成员)
|
||||
├── AppUserFavoriteCharacter (被收藏)
|
||||
└── AIMemoryVector (记忆)
|
||||
|
||||
AIChat (对话)
|
||||
├── AIMessage (消息)
|
||||
├── AIChatMember (群聊成员)
|
||||
└── AIMemoryVector (记忆)
|
||||
|
||||
AIMessage (消息)
|
||||
└── AIMessageSwipe (消息变体)
|
||||
|
||||
AIProvider (AI 提供商)
|
||||
└── AIModel (AI 模型)
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. 确保 PostgreSQL 已安装 pgvector 扩展
|
||||
2. 配置 `config.yaml` 中的数据库连接
|
||||
3. 启动服务,AutoMigrate 会自动创建所有表
|
||||
4. 检查日志确认表创建成功
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
go run main.go
|
||||
|
||||
# 查看日志
|
||||
# [GVA] pgvector extension is ready
|
||||
# [GVA] vector indexes created successfully
|
||||
# [GVA] register table success
|
||||
```
|
||||
|
||||
## 📝 开发建议
|
||||
|
||||
1. 查询时使用预加载避免 N+1 问题:
|
||||
```go
|
||||
db.Preload("User").Preload("Character").Find(&chats)
|
||||
```
|
||||
|
||||
2. 向量搜索示例:
|
||||
```go
|
||||
db.Order("embedding <=> ?", queryVector).Limit(10).Find(&memories)
|
||||
```
|
||||
|
||||
3. JSONB 查询示例:
|
||||
```go
|
||||
db.Where("ai_settings->>'model' = ?", "gpt-4").Find(&users)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**创建日期**: 2026-02-10
|
||||
**维护者**: 开发团队
|
||||
49
server/model/app/ai_character.go
Normal file
49
server/model/app/ai_character.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"github.com/lib/pq"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AICharacter AI 角色表
|
||||
type AICharacter struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"type:varchar(500);not null;comment:角色名称"`
|
||||
Description string `json:"description" gorm:"type:text;comment:角色描述"`
|
||||
Personality string `json:"personality" gorm:"type:text;comment:角色性格"`
|
||||
Scenario string `json:"scenario" gorm:"type:text;comment:角色场景"`
|
||||
Avatar string `json:"avatar" gorm:"type:varchar(1024);comment:角色头像"`
|
||||
CreatorID *uint `json:"creatorId" gorm:"index;comment:创建者ID"`
|
||||
Creator *AppUser `json:"creator" gorm:"foreignKey:CreatorID"`
|
||||
CreatorName string `json:"creatorName" gorm:"type:varchar(200);comment:创建者名称"`
|
||||
CreatorNotes string `json:"creatorNotes" gorm:"type:text;comment:创建者备注"`
|
||||
CardData datatypes.JSON `json:"cardData" gorm:"type:jsonb;not null;comment:角色卡片数据"`
|
||||
Tags pq.StringArray `json:"tags" gorm:"type:text[];comment:角色标签"`
|
||||
IsPublic bool `json:"isPublic" gorm:"default:false;index;comment:是否公开"`
|
||||
Version int `json:"version" gorm:"default:1;comment:角色版本"`
|
||||
FirstMessage string `json:"firstMessage" gorm:"type:text;comment:第一条消息"`
|
||||
ExampleMessages pq.StringArray `json:"exampleMessages" gorm:"type:text[];comment:消息示例"`
|
||||
TotalChats int `json:"totalChats" gorm:"default:0;comment:对话总数"`
|
||||
TotalLikes int `json:"totalLikes" gorm:"default:0;comment:点赞总数"`
|
||||
UsageCount int `json:"usageCount" gorm:"default:0;comment:使用次数"`
|
||||
FavoriteCount int `json:"favoriteCount" gorm:"default:0;comment:收藏次数"`
|
||||
TokenCount int `json:"tokenCount" gorm:"default:0;comment:Token数量"`
|
||||
}
|
||||
|
||||
func (AICharacter) TableName() string {
|
||||
return "ai_characters"
|
||||
}
|
||||
|
||||
// AppUserFavoriteCharacter 用户收藏的角色
|
||||
type AppUserFavoriteCharacter struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"not null;index:idx_user_character,unique;comment:用户ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
CharacterID uint `json:"characterId" gorm:"not null;index:idx_user_character,unique;comment:角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
}
|
||||
|
||||
func (AppUserFavoriteCharacter) TableName() string {
|
||||
return "app_user_favorite_characters"
|
||||
}
|
||||
41
server/model/app/ai_chat.go
Normal file
41
server/model/app/ai_chat.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AIChat 对话表
|
||||
type AIChat struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" gorm:"type:varchar(500);default:新对话;comment:对话标题"`
|
||||
UserID uint `json:"userId" gorm:"not null;index;comment:所属用户ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
CharacterID *uint `json:"characterId" gorm:"index;comment:关联角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
ChatType string `json:"chatType" gorm:"type:varchar(50);default:single;comment:对话类型"`
|
||||
Settings datatypes.JSON `json:"settings" gorm:"type:jsonb;comment:对话设置"`
|
||||
LastMessageAt *time.Time `json:"lastMessageAt" gorm:"index;comment:最后一条消息时间"`
|
||||
MessageCount int `json:"messageCount" gorm:"default:0;comment:消息数量"`
|
||||
IsPinned bool `json:"isPinned" gorm:"default:false;comment:是否固定"`
|
||||
}
|
||||
|
||||
func (AIChat) TableName() string {
|
||||
return "ai_chats"
|
||||
}
|
||||
|
||||
// AIChatMember 群聊成员表
|
||||
type AIChatMember struct {
|
||||
global.GVA_MODEL
|
||||
ChatID uint `json:"chatId" gorm:"not null;index:idx_chat_character,unique;comment:对话ID"`
|
||||
Chat *AIChat `json:"chat" gorm:"foreignKey:ChatID"`
|
||||
CharacterID uint `json:"characterId" gorm:"not null;index:idx_chat_character,unique;comment:角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
DisplayOrder int `json:"displayOrder" gorm:"default:0;comment:显示顺序"`
|
||||
Settings datatypes.JSON `json:"settings" gorm:"type:jsonb;comment:成员设置"`
|
||||
}
|
||||
|
||||
func (AIChatMember) TableName() string {
|
||||
return "ai_chat_members"
|
||||
}
|
||||
26
server/model/app/ai_file.go
Normal file
26
server/model/app/ai_file.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIFile 文件表
|
||||
type AIFile struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"not null;index;comment:上传者ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
Filename string `json:"filename" gorm:"type:varchar(500);not null;comment:文件名"`
|
||||
OriginalFilename string `json:"originalFilename" gorm:"type:varchar(500);not null;comment:原始文件名"`
|
||||
FileType string `json:"fileType" gorm:"type:varchar(100);not null;index;comment:文件类型"`
|
||||
MimeType string `json:"mimeType" gorm:"type:varchar(200);comment:MIME类型"`
|
||||
FileSize int64 `json:"fileSize" gorm:"comment:文件大小(字节)"`
|
||||
StoragePath string `json:"storagePath" gorm:"type:varchar(1024);not null;comment:存储路径"`
|
||||
URL string `json:"url" gorm:"type:varchar(1024);comment:对象存储URL"`
|
||||
RelatedTo datatypes.JSON `json:"relatedTo" gorm:"type:jsonb;comment:关联对象"`
|
||||
Metadata datatypes.JSON `json:"metadata" gorm:"type:jsonb;comment:元数据"`
|
||||
}
|
||||
|
||||
func (AIFile) TableName() string {
|
||||
return "ai_files"
|
||||
}
|
||||
26
server/model/app/ai_memory.go
Normal file
26
server/model/app/ai_memory.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"github.com/pgvector/pgvector-go"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIMemoryVector 向量记忆表(使用 pgvector)
|
||||
type AIMemoryVector struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"not null;index;comment:所属用户ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
CharacterID *uint `json:"characterId" gorm:"index;comment:所属角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
ChatID *uint `json:"chatId" gorm:"index;comment:所属对话ID"`
|
||||
Chat *AIChat `json:"chat" gorm:"foreignKey:ChatID"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:文本内容"`
|
||||
Embedding pgvector.Vector `json:"-" gorm:"type:vector(1536);comment:向量嵌入"`
|
||||
Metadata datatypes.JSON `json:"metadata" gorm:"type:jsonb;comment:元数据"`
|
||||
Importance float64 `json:"importance" gorm:"default:0.5;comment:重要性评分"`
|
||||
}
|
||||
|
||||
func (AIMemoryVector) TableName() string {
|
||||
return "ai_memory_vectors"
|
||||
}
|
||||
46
server/model/app/ai_message.go
Normal file
46
server/model/app/ai_message.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIMessage 消息表
|
||||
type AIMessage struct {
|
||||
global.GVA_MODEL
|
||||
ChatID uint `json:"chatId" gorm:"not null;index:idx_chat_sequence;comment:所属对话ID"`
|
||||
Chat *AIChat `json:"chat" gorm:"foreignKey:ChatID"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:消息内容"`
|
||||
Role string `json:"role" gorm:"type:varchar(50);not null;comment:发送者类型"`
|
||||
SenderID *uint `json:"senderId" gorm:"comment:发送者ID"`
|
||||
Sender *AppUser `json:"sender" gorm:"foreignKey:SenderID"`
|
||||
CharacterID *uint `json:"characterId" gorm:"comment:AI角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
SequenceNumber int `json:"sequenceNumber" gorm:"not null;index:idx_chat_sequence;comment:消息序号"`
|
||||
Model string `json:"model" gorm:"type:varchar(200);comment:AI模型"`
|
||||
PromptTokens int `json:"promptTokens" gorm:"default:0;comment:提示词Token数"`
|
||||
CompletionTokens int `json:"completionTokens" gorm:"default:0;comment:补全Token数"`
|
||||
TotalTokens int `json:"totalTokens" gorm:"default:0;comment:总Token数"`
|
||||
GenerationParams datatypes.JSON `json:"generationParams" gorm:"type:jsonb;comment:生成参数"`
|
||||
Metadata datatypes.JSON `json:"metadata" gorm:"type:jsonb;comment:消息元数据"`
|
||||
IsDeleted bool `json:"isDeleted" gorm:"default:false;comment:是否被删除"`
|
||||
}
|
||||
|
||||
func (AIMessage) TableName() string {
|
||||
return "ai_messages"
|
||||
}
|
||||
|
||||
// AIMessageSwipe 消息变体表(swipe 功能)
|
||||
type AIMessageSwipe struct {
|
||||
global.GVA_MODEL
|
||||
MessageID uint `json:"messageId" gorm:"not null;index:idx_message_swipe,unique;comment:消息ID"`
|
||||
Message *AIMessage `json:"message" gorm:"foreignKey:MessageID"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:变体内容"`
|
||||
SwipeIndex int `json:"swipeIndex" gorm:"not null;index:idx_message_swipe,unique;comment:变体序号"`
|
||||
IsActive bool `json:"isActive" gorm:"default:false;comment:是否为当前选中"`
|
||||
GenerationParams datatypes.JSON `json:"generationParams" gorm:"type:jsonb;comment:生成参数"`
|
||||
}
|
||||
|
||||
func (AIMessageSwipe) TableName() string {
|
||||
return "ai_message_swipes"
|
||||
}
|
||||
22
server/model/app/ai_preset.go
Normal file
22
server/model/app/ai_preset.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIPreset 对话预设表
|
||||
type AIPreset struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"type:varchar(200);not null;comment:预设名称"`
|
||||
UserID *uint `json:"userId" gorm:"index;comment:所属用户ID(NULL表示系统预设)"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
PresetType string `json:"presetType" gorm:"type:varchar(100);not null;index;comment:预设类型"`
|
||||
Config datatypes.JSON `json:"config" gorm:"type:jsonb;not null;comment:预设配置"`
|
||||
IsSystem bool `json:"isSystem" gorm:"default:false;comment:是否为系统预设"`
|
||||
IsDefault bool `json:"isDefault" gorm:"default:false;comment:是否为默认预设"`
|
||||
}
|
||||
|
||||
func (AIPreset) TableName() string {
|
||||
return "ai_presets"
|
||||
}
|
||||
36
server/model/app/ai_provider.go
Normal file
36
server/model/app/ai_provider.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIProvider AI 服务提供商配置
|
||||
type AIProvider struct {
|
||||
global.GVA_MODEL
|
||||
UserID *uint `json:"userId" gorm:"index;comment:用户ID(NULL表示系统配置)"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
ProviderName string `json:"providerName" gorm:"type:varchar(100);not null;index;comment:提供商名称"`
|
||||
APIConfig datatypes.JSON `json:"apiConfig" gorm:"type:jsonb;not null;comment:API配置(加密存储)"`
|
||||
IsEnabled bool `json:"isEnabled" gorm:"default:true;comment:是否启用"`
|
||||
IsDefault bool `json:"isDefault" gorm:"default:false;comment:是否为默认提供商"`
|
||||
}
|
||||
|
||||
func (AIProvider) TableName() string {
|
||||
return "ai_providers"
|
||||
}
|
||||
|
||||
// AIModel AI 模型配置
|
||||
type AIModel struct {
|
||||
global.GVA_MODEL
|
||||
ProviderID uint `json:"providerId" gorm:"not null;index;comment:提供商ID"`
|
||||
Provider *AIProvider `json:"provider" gorm:"foreignKey:ProviderID"`
|
||||
ModelName string `json:"modelName" gorm:"type:varchar(200);not null;comment:模型名称"`
|
||||
DisplayName string `json:"displayName" gorm:"type:varchar(200);comment:模型显示名称"`
|
||||
Config datatypes.JSON `json:"config" gorm:"type:jsonb;comment:模型参数配置"`
|
||||
IsEnabled bool `json:"isEnabled" gorm:"default:true;comment:是否启用"`
|
||||
}
|
||||
|
||||
func (AIModel) TableName() string {
|
||||
return "ai_models"
|
||||
}
|
||||
25
server/model/app/ai_usage_stat.go
Normal file
25
server/model/app/ai_usage_stat.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AIUsageStat AI 使用统计表
|
||||
type AIUsageStat struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"not null;index:idx_user_stat,unique;comment:用户ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
StatDate time.Time `json:"statDate" gorm:"type:date;not null;index:idx_user_stat,unique;comment:统计日期"`
|
||||
ProviderName string `json:"providerName" gorm:"type:varchar(100);index:idx_user_stat,unique;comment:AI提供商"`
|
||||
ModelName string `json:"modelName" gorm:"type:varchar(200);index:idx_user_stat,unique;comment:模型名称"`
|
||||
RequestCount int `json:"requestCount" gorm:"default:0;comment:请求次数"`
|
||||
TotalTokens int64 `json:"totalTokens" gorm:"default:0;comment:总Token使用量"`
|
||||
PromptTokens int64 `json:"promptTokens" gorm:"default:0;comment:提示词Token使用量"`
|
||||
CompletionTokens int64 `json:"completionTokens" gorm:"default:0;comment:补全Token使用量"`
|
||||
Cost float64 `json:"cost" gorm:"type:decimal(10,4);default:0;comment:费用"`
|
||||
}
|
||||
|
||||
func (AIUsageStat) TableName() string {
|
||||
return "ai_usage_stats"
|
||||
}
|
||||
26
server/model/app/ai_world_info.go
Normal file
26
server/model/app/ai_world_info.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"github.com/lib/pq"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// AIWorldInfo 世界书(World Info)表
|
||||
type AIWorldInfo struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"not null;index;comment:所属用户ID"`
|
||||
User *AppUser `json:"user" gorm:"foreignKey:UserID"`
|
||||
CharacterID *uint `json:"characterId" gorm:"index;comment:关联角色ID"`
|
||||
Character *AICharacter `json:"character" gorm:"foreignKey:CharacterID"`
|
||||
Name string `json:"name" gorm:"type:varchar(500);not null;comment:世界书名称"`
|
||||
Keywords pq.StringArray `json:"keywords" gorm:"type:text[];comment:触发关键词"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:内容"`
|
||||
Priority int `json:"priority" gorm:"default:0;comment:优先级"`
|
||||
IsEnabled bool `json:"isEnabled" gorm:"default:true;comment:是否启用"`
|
||||
TriggerConfig datatypes.JSON `json:"triggerConfig" gorm:"type:jsonb;comment:触发条件配置"`
|
||||
}
|
||||
|
||||
func (AIWorldInfo) TableName() string {
|
||||
return "ai_world_info"
|
||||
}
|
||||
31
server/model/app/app_user.go
Normal file
31
server/model/app/app_user.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppUser 前台用户模型(与 sys_users 独立)
|
||||
type AppUser struct {
|
||||
global.GVA_MODEL
|
||||
UUID string `json:"uuid" gorm:"type:uuid;uniqueIndex;comment:用户UUID"`
|
||||
Username string `json:"username" gorm:"uniqueIndex;comment:用户登录名"`
|
||||
Password string `json:"-" gorm:"comment:用户登录密码"`
|
||||
NickName string `json:"nickName" gorm:"comment:用户昵称"`
|
||||
Email string `json:"email" gorm:"index;comment:用户邮箱"`
|
||||
Phone string `json:"phone" gorm:"comment:用户手机号"`
|
||||
Avatar string `json:"avatar" gorm:"type:varchar(1024);comment:用户头像"`
|
||||
Status string `json:"status" gorm:"type:varchar(50);default:active;comment:账户状态"`
|
||||
Enable bool `json:"enable" gorm:"default:true;comment:用户是否启用"`
|
||||
LastLoginAt *time.Time `json:"lastLoginAt" gorm:"comment:最后登录时间"`
|
||||
LastLoginIP string `json:"lastLoginIp" gorm:"type:varchar(100);comment:最后登录IP"`
|
||||
AISettings datatypes.JSON `json:"aiSettings" gorm:"type:jsonb;comment:AI配置"`
|
||||
Preferences datatypes.JSON `json:"preferences" gorm:"type:jsonb;comment:用户偏好"`
|
||||
ChatCount int `json:"chatCount" gorm:"default:0;comment:对话数量"`
|
||||
MessageCount int `json:"messageCount" gorm:"default:0;comment:消息数量"`
|
||||
}
|
||||
|
||||
func (AppUser) TableName() string {
|
||||
return "app_users"
|
||||
}
|
||||
24
server/model/app/app_user_session.go
Normal file
24
server/model/app/app_user_session.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/global"
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppUserSession 前台用户会话
|
||||
type AppUserSession struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"index;comment:用户ID"`
|
||||
SessionToken string `json:"sessionToken" gorm:"type:varchar(500);uniqueIndex;comment:会话Token"`
|
||||
RefreshToken string `json:"refreshToken" gorm:"type:varchar(500);comment:刷新Token"`
|
||||
ExpiresAt time.Time `json:"expiresAt" gorm:"index;comment:过期时间"`
|
||||
RefreshExpiresAt *time.Time `json:"refreshExpiresAt" gorm:"comment:刷新Token过期时间"`
|
||||
IPAddress string `json:"ipAddress" gorm:"type:varchar(100);comment:IP地址"`
|
||||
UserAgent string `json:"userAgent" gorm:"type:text;comment:用户代理"`
|
||||
DeviceInfo datatypes.JSON `json:"deviceInfo" gorm:"type:jsonb;comment:设备信息"`
|
||||
}
|
||||
|
||||
func (AppUserSession) TableName() string {
|
||||
return "app_user_sessions"
|
||||
}
|
||||
37
server/model/app/request/auth.go
Normal file
37
server/model/app/request/auth.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package request
|
||||
|
||||
// RegisterRequest 用户注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=32"`
|
||||
Password string `json:"password" binding:"required,min=6,max=32"`
|
||||
NickName string `json:"nickName" binding:"max=50"`
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Phone string `json:"phone" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// LoginRequest 用户登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// RefreshTokenRequest 刷新 Token 请求
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refreshToken" binding:"required"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"oldPassword" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required,min=6,max=32"`
|
||||
}
|
||||
|
||||
// UpdateProfileRequest 更新用户信息请求
|
||||
type UpdateProfileRequest struct {
|
||||
NickName string `json:"nickName" binding:"max=50"`
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Preferences string `json:"preferences"` // JSON 字符串
|
||||
AISettings string `json:"aiSettings"` // JSON 字符串
|
||||
}
|
||||
54
server/model/app/request/character.go
Normal file
54
server/model/app/request/character.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package request
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
// CreateCharacterRequest 创建角色卡请求
|
||||
type CreateCharacterRequest struct {
|
||||
Name string `json:"name" binding:"required,min=1,max=500"`
|
||||
Description string `json:"description"`
|
||||
Personality string `json:"personality"`
|
||||
Scenario string `json:"scenario"`
|
||||
Avatar string `json:"avatar"`
|
||||
CreatorName string `json:"creatorName"`
|
||||
CreatorNotes string `json:"creatorNotes"`
|
||||
FirstMessage string `json:"firstMessage"`
|
||||
ExampleMessages []string `json:"exampleMessages"`
|
||||
Tags []string `json:"tags"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
// UpdateCharacterRequest 更新角色卡请求
|
||||
type UpdateCharacterRequest struct {
|
||||
ID uint `json:"id" binding:"required"`
|
||||
Name string `json:"name" binding:"required,min=1,max=500"`
|
||||
Description string `json:"description"`
|
||||
Personality string `json:"personality"`
|
||||
Scenario string `json:"scenario"`
|
||||
Avatar string `json:"avatar"`
|
||||
CreatorName string `json:"creatorName"`
|
||||
CreatorNotes string `json:"creatorNotes"`
|
||||
FirstMessage string `json:"firstMessage"`
|
||||
ExampleMessages []string `json:"exampleMessages"`
|
||||
Tags []string `json:"tags"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
// CharacterListRequest 角色卡列表请求
|
||||
type CharacterListRequest struct {
|
||||
Page int `form:"page" binding:"min=1"`
|
||||
PageSize int `form:"pageSize" binding:"min=1,max=100"`
|
||||
Keyword string `form:"keyword"`
|
||||
Tags []string `form:"tags"`
|
||||
SortBy string `form:"sortBy"` // newest, popular, mostChats, mostLikes
|
||||
}
|
||||
|
||||
// ImportCharacterRequest 导入角色卡请求
|
||||
type ImportCharacterRequest struct {
|
||||
File *multipart.FileHeader `form:"file" binding:"required"`
|
||||
IsPublic bool `form:"isPublic"`
|
||||
}
|
||||
|
||||
// CharacterActionRequest 角色卡操作请求(点赞、收藏等)
|
||||
type CharacterActionRequest struct {
|
||||
CharacterID uint `json:"characterId" binding:"required"`
|
||||
}
|
||||
54
server/model/app/response/auth.go
Normal file
54
server/model/app/response/auth.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
User AppUserResponse `json:"user"`
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresAt int64 `json:"expiresAt"` // Unix 时间戳
|
||||
}
|
||||
|
||||
// AppUserResponse 用户信息响应(不包含密码)
|
||||
type AppUserResponse struct {
|
||||
ID uint `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
Enable bool `json:"enable"`
|
||||
LastLoginAt *time.Time `json:"lastLoginAt"`
|
||||
ChatCount int `json:"chatCount"`
|
||||
MessageCount int `json:"messageCount"`
|
||||
AISettings interface{} `json:"aiSettings"`
|
||||
Preferences interface{} `json:"preferences"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ToAppUserResponse 将 AppUser 转换为 AppUserResponse
|
||||
func ToAppUserResponse(user *app.AppUser) AppUserResponse {
|
||||
return AppUserResponse{
|
||||
ID: user.ID,
|
||||
UUID: user.UUID,
|
||||
Username: user.Username,
|
||||
NickName: user.NickName,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Avatar: user.Avatar,
|
||||
Status: user.Status,
|
||||
Enable: user.Enable,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
ChatCount: user.ChatCount,
|
||||
MessageCount: user.MessageCount,
|
||||
AISettings: user.AISettings,
|
||||
Preferences: user.Preferences,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
}
|
||||
79
server/model/app/response/character.go
Normal file
79
server/model/app/response/character.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/st/server/model/app"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CharacterResponse 角色卡响应
|
||||
type CharacterResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Personality string `json:"personality"`
|
||||
Scenario string `json:"scenario"`
|
||||
Avatar string `json:"avatar"`
|
||||
CreatorID *uint `json:"creatorId"`
|
||||
CreatorName string `json:"creatorName"`
|
||||
CreatorNotes string `json:"creatorNotes"`
|
||||
Tags []string `json:"tags"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
Version int `json:"version"`
|
||||
FirstMessage string `json:"firstMessage"`
|
||||
ExampleMessages []string `json:"exampleMessages"`
|
||||
TotalChats int `json:"totalChats"`
|
||||
TotalLikes int `json:"totalLikes"`
|
||||
UsageCount int `json:"usageCount"`
|
||||
FavoriteCount int `json:"favoriteCount"`
|
||||
TokenCount int `json:"tokenCount"`
|
||||
IsFavorited bool `json:"isFavorited"` // 当前用户是否收藏
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// CharacterListResponse 角色卡列表响应
|
||||
type CharacterListResponse struct {
|
||||
List []CharacterResponse `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// ToCharacterResponse 转换为角色卡响应
|
||||
func ToCharacterResponse(character *app.AICharacter, isFavorited bool) CharacterResponse {
|
||||
// pq.StringArray 可以直接赋值给 []string
|
||||
tags := []string{}
|
||||
if character.Tags != nil {
|
||||
tags = character.Tags
|
||||
}
|
||||
|
||||
exampleMessages := []string{}
|
||||
if character.ExampleMessages != nil {
|
||||
exampleMessages = character.ExampleMessages
|
||||
}
|
||||
|
||||
return CharacterResponse{
|
||||
ID: character.ID,
|
||||
Name: character.Name,
|
||||
Description: character.Description,
|
||||
Personality: character.Personality,
|
||||
Scenario: character.Scenario,
|
||||
Avatar: character.Avatar,
|
||||
CreatorID: character.CreatorID,
|
||||
CreatorName: character.CreatorName,
|
||||
CreatorNotes: character.CreatorNotes,
|
||||
Tags: tags,
|
||||
IsPublic: character.IsPublic,
|
||||
Version: character.Version,
|
||||
FirstMessage: character.FirstMessage,
|
||||
ExampleMessages: exampleMessages,
|
||||
TotalChats: character.TotalChats,
|
||||
TotalLikes: character.TotalLikes,
|
||||
UsageCount: character.UsageCount,
|
||||
FavoriteCount: character.FavoriteCount,
|
||||
TokenCount: character.TokenCount,
|
||||
IsFavorited: isFavorited,
|
||||
CreatedAt: character.CreatedAt,
|
||||
UpdatedAt: character.UpdatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user