🎉 初始化项目
This commit is contained in:
230
server/model/app/README.md
Normal file
230
server/model/app/README.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# AI 中转代理系统 - App 模块
|
||||
|
||||
## 已完成的功能模块
|
||||
|
||||
按照 server 目录的标准结构,已将 AI 中转代理项目的代码编写到各个 `app` 目录下:
|
||||
|
||||
### 1. Model 层 (server/model/app/)
|
||||
|
||||
**核心模型:**
|
||||
- `ai_preset.go` - AI 预设模板模型(支持 SillyTavern 格式)
|
||||
- `ai_provider.go` - AI 服务提供商模型
|
||||
- `ai_preset_binding.go` - 用户-预设-提供商绑定模型
|
||||
- `ai_request_log.go` - AI 请求日志模型
|
||||
- `ai_preset_types.go` - 预设相关的自定义类型
|
||||
|
||||
**Request 结构体 (server/model/app/request/):**
|
||||
- `ai_preset.go` - 预设相关请求(创建、更新、导入)
|
||||
- `ai_provider.go` - 提供商相关请求(创建、更新)
|
||||
- `ai_proxy.go` - 代理请求(聊天补全、消息、角色卡片)
|
||||
|
||||
**Response 结构体 (server/model/app/response/):**
|
||||
- `ai_proxy.go` - 代理响应(OpenAI 兼容格式)
|
||||
|
||||
### 2. Service 层 (server/service/app/)
|
||||
|
||||
- `ai_preset.go` - 预设服务(CRUD、导入导出)
|
||||
- `ai_provider.go` - 提供商服务(CRUD、连接测试)
|
||||
- `ai_proxy.go` - 代理服务(核心预设注入引擎、转发到上游 AI)
|
||||
- `enter.go` - Service 统一入口
|
||||
|
||||
### 3. API 层 (server/api/v1/app/)
|
||||
|
||||
- `ai_preset.go` - 预设管理 API
|
||||
- `ai_provider.go` - 提供商管理 API
|
||||
- `ai_proxy.go` - 代理转发 API(OpenAI 兼容接口)
|
||||
- `enter.go` - API 统一入口
|
||||
|
||||
### 4. Router 层 (server/router/app/)
|
||||
|
||||
- `ai_preset.go` - 预设路由
|
||||
- `ai_provider.go` - 提供商路由
|
||||
- `ai_proxy.go` - 代理路由
|
||||
- `enter.go` - Router 统一入口
|
||||
|
||||
## 核心功能特性
|
||||
|
||||
### 1. AI 预设管理
|
||||
- ✅ 创建、更新、删除预设
|
||||
- ✅ 获取预设列表和详情
|
||||
- ✅ 导入 SillyTavern 格式预设
|
||||
- ✅ 导出预设为 JSON
|
||||
- ✅ 支持公开/私有预设
|
||||
- ✅ 完整的 Prompt 注入配置
|
||||
- ✅ 正则脚本处理
|
||||
|
||||
### 2. AI 服务提供商管理
|
||||
- ✅ 创建、更新、删除提供商
|
||||
- ✅ 获取提供商列表和详情
|
||||
- ✅ 支持自定义配置
|
||||
- ✅ 启用/禁用状态管理
|
||||
|
||||
### 3. AI 代理服务
|
||||
- ✅ OpenAI 兼容的聊天补全接口
|
||||
- ✅ 预设注入引擎
|
||||
- ✅ 变量替换({{user}}, {{char}})
|
||||
- ✅ 正则脚本处理(输入/输出)
|
||||
- ✅ 转发到上游 AI 服务
|
||||
- ✅ 请求日志记录
|
||||
- ⏳ 流式响应(待实现)
|
||||
|
||||
## API 端点
|
||||
|
||||
### 预设管理
|
||||
```
|
||||
POST /app/preset # 创建预设
|
||||
PUT /app/preset # 更新预设
|
||||
DELETE /app/preset/:id # 删除预设
|
||||
GET /app/preset/:id # 获取预设详情
|
||||
GET /app/preset/list # 获取预设列表
|
||||
POST /app/preset/import # 导入预设
|
||||
GET /app/preset/:id/export # 导出预设
|
||||
```
|
||||
|
||||
### 提供商管理
|
||||
```
|
||||
POST /app/provider # 创建提供商
|
||||
PUT /app/provider # 更新提供商
|
||||
DELETE /app/provider/:id # 删除提供商
|
||||
GET /app/provider/:id # 获取提供商详情
|
||||
GET /app/provider/list # 获取提供商列表
|
||||
```
|
||||
|
||||
### 代理接口(OpenAI 兼容)
|
||||
```
|
||||
POST /v1/chat/completions # 聊天补全
|
||||
```
|
||||
|
||||
## 数据模型说明
|
||||
|
||||
### AiPreset(预设模板)
|
||||
```go
|
||||
- UserID: 用户ID
|
||||
- Name: 预设名称
|
||||
- Description: 预设描述
|
||||
- Prompts: 提示词数组(支持 SillyTavern 格式)
|
||||
- RegexScripts: 正则脚本数组
|
||||
- Temperature, TopP, MaxTokens: 模型参数
|
||||
- StreamEnabled: 是否启用流式
|
||||
- IsDefault: 是否默认
|
||||
- IsPublic: 是否公开
|
||||
```
|
||||
|
||||
### Prompt(提示词)
|
||||
```go
|
||||
- Name: 提示词名称
|
||||
- Role: 角色(system/user/assistant)
|
||||
- Content: 提示词内容
|
||||
- Identifier: 标识符
|
||||
- InjectionPosition: 注入位置
|
||||
- InjectionDepth: 注入深度
|
||||
- InjectionOrder: 注入顺序
|
||||
- Marker: 是否为占位符
|
||||
```
|
||||
|
||||
### RegexScript(正则脚本)
|
||||
```go
|
||||
- ScriptName: 脚本名称
|
||||
- FindRegex: 查找正则
|
||||
- ReplaceString: 替换字符串
|
||||
- Placement: 应用位置(1=输入, 2=输出)
|
||||
- MinDepth, MaxDepth: 深度限制
|
||||
```
|
||||
|
||||
## 预设注入流程
|
||||
|
||||
1. 接收用户请求
|
||||
2. 加载预设配置
|
||||
3. 按 injection_order 排序 prompts
|
||||
4. 根据 injection_depth 插入到对话历史中
|
||||
5. 替换变量({{user}}, {{char}})
|
||||
6. 应用正则脚本(placement=1 处理输入)
|
||||
7. 转发到上游 AI
|
||||
8. 应用正则脚本(placement=2 处理输出)
|
||||
9. 记录日志
|
||||
10. 返回响应
|
||||
|
||||
## 待完善功能
|
||||
|
||||
1. **完整的预设注入引擎**
|
||||
- 完整实现 injection_depth 逻辑
|
||||
- 完整实现正则脚本处理
|
||||
- 支持更多变量替换
|
||||
|
||||
2. **流式响应**
|
||||
- 实现 SSE 流式输出
|
||||
- 支持流式日志记录
|
||||
|
||||
3. **预设绑定管理**
|
||||
- 实现 binding_key 机制
|
||||
- 支持多预设切换
|
||||
|
||||
4. **导入导出**
|
||||
- 完善 SillyTavern JSON 解析
|
||||
- 支持批量导入导出
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 创建 AI 提供商
|
||||
```bash
|
||||
POST /app/provider
|
||||
{
|
||||
"name": "OpenAI",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "sk-xxx",
|
||||
"model": "gpt-4",
|
||||
"isActive": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 导入 SillyTavern 预设
|
||||
```bash
|
||||
POST /app/preset/import
|
||||
{
|
||||
"name": "TG角色扮演",
|
||||
"data": { ... } // TGbreak.json 内容
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 发送聊天请求
|
||||
```bash
|
||||
POST /v1/chat/completions
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "content": "你好"}
|
||||
],
|
||||
"presetId": 1,
|
||||
"characterCard": {
|
||||
"name": "小美",
|
||||
"description": "活泼的女孩"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
server/
|
||||
├── model/app/ # 数据模型
|
||||
│ ├── ai_preset.go
|
||||
│ ├── ai_provider.go
|
||||
│ ├── ai_preset_binding.go
|
||||
│ ├── ai_request_log.go
|
||||
│ ├── request/ # 请求结构体
|
||||
│ └── response/ # 响应结构体
|
||||
├── service/app/ # 业务逻辑
|
||||
│ ├── ai_preset.go
|
||||
│ ├── ai_provider.go
|
||||
│ ├── ai_proxy.go
|
||||
│ └── enter.go
|
||||
├── api/v1/app/ # API 处理器
|
||||
│ ├── ai_preset.go
|
||||
│ ├── ai_provider.go
|
||||
│ ├── ai_proxy.go
|
||||
│ └── enter.go
|
||||
└── router/app/ # 路由注册
|
||||
├── ai_preset.go
|
||||
├── ai_provider.go
|
||||
├── ai_proxy.go
|
||||
└── enter.go
|
||||
```
|
||||
59
server/model/app/ai_preset.go
Normal file
59
server/model/app/ai_preset.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
)
|
||||
|
||||
// AiPreset AI预设模板
|
||||
type AiPreset struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" gorm:"comment:用户ID;index"`
|
||||
Name string `json:"name" gorm:"comment:预设名称;size:200;not null"`
|
||||
Description string `json:"description" gorm:"comment:预设描述;type:text"`
|
||||
Prompts Prompts `json:"prompts" gorm:"comment:提示词数组;type:jsonb;not null"`
|
||||
RegexScripts RegexScripts `json:"regexScripts" gorm:"comment:正则脚本;type:jsonb"`
|
||||
Temperature float64 `json:"temperature" gorm:"comment:温度;default:1.0"`
|
||||
TopP float64 `json:"topP" gorm:"comment:TopP;default:0.9"`
|
||||
MaxTokens int `json:"maxTokens" gorm:"comment:最大Token数;default:4096"`
|
||||
FrequencyPenalty float64 `json:"frequencyPenalty" gorm:"comment:频率惩罚;default:0"`
|
||||
PresencePenalty float64 `json:"presencePenalty" gorm:"comment:存在惩罚;default:0"`
|
||||
StreamEnabled bool `json:"streamEnabled" gorm:"comment:是否启用流式;default:true"`
|
||||
IsDefault bool `json:"isDefault" gorm:"comment:是否默认;default:false"`
|
||||
IsPublic bool `json:"isPublic" gorm:"comment:是否公开;default:false"`
|
||||
}
|
||||
|
||||
func (AiPreset) TableName() string {
|
||||
return "ai_presets"
|
||||
}
|
||||
|
||||
// Prompt 提示词结构
|
||||
type Prompt struct {
|
||||
Name string `json:"name"`
|
||||
SystemPrompt bool `json:"system_prompt"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Identifier string `json:"identifier"`
|
||||
InjectionPosition int `json:"injection_position"`
|
||||
InjectionDepth int `json:"injection_depth"`
|
||||
InjectionOrder int `json:"injection_order"`
|
||||
InjectionTrigger []string `json:"injection_trigger"`
|
||||
Marker bool `json:"marker"`
|
||||
ForbidOverrides bool `json:"forbid_overrides"`
|
||||
}
|
||||
|
||||
// RegexScript 正则脚本
|
||||
type RegexScript struct {
|
||||
ID string `json:"id"`
|
||||
ScriptName string `json:"scriptName"`
|
||||
FindRegex string `json:"findRegex"`
|
||||
ReplaceString string `json:"replaceString"`
|
||||
TrimStrings []string `json:"trimStrings"`
|
||||
Placement []int `json:"placement"`
|
||||
Disabled bool `json:"disabled"`
|
||||
MarkdownOnly bool `json:"markdownOnly"`
|
||||
PromptOnly bool `json:"promptOnly"`
|
||||
RunOnEdit bool `json:"runOnEdit"`
|
||||
SubstituteRegex int `json:"substituteRegex"`
|
||||
MinDepth *int `json:"minDepth"`
|
||||
MaxDepth *int `json:"maxDepth"`
|
||||
}
|
||||
22
server/model/app/ai_preset_binding.go
Normal file
22
server/model/app/ai_preset_binding.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
)
|
||||
|
||||
// AiPresetBinding 预设-提供商绑定关系
|
||||
type AiPresetBinding struct {
|
||||
global.GVA_MODEL
|
||||
PresetID uint `json:"presetId" gorm:"comment:预设ID;index"`
|
||||
ProviderID uint `json:"providerId" gorm:"comment:提供商ID;index"`
|
||||
Priority int `json:"priority" gorm:"comment:优先级;default:0"` // 多个预设时的执行顺序
|
||||
IsActive bool `json:"isActive" gorm:"comment:是否启用;default:true"`
|
||||
|
||||
// 关联
|
||||
Preset AiPreset `json:"preset" gorm:"foreignKey:PresetID"`
|
||||
Provider AiProvider `json:"provider" gorm:"foreignKey:ProviderID"`
|
||||
}
|
||||
|
||||
func (AiPresetBinding) TableName() string {
|
||||
return "ai_preset_bindings"
|
||||
}
|
||||
36
server/model/app/ai_preset_types.go
Normal file
36
server/model/app/ai_preset_types.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Prompts 提示词数组类型
|
||||
type Prompts []Prompt
|
||||
|
||||
func (p Prompts) Value() (driver.Value, error) {
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
func (p *Prompts) Scan(value interface{}) error {
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, p)
|
||||
}
|
||||
|
||||
// RegexScripts 正则脚本数组类型
|
||||
type RegexScripts []RegexScript
|
||||
|
||||
func (r RegexScripts) Value() (driver.Value, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func (r *RegexScripts) Scan(value interface{}) error {
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, r)
|
||||
}
|
||||
23
server/model/app/ai_provider.go
Normal file
23
server/model/app/ai_provider.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
)
|
||||
|
||||
// AiProvider AI服务提供商配置
|
||||
type AiProvider struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"comment:提供商名称;size:100;not null;uniqueIndex"`
|
||||
Type string `json:"type" gorm:"comment:提供商类型;size:50;not null"` // openai, claude, gemini 等
|
||||
BaseURL string `json:"baseUrl" gorm:"comment:API基础地址;size:500;not null"`
|
||||
Endpoint string `json:"endpoint" gorm:"comment:API端点;size:200"` // 如 /v1/chat/completions
|
||||
UpstreamKey string `json:"upstreamKey" gorm:"comment:上游API密钥;type:text"` // 上游 AI 的 key
|
||||
Model string `json:"model" gorm:"comment:默认模型;size:100"`
|
||||
ProxyKey string `json:"proxyKey" gorm:"comment:代理访问密钥;size:100;uniqueIndex"` // 用户访问本代理时使用的 key
|
||||
Config map[string]interface{} `json:"config" gorm:"comment:额外配置;type:jsonb;serializer:json"`
|
||||
IsActive bool `json:"isActive" gorm:"comment:是否启用;default:true"`
|
||||
}
|
||||
|
||||
func (AiProvider) TableName() string {
|
||||
return "ai_providers"
|
||||
}
|
||||
26
server/model/app/ai_request_log.go
Normal file
26
server/model/app/ai_request_log.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
)
|
||||
|
||||
// AiRequestLog AI请求日志
|
||||
type AiRequestLog struct {
|
||||
global.GVA_MODEL
|
||||
UserID *uint `json:"userId" gorm:"comment:用户ID;index"`
|
||||
PresetID *uint `json:"presetId" gorm:"comment:预设ID"`
|
||||
ProviderID *uint `json:"providerId" gorm:"comment:提供商ID"`
|
||||
OriginalMessage string `json:"originalMessage" gorm:"comment:原始消息;type:text"`
|
||||
InjectedPrompt map[string]interface{} `json:"injectedPrompt" gorm:"comment:注入后的提示词;type:jsonb;serializer:json"`
|
||||
ResponseText string `json:"responseText" gorm:"comment:响应文本;type:text"`
|
||||
ResponseTokens int `json:"responseTokens" gorm:"comment:响应Token数"`
|
||||
PromptTokens int `json:"promptTokens" gorm:"comment:提示Token数"`
|
||||
TotalTokens int `json:"totalTokens" gorm:"comment:总Token数"`
|
||||
LatencyMs int `json:"latencyMs" gorm:"comment:延迟(毫秒)"`
|
||||
Status string `json:"status" gorm:"comment:状态;size:20"` // success/error/timeout
|
||||
ErrorMessage string `json:"errorMessage" gorm:"comment:错误信息;type:text"`
|
||||
}
|
||||
|
||||
func (AiRequestLog) TableName() string {
|
||||
return "ai_request_logs"
|
||||
}
|
||||
42
server/model/app/request/ai_preset.go
Normal file
42
server/model/app/request/ai_preset.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package request
|
||||
|
||||
import "git.echol.cn/loser/ai_proxy/server/model/app"
|
||||
|
||||
// CreateAiPresetRequest 创建预设请求
|
||||
type CreateAiPresetRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Prompts []app.Prompt `json:"prompts" binding:"required"`
|
||||
RegexScripts []app.RegexScript `json:"regexScripts"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"topP"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
FrequencyPenalty float64 `json:"frequencyPenalty"`
|
||||
PresencePenalty float64 `json:"presencePenalty"`
|
||||
StreamEnabled bool `json:"streamEnabled"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
// UpdateAiPresetRequest 更新预设请求
|
||||
type UpdateAiPresetRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Prompts []app.Prompt `json:"prompts"`
|
||||
RegexScripts []app.RegexScript `json:"regexScripts"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"topP"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
FrequencyPenalty float64 `json:"frequencyPenalty"`
|
||||
PresencePenalty float64 `json:"presencePenalty"`
|
||||
StreamEnabled bool `json:"streamEnabled"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
// ImportAiPresetRequest 导入预设请求(支持SillyTavern格式)
|
||||
type ImportAiPresetRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Data interface{} `json:"data" binding:"required"`
|
||||
}
|
||||
23
server/model/app/request/ai_preset_binding.go
Normal file
23
server/model/app/request/ai_preset_binding.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package request
|
||||
|
||||
// CreateBindingRequest 创建绑定请求
|
||||
type CreateBindingRequest struct {
|
||||
PresetID uint `json:"presetId" binding:"required"`
|
||||
ProviderID uint `json:"providerId" binding:"required"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// UpdateBindingRequest 更新绑定请求
|
||||
type UpdateBindingRequest struct {
|
||||
ID uint `json:"id" binding:"required"`
|
||||
Priority int `json:"priority"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
|
||||
// GetBindingListRequest 获取绑定列表请求
|
||||
type GetBindingListRequest struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
ProviderID uint `json:"providerId" form:"providerId"`
|
||||
PresetID uint `json:"presetId" form:"presetId"`
|
||||
}
|
||||
28
server/model/app/request/ai_provider.go
Normal file
28
server/model/app/request/ai_provider.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package request
|
||||
|
||||
// CreateAiProviderRequest 创建AI提供商请求
|
||||
type CreateAiProviderRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
BaseURL string `json:"baseUrl" binding:"required,url"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
UpstreamKey string `json:"upstreamKey" binding:"required"`
|
||||
Model string `json:"model"`
|
||||
ProxyKey string `json:"proxyKey" binding:"required"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
|
||||
// UpdateAiProviderRequest 更新AI提供商请求
|
||||
type UpdateAiProviderRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
UpstreamKey string `json:"upstreamKey"`
|
||||
Model string `json:"model"`
|
||||
ProxyKey string `json:"proxyKey"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
15
server/model/app/request/ai_provider_extra.go
Normal file
15
server/model/app/request/ai_provider_extra.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package request
|
||||
|
||||
// TestConnectionRequest 测试连接请求
|
||||
type TestConnectionRequest struct {
|
||||
BaseURL string `json:"baseUrl" binding:"required"`
|
||||
UpstreamKey string `json:"upstreamKey" binding:"required"`
|
||||
Type string `json:"type" binding:"required"` // openai, claude, gemini 等
|
||||
}
|
||||
|
||||
// GetModelsRequest 获取模型列表请求
|
||||
type GetModelsRequest struct {
|
||||
BaseURL string `json:"baseUrl" binding:"required"`
|
||||
UpstreamKey string `json:"upstreamKey" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
}
|
||||
31
server/model/app/request/ai_proxy.go
Normal file
31
server/model/app/request/ai_proxy.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package request
|
||||
|
||||
// Message 消息结构
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// CharacterCard 角色卡片
|
||||
type CharacterCard struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Personality string `json:"personality"`
|
||||
Scenario string `json:"scenario"`
|
||||
}
|
||||
|
||||
// ChatCompletionRequest 聊天补全请求(OpenAI兼容)
|
||||
type ChatCompletionRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages" binding:"required"`
|
||||
PresetID uint `json:"presetId"`
|
||||
BindingKey string `json:"bindingKey"`
|
||||
CharacterCard *CharacterCard `json:"characterCard"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
TopP *float64 `json:"topP"`
|
||||
MaxTokens *int `json:"maxTokens"`
|
||||
FrequencyPenalty *float64 `json:"frequencyPenalty"`
|
||||
PresencePenalty *float64 `json:"presencePenalty"`
|
||||
Stream bool `json:"stream"`
|
||||
}
|
||||
16
server/model/app/response/ai_preset_binding.go
Normal file
16
server/model/app/response/ai_preset_binding.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
// BindingInfo 绑定信息
|
||||
type BindingInfo struct {
|
||||
ID uint `json:"id"`
|
||||
PresetID uint `json:"presetId"`
|
||||
PresetName string `json:"presetName"`
|
||||
ProviderID uint `json:"providerId"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Priority int `json:"priority"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
15
server/model/app/response/ai_provider_extra.go
Normal file
15
server/model/app/response/ai_provider_extra.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package response
|
||||
|
||||
// ModelInfo 模型信息
|
||||
type ModelInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OwnedBy string `json:"ownedBy"`
|
||||
}
|
||||
|
||||
// TestConnectionResponse 测试连接响应
|
||||
type TestConnectionResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Latency int64 `json:"latency"` // 延迟(毫秒)
|
||||
}
|
||||
38
server/model/app/response/ai_proxy.go
Normal file
38
server/model/app/response/ai_proxy.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
// ChatCompletionResponse OpenAI兼容的聊天补全响应
|
||||
type ChatCompletionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
|
||||
// Choice 选择项
|
||||
type Choice struct {
|
||||
Index int `json:"index"`
|
||||
Message Message `json:"message"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
// Message 消息
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// Usage Token使用情况
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
// StreamChunk 流式响应块
|
||||
type StreamChunk struct {
|
||||
Content string `json:"content"`
|
||||
Done bool `json:"done"`
|
||||
Error error `json:"error,omitempty"`
|
||||
}
|
||||
43
server/model/common/basetypes.go
Normal file
43
server/model/common/basetypes.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type JSONMap map[string]interface{}
|
||||
|
||||
func (m JSONMap) Value() (driver.Value, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (m *JSONMap) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*m = make(map[string]interface{})
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
switch value.(type) {
|
||||
case []byte:
|
||||
err = json.Unmarshal(value.([]byte), m)
|
||||
case string:
|
||||
err = json.Unmarshal([]byte(value.(string)), m)
|
||||
default:
|
||||
err = errors.New("basetypes.JSONMap.Scan: invalid value type")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TreeNode[T any] interface {
|
||||
GetChildren() []T
|
||||
SetChildren(children T)
|
||||
GetID() int
|
||||
GetParentID() int
|
||||
}
|
||||
7
server/model/common/clearDB.go
Normal file
7
server/model/common/clearDB.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
type ClearDB struct {
|
||||
TableName string
|
||||
CompareField string
|
||||
Interval string
|
||||
}
|
||||
21
server/model/common/common.go
Normal file
21
server/model/common/common.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetAppUserID 从上下文获取前台用户 ID
|
||||
func GetAppUserID(c *gin.Context) uint {
|
||||
if userID, exists := c.Get("appUserId"); exists {
|
||||
return userID.(uint)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetAppUsername 从上下文获取前台用户名
|
||||
func GetAppUsername(c *gin.Context) string {
|
||||
if username, exists := c.Get("appUsername"); exists {
|
||||
return username.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
48
server/model/common/request/common.go
Normal file
48
server/model/common/request/common.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PageInfo Paging common input parameter structure
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" form:"page,default=1"` // 页码
|
||||
PageSize int `json:"pageSize" form:"pageSize,default=20"` // 每页大小
|
||||
Keyword string `json:"keyword" form:"keyword"` // 关键字
|
||||
}
|
||||
|
||||
func (r *PageInfo) Paginate() func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if r.Page <= 0 {
|
||||
r.Page = 1
|
||||
}
|
||||
switch {
|
||||
case r.PageSize > 100:
|
||||
r.PageSize = 100
|
||||
case r.PageSize <= 0:
|
||||
r.PageSize = 10
|
||||
}
|
||||
offset := (r.Page - 1) * r.PageSize
|
||||
return db.Offset(offset).Limit(r.PageSize)
|
||||
}
|
||||
}
|
||||
|
||||
// GetById Find by id structure
|
||||
type GetById struct {
|
||||
ID int `json:"id" form:"id"` // 主键ID
|
||||
}
|
||||
|
||||
func (r *GetById) Uint() uint {
|
||||
return uint(r.ID)
|
||||
}
|
||||
|
||||
type IdsReq struct {
|
||||
Ids []int `json:"ids" form:"ids"`
|
||||
}
|
||||
|
||||
// GetAuthorityId Get role by id structure
|
||||
type GetAuthorityId struct {
|
||||
AuthorityId uint `json:"authorityId" form:"authorityId"` // 角色ID
|
||||
}
|
||||
|
||||
type Empty struct{}
|
||||
8
server/model/common/response/common.go
Normal file
8
server/model/common/response/common.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package response
|
||||
|
||||
type PageResult struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
62
server/model/common/response/response.go
Normal file
62
server/model/common/response/response.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Data interface{} `json:"data"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
const (
|
||||
ERROR = 7
|
||||
SUCCESS = 0
|
||||
)
|
||||
|
||||
func Result(code int, data interface{}, msg string, c *gin.Context) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
code,
|
||||
data,
|
||||
msg,
|
||||
})
|
||||
}
|
||||
|
||||
func Ok(c *gin.Context) {
|
||||
Result(SUCCESS, map[string]interface{}{}, "操作成功", c)
|
||||
}
|
||||
|
||||
func OkWithMessage(message string, c *gin.Context) {
|
||||
Result(SUCCESS, map[string]interface{}{}, message, c)
|
||||
}
|
||||
|
||||
func OkWithData(data interface{}, c *gin.Context) {
|
||||
Result(SUCCESS, data, "成功", c)
|
||||
}
|
||||
|
||||
func OkWithDetailed(data interface{}, message string, c *gin.Context) {
|
||||
Result(SUCCESS, data, message, c)
|
||||
}
|
||||
|
||||
func Fail(c *gin.Context) {
|
||||
Result(ERROR, map[string]interface{}{}, "操作失败", c)
|
||||
}
|
||||
|
||||
func FailWithMessage(message string, c *gin.Context) {
|
||||
Result(ERROR, map[string]interface{}{}, message, c)
|
||||
}
|
||||
|
||||
func NoAuth(message string, c *gin.Context) {
|
||||
c.JSON(http.StatusUnauthorized, Response{
|
||||
7,
|
||||
nil,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
func FailWithDetailed(data interface{}, message string, c *gin.Context) {
|
||||
Result(ERROR, data, message, c)
|
||||
}
|
||||
22
server/model/system/request/jwt.go
Normal file
22
server/model/system/request/jwt.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BaseClaims 基础 Claims
|
||||
type BaseClaims struct {
|
||||
UUID uuid.UUID
|
||||
ID uint
|
||||
Username string
|
||||
NickName string
|
||||
AuthorityId uint
|
||||
}
|
||||
|
||||
// CustomClaims 自定义 Claims
|
||||
type CustomClaims struct {
|
||||
BaseClaims
|
||||
BufferTime int64
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
27
server/model/system/request/sys_api.go
Normal file
27
server/model/system/request/sys_api.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package request
|
||||
|
||||
// CreateApiRequest 创建API请求
|
||||
type CreateApiRequest struct {
|
||||
Path string `json:"path" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup" binding:"required"`
|
||||
Method string `json:"method" binding:"required,oneof=GET POST PUT DELETE PATCH"`
|
||||
}
|
||||
|
||||
// UpdateApiRequest 更新API请求
|
||||
type UpdateApiRequest struct {
|
||||
ID uint `json:"id" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup" binding:"required"`
|
||||
Method string `json:"method" binding:"required,oneof=GET POST PUT DELETE PATCH"`
|
||||
}
|
||||
|
||||
// GetApiListRequest 获取API列表请求
|
||||
type GetApiListRequest struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
Path string `json:"path" form:"path"`
|
||||
ApiGroup string `json:"apiGroup" form:"apiGroup"`
|
||||
Method string `json:"method" form:"method"`
|
||||
}
|
||||
22
server/model/system/request/sys_user.go
Normal file
22
server/model/system/request/sys_user.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package request
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Email string `json:"email" binding:"email"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
14
server/model/system/response/sys_api.go
Normal file
14
server/model/system/response/sys_api.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package response
|
||||
|
||||
import "time"
|
||||
|
||||
// ApiInfo API信息
|
||||
type ApiInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
17
server/model/system/response/sys_user.go
Normal file
17
server/model/system/response/sys_user.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package response
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
User UserInfo `json:"user"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
18
server/model/system/sys_api.go
Normal file
18
server/model/system/sys_api.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
)
|
||||
|
||||
// SysApi API管理
|
||||
type SysApi struct {
|
||||
global.GVA_MODEL
|
||||
Path string `json:"path" gorm:"comment:API路径;size:255;not null"`
|
||||
Description string `json:"description" gorm:"comment:API描述;size:500"`
|
||||
ApiGroup string `json:"apiGroup" gorm:"comment:API分组;size:100"`
|
||||
Method string `json:"method" gorm:"comment:请求方法;size:20;not null"` // GET/POST/PUT/DELETE
|
||||
}
|
||||
|
||||
func (SysApi) TableName() string {
|
||||
return "sys_apis"
|
||||
}
|
||||
56
server/model/system/sys_user.go
Normal file
56
server/model/system/sys_user.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/ai_proxy/server/global"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SysUser 系统用户
|
||||
type SysUser struct {
|
||||
global.GVA_MODEL
|
||||
Username string `json:"username" gorm:"comment:用户名;size:100;uniqueIndex;not null"`
|
||||
Password string `json:"-" gorm:"comment:密码;size:255;not null"`
|
||||
Nickname string `json:"nickname" gorm:"comment:昵称;size:100"`
|
||||
Email string `json:"email" gorm:"comment:邮箱;size:100"`
|
||||
Phone string `json:"phone" gorm:"comment:手机号;size:20"`
|
||||
Avatar string `json:"avatar" gorm:"comment:头像;size:500"`
|
||||
Role string `json:"role" gorm:"comment:角色;size:20;default:'user'"` // admin/user
|
||||
Status string `json:"status" gorm:"comment:状态;size:20;default:'active'"` // active/disabled
|
||||
APIKey string `json:"apiKey" gorm:"comment:API密钥;size:255;uniqueIndex"`
|
||||
}
|
||||
|
||||
func (SysUser) TableName() string {
|
||||
return "sys_users"
|
||||
}
|
||||
|
||||
// Login 接口实现
|
||||
type Login interface {
|
||||
GetUUID() uuid.UUID
|
||||
GetUserId() uint
|
||||
GetUsername() string
|
||||
GetNickname() string
|
||||
GetAuthorityId() uint
|
||||
}
|
||||
|
||||
func (s *SysUser) GetUUID() uuid.UUID {
|
||||
return uuid.New()
|
||||
}
|
||||
|
||||
func (s *SysUser) GetUserId() uint {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
func (s *SysUser) GetUsername() string {
|
||||
return s.Username
|
||||
}
|
||||
|
||||
func (s *SysUser) GetNickname() string {
|
||||
return s.Nickname
|
||||
}
|
||||
|
||||
func (s *SysUser) GetAuthorityId() uint {
|
||||
if s.Role == "admin" {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
Reference in New Issue
Block a user