diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 667eb22..e0e90db 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -21,9 +21,9 @@ jobs:
# 设置 Go 环境
- name: Set up Go
- uses: actions/setup-go@v4
+ uses: actions/setup-go@v5
with:
- go-version: '1.21'
+ go-version: '1.23'
# 构建后端(使用 Alpine 环境)
- name: Build backend
@@ -31,12 +31,14 @@ jobs:
cd backend
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o main-amd64 .
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o main-arm64 .
+ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o migrate-amd64 scripts/migrate.go
+ GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o migrate-arm64 scripts/migrate.go
# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
- node-version: '18'
+ node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
diff --git a/.gitignore b/.gitignore
index d9ee314..d2e65bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ out/
# 日志文件
*.log
logs/
+backend/data/aimodels.db
diff --git a/Dockerfile b/Dockerfile
index 631a15c..221f6a7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -24,6 +24,16 @@ RUN if [ "$(uname -m)" = "aarch64" ]; then \
rm main-* && \
chmod +x main
+# 复制迁移工具
+COPY backend/migrate-* ./
+RUN if [ "$(uname -m)" = "aarch64" ]; then \
+ cp migrate-arm64 migrate; \
+ else \
+ cp migrate-amd64 migrate; \
+ fi && \
+ rm migrate-* && \
+ chmod +x migrate
+
COPY frontend/dist /app/frontend
COPY backend/config/nginx.conf /etc/nginx/nginx.conf
COPY scripts/start.sh ./
diff --git a/backend/handlers/model_type.go b/backend/handlers/model_type.go
new file mode 100644
index 0000000..511cdef
--- /dev/null
+++ b/backend/handlers/model_type.go
@@ -0,0 +1,61 @@
+package handlers
+
+import (
+ "database/sql"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ModelType 模型类型结构
+type ModelType struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+}
+
+// GetModelTypes 获取所有模型类型
+func GetModelTypes(c *gin.Context) {
+ db := c.MustGet("db").(*sql.DB)
+
+ rows, err := db.Query("SELECT key, label FROM model_type")
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ defer rows.Close()
+
+ var types []ModelType
+ for rows.Next() {
+ var t ModelType
+ if err := rows.Scan(&t.Key, &t.Label); err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+ types = append(types, t)
+ }
+
+ c.JSON(200, types)
+}
+
+// CreateModelType 添加新的模型类型
+func CreateModelType(c *gin.Context) {
+ db := c.MustGet("db").(*sql.DB)
+
+ var newType ModelType
+ if err := c.ShouldBindJSON(&newType); err != nil {
+ c.JSON(400, gin.H{"error": err.Error()})
+ return
+ }
+
+ _, err := db.Exec(`
+ INSERT INTO model_type (key, label)
+ VALUES (?, ?)
+ ON CONFLICT(key) DO UPDATE SET label = excluded.label
+ `, newType.Key, newType.Label)
+
+ if err != nil {
+ c.JSON(500, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(201, newType)
+}
diff --git a/backend/handlers/prices.go b/backend/handlers/prices.go
index f54b927..57dc73a 100644
--- a/backend/handlers/prices.go
+++ b/backend/handlers/prices.go
@@ -50,9 +50,9 @@ func GetPrices(c *gin.Context) {
// 使用分页查询
query := `
- SELECT id, model, billing_type, channel_type, currency, input_price, output_price,
+ SELECT id, model, model_type, billing_type, channel_type, currency, input_price, output_price,
price_source, status, created_at, updated_at, created_by,
- temp_model, temp_billing_type, temp_channel_type, temp_currency,
+ temp_model, temp_model_type, temp_billing_type, temp_channel_type, temp_currency,
temp_input_price, temp_output_price, temp_price_source, updated_by
FROM price`
if whereClause != "" {
@@ -72,10 +72,10 @@ func GetPrices(c *gin.Context) {
for rows.Next() {
var price models.Price
if err := rows.Scan(
- &price.ID, &price.Model, &price.BillingType, &price.ChannelType, &price.Currency,
+ &price.ID, &price.Model, &price.ModelType, &price.BillingType, &price.ChannelType, &price.Currency,
&price.InputPrice, &price.OutputPrice, &price.PriceSource, &price.Status,
&price.CreatedAt, &price.UpdatedAt, &price.CreatedBy,
- &price.TempModel, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency,
+ &price.TempModel, &price.TempModelType, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency,
&price.TempInputPrice, &price.TempOutputPrice, &price.TempPriceSource, &price.UpdatedBy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan price"})
return
@@ -107,10 +107,10 @@ func CreatePrice(c *gin.Context) {
now := time.Now()
result, err := db.Exec(`
- INSERT INTO price (model, billing_type, channel_type, currency, input_price, output_price,
+ INSERT INTO price (model, model_type, billing_type, channel_type, currency, input_price, output_price,
price_source, status, created_by, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
- price.Model, price.BillingType, price.ChannelType, price.Currency,
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
+ price.Model, price.ModelType, price.BillingType, price.ChannelType, price.Currency,
price.InputPrice, price.OutputPrice, price.PriceSource, price.CreatedBy,
now, now)
if err != nil {
@@ -146,6 +146,7 @@ func UpdatePriceStatus(c *gin.Context) {
_, err := db.Exec(`
UPDATE price
SET model = COALESCE(temp_model, model),
+ model_type = COALESCE(temp_model_type, model_type),
billing_type = COALESCE(temp_billing_type, billing_type),
channel_type = COALESCE(temp_channel_type, channel_type),
currency = COALESCE(temp_currency, currency),
@@ -155,6 +156,7 @@ func UpdatePriceStatus(c *gin.Context) {
status = ?,
updated_at = ?,
temp_model = NULL,
+ temp_model_type = NULL,
temp_billing_type = NULL,
temp_channel_type = NULL,
temp_currency = NULL,
@@ -174,6 +176,7 @@ func UpdatePriceStatus(c *gin.Context) {
SET status = ?,
updated_at = ?,
temp_model = NULL,
+ temp_model_type = NULL,
temp_billing_type = NULL,
temp_channel_type = NULL,
temp_currency = NULL,
@@ -225,11 +228,11 @@ func UpdatePrice(c *gin.Context) {
// 将新的价格信息存储到临时字段
_, err = db.Exec(`
UPDATE price
- SET temp_model = ?, temp_billing_type = ?, temp_channel_type = ?, temp_currency = ?,
+ SET temp_model = ?, temp_model_type = ?, temp_billing_type = ?, temp_channel_type = ?, temp_currency = ?,
temp_input_price = ?, temp_output_price = ?, temp_price_source = ?,
updated_by = ?, updated_at = ?, status = 'pending'
WHERE id = ?`,
- price.Model, price.BillingType, price.ChannelType, price.Currency,
+ price.Model, price.ModelType, price.BillingType, price.ChannelType, price.Currency,
price.InputPrice, price.OutputPrice, price.PriceSource,
currentUser.Username, now, id)
if err != nil {
@@ -239,15 +242,15 @@ func UpdatePrice(c *gin.Context) {
// 获取更新后的价格信息
err = db.QueryRow(`
- SELECT id, model, billing_type, channel_type, currency, input_price, output_price,
+ SELECT id, model, model_type, billing_type, channel_type, currency, input_price, output_price,
price_source, status, created_at, updated_at, created_by,
- temp_model, temp_billing_type, temp_channel_type, temp_currency,
+ temp_model, temp_model_type, temp_billing_type, temp_channel_type, temp_currency,
temp_input_price, temp_output_price, temp_price_source, updated_by
FROM price WHERE id = ?`, id).Scan(
- &price.ID, &price.Model, &price.BillingType, &price.ChannelType, &price.Currency,
+ &price.ID, &price.Model, &price.ModelType, &price.BillingType, &price.ChannelType, &price.Currency,
&price.InputPrice, &price.OutputPrice, &price.PriceSource, &price.Status,
&price.CreatedAt, &price.UpdatedAt, &price.CreatedBy,
- &price.TempModel, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency,
+ &price.TempModel, &price.TempModelType, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency,
&price.TempInputPrice, &price.TempOutputPrice, &price.TempPriceSource, &price.UpdatedBy)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get updated price"})
@@ -274,6 +277,7 @@ func DeletePrice(c *gin.Context) {
// PriceRate 价格倍率结构
type PriceRate struct {
Model string `json:"model"`
+ ModelType string `json:"model_type"`
Type string `json:"type"`
ChannelType uint `json:"channel_type"`
Input float64 `json:"input"`
@@ -284,7 +288,7 @@ type PriceRate struct {
func GetPriceRates(c *gin.Context) {
db := c.MustGet("db").(*sql.DB)
rows, err := db.Query(`
- SELECT model, billing_type, channel_type,
+ SELECT model, model_type, billing_type, channel_type,
CASE
WHEN currency = 'USD' THEN input_price / 2
ELSE input_price / 14
@@ -307,6 +311,7 @@ func GetPriceRates(c *gin.Context) {
var rate PriceRate
if err := rows.Scan(
&rate.Model,
+ &rate.ModelType,
&rate.Type,
&rate.ChannelType,
&rate.Input,
diff --git a/backend/main.go b/backend/main.go
index b30509c..e2b5fee 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -88,6 +88,13 @@ func main() {
auth.GET("/user", handlers.GetUser)
auth.GET("/callback", handlers.AuthCallback)
}
+
+ // 模型类型相关路由
+ modelTypes := api.Group("/model-types")
+ {
+ modelTypes.GET("", handlers.GetModelTypes)
+ modelTypes.POST("", middleware.AuthRequired(), handlers.CreateModelType)
+ }
}
// 启动服务器
diff --git a/backend/models/price.go b/backend/models/price.go
index 8e46e61..0c7ca05 100644
--- a/backend/models/price.go
+++ b/backend/models/price.go
@@ -7,6 +7,7 @@ import (
type Price struct {
ID uint `json:"id"`
Model string `json:"model"`
+ ModelType string `json:"model_type"` // text2text, text2image, etc.
BillingType string `json:"billing_type"` // tokens or times
ChannelType string `json:"channel_type"`
Currency string `json:"currency"` // USD or CNY
@@ -19,6 +20,7 @@ type Price struct {
CreatedBy string `json:"created_by"`
// 临时字段,用于存储待审核的更新
TempModel *string `json:"temp_model,omitempty"`
+ TempModelType *string `json:"temp_model_type,omitempty"`
TempBillingType *string `json:"temp_billing_type,omitempty"`
TempChannelType *string `json:"temp_channel_type,omitempty"`
TempCurrency *string `json:"temp_currency,omitempty"`
@@ -34,6 +36,7 @@ func CreatePriceTableSQL() string {
CREATE TABLE IF NOT EXISTS price (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model TEXT NOT NULL,
+ model_type TEXT NOT NULL,
billing_type TEXT NOT NULL,
channel_type TEXT NOT NULL,
currency TEXT NOT NULL,
@@ -45,6 +48,7 @@ func CreatePriceTableSQL() string {
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by TEXT NOT NULL,
temp_model TEXT,
+ temp_model_type TEXT,
temp_billing_type TEXT,
temp_channel_type TEXT,
temp_currency TEXT,
diff --git a/backend/scripts/migrate.go b/backend/scripts/migrate.go
new file mode 100644
index 0000000..a8ebf05
--- /dev/null
+++ b/backend/scripts/migrate.go
@@ -0,0 +1,174 @@
+package main
+
+import (
+ "database/sql"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ _ "modernc.org/sqlite"
+)
+
+// ModelType 模型类型结构
+type ModelType struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+}
+
+func main() {
+ // 确保数据目录存在
+ dbDir := "./data"
+ if err := os.MkdirAll(dbDir, 0755); err != nil {
+ log.Fatalf("创建数据目录失败: %v", err)
+ }
+
+ // 连接数据库
+ dbPath := filepath.Join(dbDir, "aimodels.db")
+ db, err := sql.Open("sqlite", dbPath)
+ if err != nil {
+ log.Fatalf("连接数据库失败: %v", err)
+ }
+ defer db.Close()
+
+ // 创建model_type表
+ _, err = db.Exec(`
+ CREATE TABLE IF NOT EXISTS model_type (
+ key TEXT PRIMARY KEY,
+ label TEXT NOT NULL
+ )
+ `)
+ if err != nil {
+ log.Fatalf("创建model_type表失败: %v", err)
+ }
+
+ // 初始化默认的模型类型
+ defaultTypes := []ModelType{
+ {Key: "text2text", Label: "文生文"},
+ {Key: "text2image", Label: "文生图"},
+ {Key: "text2speech", Label: "文生音"},
+ {Key: "speech2text", Label: "音生文"},
+ {Key: "image2text", Label: "图生文"},
+ {Key: "embedding", Label: "向量"},
+ {Key: "other", Label: "其他"},
+ }
+
+ // 插入默认类型
+ for _, t := range defaultTypes {
+ _, err = db.Exec(`
+ INSERT OR IGNORE INTO model_type (key, label)
+ VALUES (?, ?)
+ `, t.Key, t.Label)
+ if err != nil {
+ log.Printf("插入默认类型失败 %s: %v", t.Key, err)
+ }
+ }
+
+ // 检查model_type列是否存在
+ var hasModelType bool
+ err = db.QueryRow(`
+ SELECT COUNT(*) > 0
+ FROM pragma_table_info('price')
+ WHERE name = 'model_type'
+ `).Scan(&hasModelType)
+ if err != nil {
+ log.Fatalf("检查model_type列失败: %v", err)
+ }
+
+ // 如果model_type列不存在,则添加它
+ if !hasModelType {
+ log.Println("开始添加model_type列...")
+
+ // 开始事务
+ tx, err := db.Begin()
+ if err != nil {
+ log.Fatalf("开始事务失败: %v", err)
+ }
+
+ // 添加model_type列
+ _, err = tx.Exec(`ALTER TABLE price ADD COLUMN model_type TEXT`)
+ if err != nil {
+ tx.Rollback()
+ log.Fatalf("添加model_type列失败: %v", err)
+ }
+
+ // 添加temp_model_type列
+ _, err = tx.Exec(`ALTER TABLE price ADD COLUMN temp_model_type TEXT`)
+ if err != nil {
+ tx.Rollback()
+ log.Fatalf("添加temp_model_type列失败: %v", err)
+ }
+
+ // 根据模型名称推断类型并更新
+ rows, err := tx.Query(`SELECT id, model FROM price`)
+ if err != nil {
+ tx.Rollback()
+ log.Fatalf("查询价格数据失败: %v", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var id int
+ var model string
+ if err := rows.Scan(&id, &model); err != nil {
+ tx.Rollback()
+ log.Fatalf("读取行数据失败: %v", err)
+ }
+
+ // 根据模型名称推断类型
+ modelType := inferModelType(model)
+
+ // 更新model_type
+ _, err = tx.Exec(`UPDATE price SET model_type = ? WHERE id = ?`, modelType, id)
+ if err != nil {
+ tx.Rollback()
+ log.Fatalf("更新model_type失败: %v", err)
+ }
+ }
+
+ // 提交事务
+ if err := tx.Commit(); err != nil {
+ log.Fatalf("提交事务失败: %v", err)
+ }
+
+ log.Println("成功添加并更新model_type列")
+ } else {
+ log.Println("model_type列已存在,无需迁移")
+ }
+}
+
+// inferModelType 根据模型名称推断模型类型
+func inferModelType(model string) string {
+ model = strings.ToLower(model)
+
+ switch {
+ case strings.Contains(model, "gpt") ||
+ strings.Contains(model, "llama") ||
+ strings.Contains(model, "claude") ||
+ strings.Contains(model, "palm") ||
+ strings.Contains(model, "gemini") ||
+ strings.Contains(model, "qwen") ||
+ strings.Contains(model, "chatglm"):
+ return "text2text"
+
+ case strings.Contains(model, "dall-e") ||
+ strings.Contains(model, "stable") ||
+ strings.Contains(model, "midjourney") ||
+ strings.Contains(model, "sd") ||
+ strings.Contains(model, "diffusion"):
+ return "text2image"
+
+ case strings.Contains(model, "whisper") ||
+ strings.Contains(model, "speech") ||
+ strings.Contains(model, "tts"):
+ return "text2speech"
+
+ case strings.Contains(model, "embedding") ||
+ strings.Contains(model, "ada") ||
+ strings.Contains(model, "text-embedding"):
+ return "embedding"
+
+ default:
+ return "other"
+ }
+}
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index 07b543c..a11958a 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -24,7 +24,7 @@
API文档
-
+
GET
diff --git a/frontend/src/views/Prices.vue b/frontend/src/views/Prices.vue
index 26bbd45..5c0cb80 100644
--- a/frontend/src/views/Prices.vue
+++ b/frontend/src/views/Prices.vue
@@ -68,6 +68,16 @@
+
+
+
+ {{ getModelType(row.model_type) }}
+
+ 待审核: {{ getModelType(row.temp_model_type) }}
+
+
+
+
@@ -125,16 +135,6 @@
-
-
- {{ row.input_price === 0 ? '免费' : calculateRate(row.input_price, row.currency) }}
-
-
-
-
- {{ row.output_price === 0 ? '免费' : calculateRate(row.output_price, row.currency) }}
-
-
+
+
+
+
+
+
+
@@ -319,6 +337,24 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
+
+
+
+
+
+
+
@@ -397,6 +433,7 @@ const prices = ref([])
const dialogVisible = ref(false)
const form = ref({
model: '',
+ model_type: '',
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
@@ -530,6 +567,7 @@ const handleAdd = () => {
editingPrice.value = null
form.value = {
model: '',
+ model_type: '',
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
@@ -597,6 +635,7 @@ const handleSubmitResponse = async (response) => {
editingPrice.value = null
form.value = {
model: '',
+ model_type: '',
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
@@ -633,9 +672,57 @@ const batchForms = ref([])
const selectedRows = ref([])
const batchSubmitting = ref(false)
+// 添加模型类型映射
+const modelTypeMap = ref({})
+
+// 加载模型类型
+const loadModelTypes = async () => {
+ try {
+ const response = await axios.get('/api/model-types')
+ const types = response.data
+ const map = {}
+ types.forEach(type => {
+ map[type.key] = type.label
+ })
+ modelTypeMap.value = map
+ } catch (error) {
+ console.error('Failed to load model types:', error)
+ ElMessage.error('加载模型类型失败')
+ }
+}
+
+// 处理新增的模型类型
+const handleModelTypeCreate = async (value) => {
+ // 如果输入的是中文描述,尝试查找对应的key
+ const existingKey = Object.entries(modelTypeMap.value).find(([_, label]) => label === value)?.[0]
+ if (existingKey) {
+ return existingKey
+ }
+
+ // 如果输入的是英文key,直接使用
+ let key = value
+ let label = value
+ if (!/^[a-zA-Z0-9_]+$/.test(value)) {
+ // 如果是中文描述,生成一个新的key
+ key = `type_${Date.now()}`
+ label = value
+ }
+
+ try {
+ await axios.post('/api/model-types', { key, label })
+ modelTypeMap.value[key] = label
+ return key
+ } catch (error) {
+ console.error('Failed to create model type:', error)
+ ElMessage.error('创建模型类型失败')
+ return 'other'
+ }
+}
+
// 创建新行的默认数据
const createNewRow = () => ({
model: '',
+ model_type: '',
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
@@ -839,6 +926,7 @@ watch(selectedProvider, () => {
})
onMounted(async () => {
+ await loadModelTypes()
await loadPrices()
})
diff --git a/prices.json b/prices.json
deleted file mode 100644
index e6a9f8a..0000000
--- a/prices.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"model":"dall-e-2","type":"tokens","channel_type":1,"input":8,"output":8},{"model":"babbage-002","type":"tokens","channel_type":1,"input":0.2,"output":0.2},{"model":"babbage-002","type":"tokens","channel_type":1,"input":0.2,"output":0.2},{"model":"tts-1-hd-1106","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"gpt-3.5-turbo-instruct-0914","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"mj_reroll","type":"times","channel_type":34,"input":50,"output":50},{"model":"Baichuan2-Turbo-192k","type":"tokens","channel_type":26,"input":1.143,"output":1.143},{"model":"Baichuan-Text-Embedding","type":"tokens","channel_type":26,"input":0.0355,"output":0.0355},{"model":"gemma-7b-it","type":"times","channel_type":31,"input":0,"output":0},{"model":"gpt-4-1106-vision-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"ChatStd","type":"tokens","channel_type":23,"input":0.7145,"output":0.7145},{"model":"@hf/thebloke/llama-2-13b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"glm-4-plus","type":"tokens","channel_type":16,"input":3.5715,"output":3.5715},{"model":"dall-e-3","type":"tokens","channel_type":1,"input":20,"output":20},{"model":"gpt-3.5-turbo-0125","type":"tokens","channel_type":1,"input":0.25,"output":0.75},{"model":"claude-3-haiku-20240307","type":"tokens","channel_type":14,"input":0.125,"output":0.625},{"model":"claude-2.1","type":"tokens","channel_type":14,"input":4,"output":12},{"model":"o1-mini-2024-09-12","type":"tokens","channel_type":1,"input":1.5,"output":6},{"model":"text-embedding-ada-002","type":"tokens","channel_type":1,"input":0.05,"output":0.05},{"model":"coze-*","type":"times","channel_type":38,"input":0,"output":0},{"model":"gemini-1.5-pro-exp-0801","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"stable-image-core","type":"tokens","channel_type":37,"input":15,"output":15},{"model":"moonshot-v1-auto","type":"tokens","channel_type":29,"input":4.2855,"output":4.2855},{"model":"gpt-4o-mini","type":"tokens","channel_type":1,"input":0.075,"output":0.3},{"model":"moonshot-v1-32k","type":"tokens","channel_type":29,"input":1.7145,"output":1.7145},{"model":"text-moderation-latest","type":"tokens","channel_type":1,"input":0.1,"output":0.1},{"model":"yi-large-rag","type":"tokens","channel_type":33,"input":1.7855,"output":1.7855},{"model":"gpt-4o-2024-08-06","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"tts-1","type":"tokens","channel_type":1,"input":7.5,"output":7.5},{"model":"embedding-2","type":"tokens","channel_type":16,"input":0.0355,"output":0.0355},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.857,"output":0.857},{"model":"abab5.5-chat","type":"tokens","channel_type":27,"input":1.0715,"output":1.0715},{"model":"embo-01","type":"tokens","channel_type":27,"input":0.0355,"output":0.0355},{"model":"sd3","type":"tokens","channel_type":37,"input":32.5,"output":32.5},{"model":"qwen-max-longcontext","type":"tokens","channel_type":17,"input":2.857,"output":8.5715},{"model":"glm-4-air","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"gpt-3.5-turbo-0613","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"gpt-4-0314","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"gemini-1.5-pro-001","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"abab6-chat","type":"tokens","channel_type":27,"input":7.143,"output":7.143},{"model":"@cf/meta/llama-3-8b-instruct","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"gpt-4-0613","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"@hf/meta-llama/meta-llama-3-8b-instruct","type":"times","channel_type":35,"input":0.5,"output":0.5},{"model":"mj_describe","type":"times","channel_type":34,"input":25,"output":25},{"model":"llama-guard-3-8b","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-4-turbo","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gpt-4-32k","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"gpt-4o-audio-preview-2024-10-01","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"llama3-groq-8b-8192-tool-use-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-3.5-turbo-1106","type":"tokens","channel_type":1,"input":0.5,"output":1},{"model":"llava-v1.5-7b-4096-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gemini-1.0-pro-vision-latest","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"@cf/bytedance/stable-diffusion-xl-lightning","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"gpt-3.5-turbo-16k","type":"tokens","channel_type":1,"input":1.5,"output":2},{"model":"mistral-medium-latest","type":"tokens","channel_type":30,"input":1.35,"output":4.05},{"model":"@cf/stabilityai/stable-diffusion-xl-base-1.0","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"whisper-1","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"mj_blend","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-4-32k-0314","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"text-embedding-3-large","type":"tokens","channel_type":1,"input":0.065,"output":0.065},{"model":"gemini-ultra","type":"tokens","channel_type":25,"input":1,"output":1},{"model":"qwen-vl-max","type":"tokens","channel_type":17,"input":1.4285,"output":1.4285},{"model":"claude-3-5-sonnet-latest","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"mj_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"open-mixtral-8x7b","type":"tokens","channel_type":30,"input":0.35,"output":0.35},{"model":"abab6.5s-chat","type":"tokens","channel_type":27,"input":0.7145,"output":0.7145},{"model":"mj_inpaint","type":"times","channel_type":34,"input":0,"output":0},{"model":"ERNIE-Tiny-8K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gemini-1.0-pro","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"gemini-1.5-pro","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"semantic_similarity_s1_v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"Baichuan2-Turbo","type":"tokens","channel_type":26,"input":0.5715,"output":0.5715},{"model":"llama3-8b-8192","type":"times","channel_type":31,"input":0,"output":0},{"model":"o1-preview-2024-09-12","type":"tokens","channel_type":1,"input":7.5,"output":30},{"model":"glm-4v","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"o1-mini","type":"tokens","channel_type":1,"input":1.5,"output":6},{"model":"hunyuan-standard","type":"tokens","channel_type":40,"input":0.3215,"output":0.357},{"model":"yi-vision","type":"tokens","channel_type":33,"input":0.4285,"output":0.4285},{"model":"gemini-pro","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"embedding-bert-512-v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"mj_zoom","type":"times","channel_type":34,"input":50,"output":50},{"model":"@cf/meta/llama-3.1-8b-instruct","type":"times","channel_type":35,"input":0.5,"output":0.5},{"model":"@hf/google/gemma-7b-it","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"mixtral-8x7b-32768","type":"times","channel_type":31,"input":0,"output":0},{"model":"glm-4","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"gpt-4o-realtime-preview","type":"tokens","channel_type":1,"input":2.5,"output":10},{"model":"claude-3-5-sonnet-20241022","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"claude-3-5-haiku-20241022","type":"tokens","channel_type":14,"input":0.5,"output":2.5},{"model":"abab5.5s-chat","type":"tokens","channel_type":27,"input":0.357,"output":0.357},{"model":"mj_pan","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-4o-2024-11-20","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"qwen-plus","type":"tokens","channel_type":17,"input":0.057,"output":0.143},{"model":"SparkDesk-v3.1","type":"tokens","channel_type":18,"input":2.143,"output":2.143},{"model":"gpt-3.5-turbo-16k-0613","type":"tokens","channel_type":1,"input":1.5,"output":2},{"model":"SparkDesk-v4.0","type":"tokens","channel_type":18,"input":7.143,"output":7.143},{"model":"tts-1-1106","type":"tokens","channel_type":1,"input":7.5,"output":7.5},{"model":"chatgpt-4o-latest","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"tts-1-hd","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"qwen-turbo","type":"tokens","channel_type":17,"input":0.0215,"output":0.043},{"model":"ChatPro","type":"tokens","channel_type":23,"input":7.143,"output":7.143},{"model":"claude-3-5-sonnet-20240620","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"text-embedding-v1","type":"tokens","channel_type":17,"input":0.05,"output":0.05},{"model":"Baichuan2-53B","type":"tokens","channel_type":26,"input":1.4285,"output":1.4285},{"model":"dall-e-2","type":"tokens","channel_type":1,"input":8,"output":8},{"model":"abab6.5-chat","type":"tokens","channel_type":27,"input":2.143,"output":2.143},{"model":"gemini-1.5-pro-latest","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"ERNIE-4.0","type":"tokens","channel_type":15,"input":8.572,"output":8.572},{"model":"@cf/openai/whisper","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"open-mixtral-8x22b","type":"tokens","channel_type":30,"input":4,"output":12},{"model":"ERNIE-Speed-128K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gpt-4o-realtime-preview-2024-10-01","type":"tokens","channel_type":1,"input":2.5,"output":10},{"model":"gpt-3.5-turbo-instruct","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"yi-medium-200k","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"gemini-pro-vision","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"yi-large-turbo","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"qwen-max","type":"tokens","channel_type":17,"input":1.4285,"output":4.2855},{"model":"@cf/qwen/qwen1.5-14b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"o1-preview","type":"tokens","channel_type":1,"input":7.5,"output":30},{"model":"yi-medium","type":"tokens","channel_type":33,"input":0.1785,"output":0.1785},{"model":"command-r","type":"tokens","channel_type":36,"input":0.25,"output":0.75},{"model":"gpt-4-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gemini-1.0-pro-latest","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"hunyuan","type":"tokens","channel_type":23,"input":7.143,"output":7.143},{"model":"ERNIE-Bot-4","type":"tokens","channel_type":15,"input":8.572,"output":8.572},{"model":"Embedding-V1","type":"tokens","channel_type":15,"input":0.143,"output":0.143},{"model":"SparkDesk-v3.5","type":"tokens","channel_type":18,"input":2.143,"output":2.143},{"model":"claude-2.0","type":"tokens","channel_type":14,"input":4,"output":12},{"model":"PaLM-2","type":"tokens","channel_type":11,"input":1,"output":1},{"model":"embedding_s1_v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"gemma2-9b-it","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gemini-1.5-flash-8b-exp-0827","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"gpt-4o-mini-2024-07-18","type":"tokens","channel_type":1,"input":0.075,"output":0.3},{"model":"BLOOMZ-7B","type":"tokens","channel_type":15,"input":0.2855,"output":0.2855},{"model":"moonshot-v1-128k","type":"tokens","channel_type":29,"input":4.2855,"output":4.2855},{"model":"gpt-4o-audio-preview","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"gpt-4-vision-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gpt-4-turbo-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"mistral-embed","type":"tokens","channel_type":30,"input":0.05,"output":0.05},{"model":"yi-spark","type":"tokens","channel_type":33,"input":0.0715,"output":0.0715},{"model":"mistral-small-latest","type":"tokens","channel_type":30,"input":1,"output":3},{"model":"ERNIE-Functions-8K","type":"tokens","channel_type":15,"input":0.2855,"output":0.5715},{"model":"cogview-3","type":"tokens","channel_type":16,"input":17.857,"output":17.857},{"model":"gpt-3.5-turbo","type":"tokens","channel_type":1,"input":0.25,"output":0.75},{"model":"glm-4-long","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"text-moderation-stable","type":"tokens","channel_type":1,"input":0.1,"output":0.1},{"model":"@cf/lykon/dreamshaper-8-lcm","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"deepseek-coder","type":"tokens","channel_type":28,"input":0.0715,"output":0.143},{"model":"yi-large-fc","type":"tokens","channel_type":33,"input":1.4285,"output":1.4285},{"model":"claude-3-opus-20240229","type":"tokens","channel_type":14,"input":7.5,"output":37.5},{"model":"glm-4-0520","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"SparkDesk-v1.1","type":"tokens","channel_type":18,"input":0,"output":0},{"model":"mj_custom_zoom","type":"times","channel_type":34,"input":0,"output":0},{"model":"llama-3.1-8b-instant","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"llama2-70b-4096","type":"times","channel_type":31,"input":0,"output":0},{"model":"gpt-4","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"yi-large","type":"tokens","channel_type":33,"input":1.4285,"output":1.4285},{"model":"glm-4-alltools","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"davinci-002","type":"tokens","channel_type":1,"input":1,"output":1},{"model":"ERNIE-Speed","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gpt-4-1106-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gemini-1.0-pro-001","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"whisper-large-v3","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"hunyuan-standard-256k","type":"tokens","channel_type":40,"input":1.0715,"output":4.2855},{"model":"mj_low_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-3.5-turbo-0301","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"mj_upscale","type":"times","channel_type":34,"input":25,"output":25},{"model":"mj_shorten","type":"times","channel_type":34,"input":50,"output":50},{"model":"deepseek-chat","type":"tokens","channel_type":28,"input":0.0715,"output":0.143},{"model":"gpt-4-0125-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"yi-34b-chat-0205","type":"tokens","channel_type":33,"input":0.1785,"output":0.1785},{"model":"hunyuan-pro","type":"tokens","channel_type":37,"input":2.143,"output":7.143},{"model":"hunyuan-lite","type":"tokens","channel_type":40,"input":0,"output":0},{"model":"claude-instant-1.2","type":"tokens","channel_type":14,"input":0.4,"output":1.2},{"model":"mj_high_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"ERNIE-Lite-8K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"text-embedding-3-small","type":"tokens","channel_type":1,"input":0.01,"output":0.01},{"model":"open-mistral-7b","type":"tokens","channel_type":30,"input":0.125,"output":0.125},{"model":"swap_face","type":"times","channel_type":34,"input":25,"output":25},{"model":"llama-3.1-70b-versatile","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-4-turbo-2024-04-09","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"@hf/thebloke/deepseek-coder-6.7b-base-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"glm-4-airx","type":"tokens","channel_type":16,"input":0.7145,"output":0.7145},{"model":"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b","type":"tokens","channel_type":35,"input":0.25,"output":2.44},{"model":"gemini-1.5-flash-latest","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"ERNIE-Bot-turbo","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"glm-4-flash","type":"tokens","channel_type":16,"input":0,"output":0},{"model":"command-r-plus","type":"tokens","channel_type":36,"input":1.5,"output":7.5},{"model":"gemini-1.0-ultra-latest","type":"tokens","channel_type":25,"input":1,"output":1},{"model":"gemini-1.5-pro-exp-0827","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"moonshot-v1-8k","type":"tokens","channel_type":29,"input":0.857,"output":0.857},{"model":"gpt-4o-2024-05-13","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"llama3-70b-8192","type":"times","channel_type":31,"input":0,"output":0},{"model":"qwen-long","type":"tokens","channel_type":17,"input":0.0355,"output":0.143},{"model":"gpt-4-32k-0613","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"360GPT_S2_V9","type":"tokens","channel_type":19,"input":0.857,"output":0.857},{"model":"sd3-turbo","type":"tokens","channel_type":37,"input":20,"output":20},{"model":"gpt-4o","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"claude-3-5-haiku-latest","type":"tokens","channel_type":14,"input":0.5,"output":2.5},{"model":"claude-3-sonnet-20240229","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"@cf/qwen/qwen1.5-7b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"distil-whisper-large-v3-en","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"qwen-vl-plus","type":"tokens","channel_type":17,"input":0.5715,"output":0.5715},{"model":"gemini-1.5-flash-001","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"gemini-1.5-flash","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"mj_modal","type":"times","channel_type":34,"input":50,"output":50},{"model":"yi-34b-chat-200k","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"mj_imagine","type":"times","channel_type":34,"input":50,"output":50},{"model":"ERNIE-Bot","type":"tokens","channel_type":15,"input":0.857,"output":0.857},{"model":"glm-3-turbo","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"yi-vl-plus","type":"tokens","channel_type":33,"input":0.4285,"output":0.4285},{"model":"llama3-groq-70b-8192-tool-use-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"yi-lightning","type":"tokens","channel_type":33,"input":0.0705,"output":0.0705},{"model":"mistral-large-latest","type":"tokens","channel_type":30,"input":4,"output":12},{"model":"gemini-1.5-flash-exp-0827","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.12242857142857143,"output":0.12242857142857143},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.05714285714285715,"output":0.12242857142857143},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.05714285714285715,"output":0.14285714285714285}]
\ No newline at end of file
diff --git a/scripts/start.sh b/scripts/start.sh
index c7bded3..66f387f 100644
--- a/scripts/start.sh
+++ b/scripts/start.sh
@@ -1,5 +1,9 @@
#!/bin/bash
+# 执行数据库迁移
+echo "执行数据库迁移..."
+./migrate
+
# 启动后端服务
./main &