improve(ui):新增模型广场页面

This commit is contained in:
chengqian committed 2026-08-12 17:15:27 +08:00
1 parent cb7854c529
commit 0ec970a182
28 files changed
+3301 -16

No files matched your search

+2
View File
@@ -57,6 +57,8 @@ type ITenantV1 interface {
HelpPublicArticleGet(ctx context.Context, req *v1.HelpPublicArticleGetReq) (res *v1.HelpPublicArticleGetRes, err error)
HelpPublicSearch(ctx context.Context, req *v1.HelpPublicSearchReq) (res *v1.HelpPublicSearchRes, err error)
TenantLevelBenefits(ctx context.Context, req *v1.TenantLevelBenefitsReq) (res *v1.TenantLevelBenefitsRes, err error)
MarketplaceList(ctx context.Context, req *v1.MarketplaceListReq) (res *v1.MarketplaceListRes, err error)
MarketplaceDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (res *v1.MarketplaceDetailRes, err error)
TenantMemberList(ctx context.Context, req *v1.TenantMemberListReq) (res *v1.TenantMemberListRes, err error)
TenantMemberInvite(ctx context.Context, req *v1.TenantMemberInviteReq) (res *v1.TenantMemberInviteRes, err error)
TenantInvitationList(ctx context.Context, req *v1.TenantInvitationListReq) (res *v1.TenantInvitationListRes, err error)
+42
View File
@@ -0,0 +1,42 @@
package v1
import (
"github.com/gogf/gf/v2/frame/g"
)
type MarketplaceListReq struct {
g.Meta `path:"/marketplace/models" method:"get" tags:"公开-模型广场" summary:"获取模型广场列表" group:"public" middleware:"-"`
Keyword string `json:"keyword" dc:"搜索关键词"`
Category string `json:"category" dc:"模型类别筛选(chat/embedding/image/audio等)"`
Page int `json:"page" d:"1" dc:"页码"`
PageSize int `json:"page_size" d:"20" v:"between:1,100" dc:"每页数量"`
}
type MarketplaceListRes struct {
List []MarketplaceModelItem `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
type MarketplaceModelItem struct {
ModelId string `json:"model_id"` // 模型标识
ModelName string `json:"model_name"` // 模型显示名称
Category string `json:"category"` // 分类(chat/embedding/image等)
Description string `json:"description"` // 描述
MaxContextTokens int `json:"max_context_tokens"` // 最大上下文 tokens
MaxOutputTokens int `json:"max_output_tokens"` // 最大输出 tokens
InputPrice float64 `json:"input_price"` // 输入价格(每百万 token,USD)
OutputPrice float64 `json:"output_price"` // 输出价格(每百万 token,USD)
Tags []string `json:"tags"` // 标签
Capabilities g.Map `json:"capabilities"` // 能力标签
}
type MarketplaceDetailReq struct {
g.Meta `path:"/marketplace/models/{model_id}" method:"get" tags:"公开-模型广场" summary:"获取模型详情" group:"public" middleware:"-"`
ModelId string `json:"model_id" in:"path" v:"required" dc:"模型标识"`
}
type MarketplaceDetailRes struct {
MarketplaceModelItem
}
@@ -0,0 +1,15 @@
package tenant
import (
"context"
"github.com/qianfree/team-api/api/tenant/v1"
"github.com/qianfree/team-api/internal/service"
)
func (c *ControllerV1) MarketplaceList(ctx context.Context, req *v1.MarketplaceListReq) (res *v1.MarketplaceListRes, err error) {
return service.Tenant().GetModelList(ctx, req)
}
func (c *ControllerV1) MarketplaceDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (res *v1.MarketplaceDetailRes, err error) {
return service.Tenant().GetModelDetail(ctx, req)
}
+22
View File
@@ -0,0 +1,22 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"github.com/qianfree/team-api/internal/dao/internal"
)
// bilDailyUsageSummaryDao is the data access object for the table bil_daily_usage_summary.
// You can define custom methods on it to extend its functionality as needed.
type bilDailyUsageSummaryDao struct {
*internal.BilDailyUsageSummaryDao
}
var (
// BilDailyUsageSummary is a globally accessible object for table bil_daily_usage_summary operations.
BilDailyUsageSummary = bilDailyUsageSummaryDao{internal.NewBilDailyUsageSummaryDao()}
)
// Add your custom methods and functionality below.
+22
View File
@@ -0,0 +1,22 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"github.com/qianfree/team-api/internal/dao/internal"
)
// bilMonthlyUsageSummaryDao is the data access object for the table bil_monthly_usage_summary.
// You can define custom methods on it to extend its functionality as needed.
type bilMonthlyUsageSummaryDao struct {
*internal.BilMonthlyUsageSummaryDao
}
var (
// BilMonthlyUsageSummary is a globally accessible object for table bil_monthly_usage_summary operations.
BilMonthlyUsageSummary = bilMonthlyUsageSummaryDao{internal.NewBilMonthlyUsageSummaryDao()}
)
// Add your custom methods and functionality below.
@@ -51,7 +51,6 @@ type AudRequestLogsColumns struct {
TaskResult string // 异步任务完成时上游返回的原始响应体
TaskUpstreamHeaders string // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
TaskCompletedAt string // 异步任务达到终态的时间
AttachmentIds string // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
}
// audRequestLogsColumns holds the columns for the table aud_request_logs.
@@ -86,7 +85,6 @@ var audRequestLogsColumns = AudRequestLogsColumns{
TaskResult: "task_result",
TaskUpstreamHeaders: "task_upstream_headers",
TaskCompletedAt: "task_completed_at",
AttachmentIds: "attachment_ids",
}
// NewAudRequestLogsDao creates and returns a new DAO object for table data access.
@@ -0,0 +1,93 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// BilDailyUsageSummaryDao is the data access object for the table bil_daily_usage_summary.
type BilDailyUsageSummaryDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns BilDailyUsageSummaryColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// BilDailyUsageSummaryColumns defines and stores column names for the table bil_daily_usage_summary.
type BilDailyUsageSummaryColumns struct {
Id string //
TenantId string //
Date string //
TotalRequests string //
TotalTokens string //
TotalCost string //
CreatedAt string //
UpdatedAt string //
}
// bilDailyUsageSummaryColumns holds the columns for the table bil_daily_usage_summary.
var bilDailyUsageSummaryColumns = BilDailyUsageSummaryColumns{
Id: "id",
TenantId: "tenant_id",
Date: "date",
TotalRequests: "total_requests",
TotalTokens: "total_tokens",
TotalCost: "total_cost",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
// NewBilDailyUsageSummaryDao creates and returns a new DAO object for table data access.
func NewBilDailyUsageSummaryDao(handlers ...gdb.ModelHandler) *BilDailyUsageSummaryDao {
return &BilDailyUsageSummaryDao{
group: "default",
table: "bil_daily_usage_summary",
columns: bilDailyUsageSummaryColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *BilDailyUsageSummaryDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *BilDailyUsageSummaryDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *BilDailyUsageSummaryDao) Columns() BilDailyUsageSummaryColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *BilDailyUsageSummaryDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *BilDailyUsageSummaryDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *BilDailyUsageSummaryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
@@ -0,0 +1,93 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// BilMonthlyUsageSummaryDao is the data access object for the table bil_monthly_usage_summary.
type BilMonthlyUsageSummaryDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns BilMonthlyUsageSummaryColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// BilMonthlyUsageSummaryColumns defines and stores column names for the table bil_monthly_usage_summary.
type BilMonthlyUsageSummaryColumns struct {
Id string //
TenantId string //
Month string //
TotalRequests string //
TotalTokens string //
TotalCost string //
CreatedAt string //
UpdatedAt string //
}
// bilMonthlyUsageSummaryColumns holds the columns for the table bil_monthly_usage_summary.
var bilMonthlyUsageSummaryColumns = BilMonthlyUsageSummaryColumns{
Id: "id",
TenantId: "tenant_id",
Month: "month",
TotalRequests: "total_requests",
TotalTokens: "total_tokens",
TotalCost: "total_cost",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
// NewBilMonthlyUsageSummaryDao creates and returns a new DAO object for table data access.
func NewBilMonthlyUsageSummaryDao(handlers ...gdb.ModelHandler) *BilMonthlyUsageSummaryDao {
return &BilMonthlyUsageSummaryDao{
group: "default",
table: "bil_monthly_usage_summary",
columns: bilMonthlyUsageSummaryColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *BilMonthlyUsageSummaryDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *BilMonthlyUsageSummaryDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *BilMonthlyUsageSummaryDao) Columns() BilMonthlyUsageSummaryColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *BilMonthlyUsageSummaryDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *BilMonthlyUsageSummaryDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *BilMonthlyUsageSummaryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
+4 -4
View File
@@ -33,10 +33,10 @@ type BilUsageDailyColumns struct {
OutputTokens string // 输出Token合计
TotalCost string // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
AccountCost string // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
CreatedAt string //
UpdatedAt string //
SumLatencyMs string // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
SumFirstTokenMs string // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
CreatedAt string //
UpdatedAt string //
}
// bilUsageDailyColumns holds the columns for the table bil_usage_daily.
@@ -53,10 +53,10 @@ var bilUsageDailyColumns = BilUsageDailyColumns{
OutputTokens: "output_tokens",
TotalCost: "total_cost",
AccountCost: "account_cost",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
SumLatencyMs: "sum_latency_ms",
SumFirstTokenMs: "sum_first_token_ms",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
// NewBilUsageDailyDao creates and returns a new DAO object for table data access.
+1 -1
View File
@@ -27,7 +27,7 @@ type MdlTenantModelsColumns struct {
Enabled string // 是否启用(禁用后该租户无法调用此模型)
CustomInputPrice string // 租户自定义输入价格(NULL 表示使用默认定价)
CustomOutputPrice string // 租户自定义输出价格(NULL 表示使用默认定价)
Multiplier string // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
Multiplier string // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
CreatedAt string // 创建时间
UpdatedAt string // 更新时间
BillingMode string // 覆盖模型计费方式(NULL表示跟随模型默认)
+201
View File
@@ -0,0 +1,201 @@
package tenant
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
v1 "github.com/qianfree/team-api/api/tenant/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/billing"
"github.com/qianfree/team-api/internal/model/entity"
)
// GetModelList 获取模型广场列表(从默认模型分组加载)
func (s *sTenant) GetModelList(ctx context.Context, req *v1.MarketplaceListReq) (*v1.MarketplaceListRes, error) {
// 1. 查找默认模型分组
var defaultGroup *entity.MdlModelGroups
err := dao.MdlModelGroups.Ctx(ctx).
Where("is_default", true).
Where("status", "active").
Scan(&defaultGroup)
if err != nil {
return nil, err
}
if defaultGroup == nil {
// 没有默认分组,返回空列表
return &v1.MarketplaceListRes{
List: []v1.MarketplaceModelItem{},
Total: 0,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
// 2. 构建查询:通过分组关联查询模型
m := dao.MdlModels.Ctx(ctx).
LeftJoin("mdl_group_models", "mdl_group_models.model_id = mdl_models.id").
Where("mdl_group_models.group_id", defaultGroup.Id).
Where("mdl_models.status", "active")
// 搜索关键词
if req.Keyword != "" {
keyword := "%" + req.Keyword + "%"
m = m.Where("(mdl_models.model_id LIKE ? OR mdl_models.model_name LIKE ? OR mdl_models.description LIKE ?)", keyword, keyword, keyword)
}
// 分类筛选
if req.Category != "" {
m = m.Where("mdl_models.category", req.Category)
}
// 排序:按类别、模型名称
m = m.OrderAsc("mdl_models.category").OrderAsc("mdl_models.model_id")
// 3. 查询总数
total, err := m.Count()
if err != nil {
return nil, err
}
// 4. 分页查询模型
var models []*entity.MdlModels
err = m.Fields("mdl_models.*").
Page(req.Page, req.PageSize).
Scan(&models)
if err != nil {
return nil, err
}
// 5. 批量查询价格(从 mdl_pricing 表)
if len(models) == 0 {
return &v1.MarketplaceListRes{
List: []v1.MarketplaceModelItem{},
Total: 0,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
modelIds := make([]int64, len(models))
for i, model := range models {
modelIds[i] = model.Id
}
var pricings []*entity.MdlPricing
err = dao.MdlPricing.Ctx(ctx).
WhereIn("model_id", modelIds).
Where("billing_mode", "token"). // 只取 token 计费模式的基础价格
Scan(&pricings)
if err != nil {
return nil, err
}
// 构建价格映射(model_id -> pricing)
priceMap := make(map[int64]*entity.MdlPricing)
for _, pricing := range pricings {
priceMap[pricing.ModelId] = pricing
}
// 6. 转换为响应格式
list := make([]v1.MarketplaceModelItem, 0, len(models))
for _, model := range models {
item := s.convertToMarketplaceItem(ctx, model, priceMap[model.Id])
list = append(list, item)
}
return &v1.MarketplaceListRes{
List: list,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
// GetModelDetail 获取模型详情
func (s *sTenant) GetModelDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (*v1.MarketplaceDetailRes, error) {
// 1. 检查模型是否在默认分组中
var defaultGroup *entity.MdlModelGroups
err := dao.MdlModelGroups.Ctx(ctx).
Where("is_default", true).
Where("status", "active").
Scan(&defaultGroup)
if err != nil {
return nil, err
}
if defaultGroup == nil {
return nil, fmt.Errorf("默认模型分组不存在")
}
// 2. 查询模型(需在默认分组中)
var model *entity.MdlModels
err = dao.MdlModels.Ctx(ctx).
LeftJoin("mdl_group_models", "mdl_group_models.model_id = mdl_models.id").
Where("mdl_group_models.group_id", defaultGroup.Id).
Where("mdl_models.model_id", req.ModelId).
Where("mdl_models.status", "active").
Fields("mdl_models.*").
Scan(&model)
if err != nil {
return nil, err
}
if model == nil {
return nil, fmt.Errorf("模型不存在或未公开")
}
// 3. 查询价格
var pricing *entity.MdlPricing
err = dao.MdlPricing.Ctx(ctx).
Where("model_id", model.Id).
Where("billing_mode", "token").
Scan(&pricing)
if err != nil {
return nil, err
}
// 4. 转换为响应格式
item := s.convertToMarketplaceItem(ctx, model, pricing)
return &v1.MarketplaceDetailRes{
MarketplaceModelItem: item,
}, nil
}
// convertToMarketplaceItem 转换模型实体为响应格式
func (s *sTenant) convertToMarketplaceItem(ctx context.Context, model *entity.MdlModels, pricing *entity.MdlPricing) v1.MarketplaceModelItem {
item := v1.MarketplaceModelItem{
ModelId: model.ModelId,
ModelName: model.ModelName,
Category: model.Category,
Description: model.Description,
MaxContextTokens: model.MaxContextTokens,
MaxOutputTokens: model.MaxOutputTokens,
Tags: []string{},
Capabilities: g.Map{},
}
// 标签
if model.Tags != nil && len(model.Tags) > 0 {
item.Tags = model.Tags
}
// 能力标签(从 capabilities JSONB 解析)
if model.Capabilities != "" {
var capabilities map[string]interface{}
if err := gconv.Struct(model.Capabilities, &capabilities); err == nil {
item.Capabilities = capabilities
}
}
// 价格(数据库已存储每百万 token 的美元价格,直接返回)
if pricing != nil {
// input_price/output_price 数据库字段存储的就是每 1M token 价格(USD)
// 无需转换,直接使用
item.InputPrice = billing.InexactFloat64(pricing.InputPrice)
item.OutputPrice = billing.InexactFloat64(pricing.OutputPrice)
}
return item
}
-1
View File
@@ -42,5 +42,4 @@ type AudRequestLogs struct {
TaskResult any // 异步任务完成时上游返回的原始响应体
TaskUpstreamHeaders any // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
TaskCompletedAt *gtime.Time // 异步任务达到终态的时间
AttachmentIds any // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
}
@@ -0,0 +1,23 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// BilDailyUsageSummary is the golang structure of table bil_daily_usage_summary for DAO operations like Where/Data.
type BilDailyUsageSummary struct {
g.Meta `orm:"table:bil_daily_usage_summary, do:true"`
Id any //
TenantId any //
Date *gtime.Time //
TotalRequests any //
TotalTokens any //
TotalCost any //
CreatedAt *gtime.Time //
UpdatedAt *gtime.Time //
}
@@ -0,0 +1,23 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// BilMonthlyUsageSummary is the golang structure of table bil_monthly_usage_summary for DAO operations like Where/Data.
type BilMonthlyUsageSummary struct {
g.Meta `orm:"table:bil_monthly_usage_summary, do:true"`
Id any //
TenantId any //
Month *gtime.Time //
TotalRequests any //
TotalTokens any //
TotalCost any //
CreatedAt *gtime.Time //
UpdatedAt *gtime.Time //
}
+2 -2
View File
@@ -24,8 +24,8 @@ type BilUsageDaily struct {
OutputTokens any // 输出Token合计
TotalCost any // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
AccountCost any // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
CreatedAt *gtime.Time //
UpdatedAt *gtime.Time //
SumLatencyMs any // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
SumFirstTokenMs any // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
CreatedAt *gtime.Time //
UpdatedAt *gtime.Time //
}
+1 -1
View File
@@ -19,7 +19,7 @@ type MdlTenantModels struct {
Enabled any // 是否启用(禁用后该租户无法调用此模型)
CustomInputPrice *decimal.Decimal // 租户自定义输入价格(NULL 表示使用默认定价)
CustomOutputPrice *decimal.Decimal // 租户自定义输出价格(NULL 表示使用默认定价)
Multiplier any // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
Multiplier any // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 更新时间
BillingMode any // 覆盖模型计费方式(NULL表示跟随模型默认)
@@ -40,5 +40,4 @@ type AudRequestLogs struct {
TaskResult string `json:"task_result" orm:"task_result" description:"异步任务完成时上游返回的原始响应体"` // 异步任务完成时上游返回的原始响应体
TaskUpstreamHeaders string `json:"task_upstream_headers" orm:"task_upstream_headers" description:"异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)"` // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
TaskCompletedAt *gtime.Time `json:"task_completed_at" orm:"task_completed_at" description:"异步任务达到终态的时间"` // 异步任务达到终态的时间
AttachmentIds string `json:"attachment_ids" orm:"attachment_ids" description:"提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])"` // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
}
@@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
"github.com/shopspring/decimal"
)
// BilDailyUsageSummary is the golang structure for table bil_daily_usage_summary.
type BilDailyUsageSummary struct {
Id int64 `json:"id" orm:"id" description:""` //
TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` //
Date *gtime.Time `json:"date" orm:"date" description:""` //
TotalRequests int `json:"total_requests" orm:"total_requests" description:""` //
TotalTokens int64 `json:"total_tokens" orm:"total_tokens" description:""` //
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:""` //
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
}
@@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
"github.com/shopspring/decimal"
)
// BilMonthlyUsageSummary is the golang structure for table bil_monthly_usage_summary.
type BilMonthlyUsageSummary struct {
Id int64 `json:"id" orm:"id" description:""` //
TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` //
Month *gtime.Time `json:"month" orm:"month" description:""` //
TotalRequests int `json:"total_requests" orm:"total_requests" description:""` //
TotalTokens int64 `json:"total_tokens" orm:"total_tokens" description:""` //
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:""` //
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
}
+2 -2
View File
@@ -23,8 +23,8 @@ type BilUsageDaily struct {
OutputTokens int64 `json:"output_tokens" orm:"output_tokens" description:"输出Token合计"` // 输出Token合计
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:"客户侧成本合计(USD,源自 bil_usage_logs.total_cost)"` // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
AccountCost decimal.Decimal `json:"account_cost" orm:"account_cost" description:"上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)"` // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
SumLatencyMs int64 `json:"sum_latency_ms" orm:"sum_latency_ms" description:"总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)"` // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
SumFirstTokenMs int64 `json:"sum_first_token_ms" orm:"sum_first_token_ms" description:"首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)"` // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
}
+1 -1
View File
@@ -17,7 +17,7 @@ type MdlTenantModels struct {
Enabled bool `json:"enabled" orm:"enabled" description:"是否启用(禁用后该租户无法调用此模型)"` // 是否启用(禁用后该租户无法调用此模型)
CustomInputPrice *decimal.Decimal `json:"custom_input_price" orm:"custom_input_price" description:"租户自定义输入价格(NULL 表示使用默认定价)"` // 租户自定义输入价格(NULL 表示使用默认定价)
CustomOutputPrice *decimal.Decimal `json:"custom_output_price" orm:"custom_output_price" description:"租户自定义输出价格(NULL 表示使用默认定价)"` // 租户自定义输出价格(NULL 表示使用默认定价)
Multiplier decimal.Decimal `json:"multiplier" orm:"multiplier" description:"租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier"` // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
Multiplier decimal.Decimal `json:"multiplier" orm:"multiplier" description:"租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)"` // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间
BillingMode string `json:"billing_mode" orm:"billing_mode" description:"覆盖模型计费方式(NULL表示跟随模型默认)"` // 覆盖模型计费方式(NULL表示跟随模型默认)
+4
View File
@@ -125,6 +125,10 @@ type (
RequestClosure(ctx context.Context, req *v1.TenantRequestClosureReq) (*v1.TenantRequestClosureRes, error)
// CancelClosure 取消关户(仅 owner)
CancelClosure(ctx context.Context, req *v1.TenantCancelClosureReq) (*v1.TenantCancelClosureRes, error)
// GetModelList 获取模型广场列表(从默认模型分组加载)
GetModelList(ctx context.Context, req *v1.MarketplaceListReq) (*v1.MarketplaceListRes, error)
// GetModelDetail 获取模型详情
GetModelDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (*v1.MarketplaceDetailRes, error)
// ListMembers returns a paginated list of tenant members.
ListMembers(ctx context.Context, req *v1.TenantMemberListReq) (*v1.TenantMemberListRes, error)
// InviteMember generates an invitation link.
+36
View File
@@ -0,0 +1,36 @@
import request from '@/utils/request'
export interface MarketplaceModel {
model_id: string
model_name: string
category: string
description: string
max_context_tokens: number
max_output_tokens: number
input_price: number
output_price: number
tags: string[]
capabilities: Record<string, any>
}
export interface MarketplaceListParams {
keyword?: string
category?: string
page: number
page_size: number
}
export interface MarketplaceListResponse {
list: MarketplaceModel[]
total: number
page: number
page_size: number
}
export const getMarketplaceModels = (params: MarketplaceListParams) => {
return request.get<{ data: MarketplaceListResponse }>('/tenant/marketplace/models', { params }).then(res => res.data.data)
}
export const getMarketplaceModelDetail = (modelId: string) => {
return request.get<{ data: MarketplaceModel }>(`/tenant/marketplace/models/${modelId}`).then(res => res.data.data)
}
+6
View File
@@ -40,6 +40,12 @@ const tenantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/auth/JoinPage.vue'),
meta: { requiresAuth: false, title: '加入组织' },
},
{
path: '/marketplace',
name: 'Marketplace',
component: () => import('@/views/marketplace/MarketplacePage.vue'),
meta: { requiresAuth: false, title: '模型广场' },
},
{
path: '/tenant',
component: () => import('@/views/Layout.vue'),
+3 -1
View File
@@ -43,13 +43,15 @@ export interface FormatMoneyOptions {
* 统一金额格式化。默认 USD、6 位精度(与 NUMERIC(20,10) 对齐)。
* currency='CNY' 时用 ¥ 前缀 + 2 位精度(订单/充值层)。
* showSign 用 +/− 前缀(交易流水)。
* 自动去掉末尾的 0(如 $1.500000 → $1.5)。
*/
export function formatMoney(value: unknown, opts: FormatMoneyOptions = {}): string {
const { currency = 'USD', precision = 6, showSign = false } = opts
const num = Number(value ?? 0)
const symbol = currency === 'CNY' ? '¥' : '$'
const sign = showSign ? (num > 0 ? '+' : num < 0 ? '-' : '') : ''
return `${sign}${symbol}${Math.abs(num).toFixed(precision)}`
// 先 toFixed 确保精度,再用 Number 去掉末尾的 0
return `${sign}${symbol}${Number(Math.abs(num).toFixed(precision))}`
}
/** 统一日期格式化:ISO T 转空格、截断到分钟;空值显示 -- */
+9
View File
@@ -150,6 +150,8 @@ const PUBLIC_PATHS = [
// 找回密码流程无需登录态:发送验证码 / 重置密码
'/email/send-code',
'/email/reset-password',
// 模型广场(公开访问)
'/marketplace/',
]
function isPublicPath(url?: string): boolean {
@@ -240,6 +242,13 @@ request.interceptors.response.use(
async (error) => {
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean }
// 公开接口即使暂时返回 401,也不应触发刷新令牌或把访客踢到登录页。
// 交给调用页面展示自己的错误状态即可。
if (error.response?.status === 401 && isPublicPath(originalRequest?.url)) {
showRequestError(error, originalRequest)
return Promise.reject(error)
}
if (error.response?.status !== 401 || originalRequest._retry) {
// Extract unified error from response body
if (error.response?.data && typeof error.response.data === 'object' && 'code' in error.response.data) {
@@ -18,6 +18,12 @@
<span class="text-lg font-bold tracking-tight text-gray-900">{{ siteName }}</span>
</div>
<div class="flex items-center gap-2">
<router-link :to="{ name: 'Marketplace' }" class="btn-nav-link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 3.75H6.912a2.25 2.25 0 00-2.15 1.588L2.35 13.177a2.25 2.25 0 00-.1.661V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 00-2.15-1.588H15M2.25 13.5h3.86a2.25 2.25 0 012.012 1.244l.256.512a2.25 2.25 0 002.013 1.244h3.218a2.25 2.25 0 002.013-1.244l.256-.512a2.25 2.25 0 012.013-1.244h3.859M12 3v8.25m0 0l-3-3m3 3l3-3"/>
</svg>
<span>模型广场</span>
</router-link>
<button v-if="announcements.length" @click="openAnnouncements" class="btn-nav-announce" title="查看公告">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"/></svg>
</button>
@@ -532,6 +538,20 @@ const mockChartHeights = [45, 62, 38, 71, 55, 82, 67, 48, 73, 58, 90, 65, 52, 78
box-shadow: 0 4px 14px rgba(76,91,142,0.1), inset 0 1px 0 rgba(255,255,255,0.9);
}
/* Nav link — 模型广场链接 */
.btn-nav-link {
display: flex; align-items: center; gap: 6px;
padding: 8px 16px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.72);
font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease;
background: rgba(255,255,255,0.55); color: #64748b;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
text-decoration: none;
}
.btn-nav-link:hover {
background: rgba(255,255,255,0.9); color: #0d9488;
box-shadow: 0 4px 14px rgba(76,91,142,0.1), inset 0 1px 0 rgba(255,255,255,0.9);
}
/* ================================================
合并区 — 产品标题 + 场景展示
================================================ */
File diff suppressed because it is too large. Load diff