Replace "supplier" terminology with "model vendor" across frontend and backend

This commit is contained in:
wood chen 2025-02-08 21:03:01 +08:00
parent 7873fab16f
commit 1d16333e11
11 changed files with 140 additions and 32 deletions

View File

@ -51,7 +51,7 @@ func createTables() error {
return err
}
// 创建供应商表
// 创建模型厂商表
if _, err := DB.Exec(models.CreateProviderTableSQL()); err != nil {
log.Printf("Failed to create provider table: %v", err)
return err

View File

@ -49,7 +49,7 @@ func CreatePrice(c *gin.Context) {
return
}
// 验证供应商ID是否存在
// 验证模型厂商ID是否存在
db := c.MustGet("db").(*sql.DB)
var providerExists bool
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)
@ -157,7 +157,7 @@ func UpdatePrice(c *gin.Context) {
return
}
// 验证供应商ID是否存在
// 验证模型厂商ID是否存在
db := c.MustGet("db").(*sql.DB)
var providerExists bool
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)

View File

@ -11,7 +11,7 @@ import (
"aimodels-prices/models"
)
// GetProviders 获取所有供应
// GetProviders 获取所有模型厂
func GetProviders(c *gin.Context) {
db := c.MustGet("db").(*sql.DB)
rows, err := db.Query(`
@ -38,7 +38,7 @@ func GetProviders(c *gin.Context) {
c.JSON(http.StatusOK, providers)
}
// CreateProvider 创建供应
// CreateProvider 创建模型厂
func CreateProvider(c *gin.Context) {
var provider models.Provider
if err := c.ShouldBindJSON(&provider); err != nil {
@ -80,7 +80,7 @@ func CreateProvider(c *gin.Context) {
c.JSON(http.StatusCreated, provider)
}
// UpdateProvider 更新供应
// UpdateProvider 更新模型厂
func UpdateProvider(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
@ -106,7 +106,7 @@ func UpdateProvider(c *gin.Context) {
return
}
// 获取更新后的供应商信息
// 获取更新后的模型厂商信息
err = db.QueryRow(`
SELECT id, name, icon, created_at, updated_at, created_by
FROM provider WHERE id = ?`, id).Scan(
@ -120,7 +120,7 @@ func UpdateProvider(c *gin.Context) {
c.JSON(http.StatusOK, provider)
}
// UpdateProviderStatus 更新供应商状态
// UpdateProviderStatus 更新模型厂商状态
func UpdateProviderStatus(c *gin.Context) {
id := c.Param("id")
var input struct {
@ -174,7 +174,7 @@ func UpdateProviderStatus(c *gin.Context) {
})
}
// DeleteProvider 删除供应
// DeleteProvider 删除模型厂
func DeleteProvider(c *gin.Context) {
id := c.Param("id")
db := c.MustGet("db").(*sql.DB)

View File

@ -70,7 +70,7 @@ func main() {
prices.PUT("/:id/status", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdatePriceStatus)
}
// 供应商相关路由
// 模型厂商相关路由
providers := api.Group("/providers")
{
providers.GET("", handlers.GetProviders)

View File

@ -11,7 +11,7 @@ type Provider struct {
CreatedBy string `json:"created_by"`
}
// CreateProviderTableSQL 返回创建供应商表的 SQL
// CreateProviderTableSQL 返回创建模型厂商表的 SQL
func CreateProviderTableSQL() string {
return `
CREATE TABLE IF NOT EXISTS provider (

View File

@ -22,7 +22,7 @@ func SetupRouter() *gin.Engine {
auth.POST("/logout", handlers.Logout)
}
// 供应商相关路由
// 模型厂商相关路由
providers := r.Group("/providers")
{
providers.GET("", handlers.GetProviders)

View File

@ -5,7 +5,7 @@
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="专业的AI模型价格管理系统支持多供应商、多币种的价格管理提供标准的API接口。">
<meta name="description" content="专业的AI模型价格管理系统支持多模型厂商、多币种的价格管理提供标准的API接口。">
<meta name="keywords" content="AI模型,价格管理,API接口,OpenAI,Azure,Anthropic">
<meta name="author" content="AI Models Prices Team">
<title>AI模型价格</title>

View File

@ -8,7 +8,7 @@
</router-link>
<div class="nav-buttons">
<el-button @click="$router.push('/prices')" :type="$route.path === '/prices' ? 'primary' : ''">价格列表</el-button>
<el-button @click="$router.push('/providers')" :type="$route.path === '/providers' ? 'primary' : ''">供应商列表</el-button>
<el-button @click="$router.push('/providers')" :type="$route.path === '/providers' ? 'primary' : ''">模型厂商</el-button>
</div>
</div>
<div class="auth-buttons">

View File

@ -8,11 +8,11 @@
</template>
<div class="content">
<h2>项目简介</h2>
<p>这是一个专门用于管理AI模型价格的系统支持多供应多币种的价格管理并提供标准的API接口供其他系统调用</p>
<p>这是一个专门用于管理AI模型价格的系统支持多模型厂多币种的价格管理并提供标准的API接口供其他系统调用</p>
<h2>主要功能</h2>
<ul>
<li>供应商管理添加编辑和删除AI模型供应</li>
<li>模型厂商管理添加编辑和删除AI模型模型厂</li>
<li>价格管理设置和更新各个模型的价格</li>
<li>多币种支持支持USD和CNY两种货币</li>
<li>审核流程价格变更需要管理员审核</li>
@ -47,7 +47,7 @@
<ul>
<li>model: 模型名称</li>
<li>type: 计费类型tokens/times</li>
<li>channel_type: 供应商ID</li>
<li>channel_type: 模型厂商ID</li>
<li>input: 输入价格倍率</li>
<li>output: 输出价格倍率</li>
</ul>
@ -83,7 +83,7 @@
</div>
</el-collapse-item>
<el-collapse-item title="获取供应商列表">
<el-collapse-item title="获取模型厂商">
<div class="api-doc">
<div class="api-url">
<span class="method">GET</span>
@ -93,7 +93,7 @@
</span>
</el-tooltip>
</div>
<p>获取所有供应商信息</p>
<p>获取所有模型厂商信息</p>
<h4>响应示例</h4>
<pre>
[
@ -219,7 +219,7 @@ onMounted(() => {
//
const meta = {
title: 'AI模型价格 - 首页',
description: '专业的AI模型价格管理系统支持多供应商、多币种的价格管理提供标准的API接口。',
description: '专业的AI模型价格管理系统支持多模型厂商、多币种的价格管理提供标准的API接口。',
keywords: 'AI模型,价格管理,API接口,OpenAI,Azure,Anthropic'
}

View File

@ -151,6 +151,31 @@
<div class="batch-toolbar">
<el-button type="primary" @click="addRow">添加行</el-button>
<el-button type="danger" @click="removeSelectedRows" :disabled="!selectedRows.length">删除选中行</el-button>
<el-divider direction="vertical" />
<el-popover
placement="bottom"
:width="400"
trigger="click"
>
<template #reference>
<el-button type="success">从表格导入</el-button>
</template>
<div class="import-popover">
<p class="import-tip">请粘贴表格数据支持从Excel复制每行格式为</p>
<p class="import-format">模型名称 计费类型 厂商 货币 输入价格 输出价格</p>
<el-input
v-model="importText"
type="textarea"
:rows="8"
placeholder="例如
dall-e-2 按Token收费 OpenAI 美元 16.000000 16.000000
dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
/>
<div class="import-actions">
<el-button type="primary" @click="handleImport">导入</el-button>
</div>
</div>
</el-popover>
</div>
<el-table
:data="batchForms"
@ -573,6 +598,62 @@ const submitBatchForms = async () => {
}
}
//
const importText = ref('')
//
const handleImport = () => {
if (!importText.value.trim()) {
ElMessage.warning('请先粘贴数据')
return
}
const lines = importText.value.trim().split('\n')
const newRows = lines.map(line => {
const [model, billingType, providerName, currency, inputPrice, outputPrice] = line.trim().split(/\s+/)
// ID
const provider = providers.value.find(p => p.name === providerName)
if (!provider) {
ElMessage.warning(`未找到模型厂商:${providerName}`)
return null
}
//
let billing_type = 'tokens'
if (billingType.includes('Token')) {
billing_type = 'tokens'
} else if (billingType.includes('次')) {
billing_type = 'times'
}
//
let currencyCode = 'USD'
if (currency.includes('美元')) {
currencyCode = 'USD'
} else if (currency.includes('人民币') || currency.includes('CNY')) {
currencyCode = 'CNY'
}
return {
model,
billing_type,
channel_type: provider.id.toString(),
currency: currencyCode,
input_price: parseFloat(inputPrice),
output_price: parseFloat(outputPrice),
price_source: '官方',
created_by: props.user?.username || ''
}
}).filter(row => row !== null)
if (newRows.length > 0) {
batchForms.value = [...batchForms.value, ...newRows]
importText.value = ''
ElMessage.success(`成功导入 ${newRows.length} 条数据`)
}
}
onMounted(async () => {
await loadPrices()
try {
@ -580,7 +661,7 @@ onMounted(async () => {
providers.value = data
} catch (error) {
console.error('Failed to load providers:', error)
ElMessage.error('加载供应商数据失败')
ElMessage.error('加载模型厂商数据失败')
}
})
</script>
@ -683,4 +764,31 @@ onMounted(async () => {
:deep(.el-select) {
width: 100%;
}
.import-popover {
display: flex;
flex-direction: column;
gap: 12px;
}
.import-tip {
margin: 0;
color: #606266;
font-size: 14px;
}
.import-format {
margin: 0;
color: #409EFF;
font-size: 13px;
background-color: #ecf5ff;
padding: 8px;
border-radius: 4px;
}
.import-actions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
</style>

View File

@ -3,8 +3,8 @@
<el-card>
<template #header>
<div class="card-header">
<span>供应商列表</span>
<el-button type="primary" @click="handleAdd">添加供应</el-button>
<span>模型厂商</span>
<el-button type="primary" @click="handleAdd">添加模型厂</el-button>
</div>
</template>
@ -82,7 +82,7 @@ const router = useRouter()
const isAdmin = computed(() => props.user?.role === 'admin')
// ID
// ID
const sortedProviders = computed(() => {
return [...providers.value].sort((a, b) => a.id - b.id)
})
@ -103,14 +103,14 @@ onMounted(() => {
const dialogTitle = computed(() => {
if (editingProvider.value) {
return '编辑供应商'
return '编辑模型厂商'
}
return '添加供应商'
return '添加模型厂商'
})
const handleEdit = (provider) => {
if (!isAdmin.value) {
ElMessage.warning('只有管理员可以编辑供应商信息')
ElMessage.warning('只有管理员可以编辑模型厂商信息')
return
}
editingProvider.value = provider
@ -120,12 +120,12 @@ const handleEdit = (provider) => {
const handleDelete = (provider) => {
if (!isAdmin.value) {
ElMessage.warning('只有管理员可以删除供应商')
ElMessage.warning('只有管理员可以删除模型厂商')
return
}
ElMessageBox.confirm(
'确定要删除这个供应商吗?',
'确定要删除这个模型厂商吗?',
'警告',
{
confirmButtonText: '确定',
@ -162,10 +162,10 @@ const submitForm = async () => {
try {
if (editingProvider.value) {
if (!isAdmin.value) {
ElMessage.error('只有管理员可以编辑供应商信息')
ElMessage.error('只有管理员可以编辑模型厂商信息')
return
}
//
//
const { data } = await axios.put(`/api/providers/${editingProvider.value.id}`, form.value)
if (data.error) {
ElMessage.error(data.error)
@ -177,7 +177,7 @@ const submitForm = async () => {
}
ElMessage.success('更新成功')
} else {
//
//
const { data } = await axios.post('/api/providers', form.value)
if (data.error) {
ElMessage.error(data.error)