refactor(task):重构大模型异步任务日志功能
This commit is contained in:
1 parent
e53b5abc7f
commit
bc058cee45
28 files changed
+537
-429
No files matched your search
@@ -290,7 +290,7 @@ wallets 表包含 `frozen_balance` 字段,用于支付中/退款中的金额
|
||||
| `aud_` | 审计日志 | 三 | `aud_request_logs` `aud_operation_logs` `aud_sensitive_access_logs` `aud_login_history` |
|
||||
| `spt_` | 客户支持(工单+帮助+反馈) | 三/六 | `spt_tickets` `spt_replies` `spt_attachments` `spt_categories` `spt_articles` `spt_feedbacks` |
|
||||
| `ops_` | 运维监控 | 四 | `ops_alert_rules` `ops_alert_events` `ops_system_metrics` |
|
||||
| `tsk_` | 异步任务 | 四 | `tsk_tasks` `tsk_task_logs` `tsk_async_tasks` |
|
||||
| `tsk_` | 异步任务 | 四 | `tsk_tasks` `tsk_task_logs` `tsk_model_tasks` |
|
||||
| `fil_` | 文件存储 | 四 | `fil_files` |
|
||||
| `opn_` | 开放平台 + Webhook | 五 | `opn_apps` `opn_webhook_configs` `opn_webhook_events` `opn_webhook_delivery_logs` |
|
||||
| `clg_` | 更新日志 | 六 | `clg_changelogs` |
|
||||
|
||||
@@ -196,7 +196,6 @@ type IAdminV1 interface {
|
||||
TaskList(ctx context.Context, req *v1.TaskListReq) (res *v1.TaskListRes, err error)
|
||||
TaskDetail(ctx context.Context, req *v1.TaskDetailReq) (res *v1.TaskDetailRes, err error)
|
||||
TaskCancel(ctx context.Context, req *v1.TaskCancelReq) (res *v1.TaskCancelRes, err error)
|
||||
TaskRetry(ctx context.Context, req *v1.TaskRetryReq) (res *v1.TaskRetryRes, err error)
|
||||
TenantCreate(ctx context.Context, req *v1.TenantCreateReq) (res *v1.TenantCreateRes, err error)
|
||||
TenantList(ctx context.Context, req *v1.TenantListReq) (res *v1.TenantListRes, err error)
|
||||
TenantGet(ctx context.Context, req *v1.TenantGetReq) (res *v1.TenantGetRes, err error)
|
||||
|
||||
@@ -2,65 +2,55 @@ package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// === 任务管理 ===
|
||||
// === 大模型异步任务管理 ===
|
||||
|
||||
type TaskListReq struct {
|
||||
g.Meta `path:"/tasks" method:"get" mime:"json" tags:"管理后台-任务管理" summary:"任务列表"`
|
||||
g.Meta `path:"/tasks" method:"get" mime:"json" tags:"管理后台-任务管理" summary:"大模型异步任务列表"`
|
||||
Page int `json:"page" in:"query" d:"1" v:"min:1" dc:"页码"`
|
||||
PageSize int `json:"page_size" in:"query" d:"20" v:"min:1|max:100" dc:"每页数量"`
|
||||
Status string `json:"status" in:"query" dc:"筛选状态"`
|
||||
Handler string `json:"handler" in:"query" dc:"筛选handler"`
|
||||
Platform string `json:"platform" in:"query" dc:"筛选平台(sora/kling/midjourney/suno)"`
|
||||
}
|
||||
|
||||
type TaskItem struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Handler string `json:"handler"`
|
||||
Status string `json:"status"`
|
||||
MaxRetries int `json:"max_retries"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
FinishedAt string `json:"finished_at,omitempty"`
|
||||
ScheduledAt string `json:"scheduled_at,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
type ModelTaskItem struct {
|
||||
ID int64 `json:"id"`
|
||||
PublicTaskID string `json:"public_task_id"`
|
||||
Platform string `json:"platform"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Progress string `json:"progress"`
|
||||
ModelName string `json:"model_name"`
|
||||
FailReason string `json:"fail_reason,omitempty"`
|
||||
PreDeductAmount float64 `json:"pre_deduct_amount"`
|
||||
ActualCost float64 `json:"actual_cost"`
|
||||
BillingSettled bool `json:"billing_settled"`
|
||||
ResultURL string `json:"result_url,omitempty"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
SubmitTime string `json:"submit_time,omitempty"`
|
||||
FinishTime string `json:"finish_time,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type TaskListRes struct {
|
||||
List []TaskItem `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
List []ModelTaskItem `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type TaskDetailReq struct {
|
||||
g.Meta `path:"/tasks/{id}" method:"get" mime:"json" tags:"管理后台-任务管理" summary:"任务详情"`
|
||||
g.Meta `path:"/tasks/{id}" method:"get" mime:"json" tags:"管理后台-任务管理" summary:"大模型异步任务详情"`
|
||||
ID int64 `json:"id" in:"path" v:"required" dc:"任务ID"`
|
||||
}
|
||||
|
||||
type TaskLogItem struct {
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type TaskDetailRes struct {
|
||||
Task TaskItem `json:"task"`
|
||||
RecentLogs []TaskLogItem `json:"recent_logs"`
|
||||
Task ModelTaskItem `json:"task"`
|
||||
}
|
||||
|
||||
type TaskCancelReq struct {
|
||||
g.Meta `path:"/tasks/{id}/cancel" method:"post" mime:"json" tags:"管理后台-任务管理" summary:"取消任务"`
|
||||
g.Meta `path:"/tasks/{id}/cancel" method:"post" mime:"json" tags:"管理后台-任务管理" summary:"取消大模型异步任务"`
|
||||
ID int64 `json:"id" in:"path" v:"required" dc:"任务ID"`
|
||||
}
|
||||
|
||||
type TaskCancelRes struct{}
|
||||
|
||||
type TaskRetryReq struct {
|
||||
g.Meta `path:"/tasks/{id}/retry" method:"post" mime:"json" tags:"管理后台-任务管理" summary:"重试任务"`
|
||||
ID int64 `json:"id" in:"path" v:"required" dc:"任务ID"`
|
||||
}
|
||||
|
||||
type TaskRetryRes struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qianfree/team-api/api/admin/v1"
|
||||
"github.com/qianfree/team-api/internal/service"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) TaskRetry(ctx context.Context, req *v1.TaskRetryReq) (res *v1.TaskRetryRes, err error) {
|
||||
return service.Admin().TaskRetry(ctx, req)
|
||||
}
|
||||
@@ -29,9 +29,9 @@ type ChnChannelKeysColumns struct {
|
||||
LastUsedAt string // 最后使用时间
|
||||
LastError string // 最后一次错误信息
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
KeyType string // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌)
|
||||
TokenExpiresAt string // OAuth access_token 过期时间(仅 key_type=oauth 时有值)
|
||||
UpdatedAt string // 更新时间
|
||||
}
|
||||
|
||||
// chnChannelKeysColumns holds the columns for the table chn_channel_keys.
|
||||
@@ -44,9 +44,9 @@ var chnChannelKeysColumns = ChnChannelKeysColumns{
|
||||
LastUsedAt: "last_used_at",
|
||||
LastError: "last_error",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
KeyType: "key_type",
|
||||
TokenExpiresAt: "token_expires_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewChnChannelKeysDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -30,12 +30,12 @@ type MdlModelsColumns struct {
|
||||
MaxOutputTokens string // 最大输出 token 数
|
||||
Description string // 模型描述
|
||||
Tags string // 标签(如 reasoning、vision、function_calling)
|
||||
Capabilities string // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeprecatedAt string // 标记弃用的时间(NULL表示未弃用)
|
||||
SunsetDate string // 计划下线日期(到达后返回410 Gone,NULL表示未设置)
|
||||
ReplacementModel string // 推荐替代模型名(NULL表示无替代)
|
||||
Capabilities string // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
}
|
||||
|
||||
// mdlModelsColumns holds the columns for the table mdl_models.
|
||||
@@ -49,12 +49,12 @@ var mdlModelsColumns = MdlModelsColumns{
|
||||
MaxOutputTokens: "max_output_tokens",
|
||||
Description: "description",
|
||||
Tags: "tags",
|
||||
Capabilities: "capabilities",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeprecatedAt: "deprecated_at",
|
||||
SunsetDate: "sunset_date",
|
||||
ReplacementModel: "replacement_model",
|
||||
Capabilities: "capabilities",
|
||||
}
|
||||
|
||||
// NewMdlModelsDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -31,8 +31,8 @@ type NtfMessagesColumns struct {
|
||||
IsRead string // 是否已读:0=未读, 1=已读
|
||||
IsBroadcast string // 是否广播消息:0=个人消息, 1=广播消息
|
||||
Metadata string // 附加元数据(JSONB,如关联资源ID、跳转链接等)
|
||||
TargetRoles string // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
CreatedAt string // 创建时间
|
||||
TargetRoles string // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
}
|
||||
|
||||
// ntfMessagesColumns holds the columns for the table ntf_messages.
|
||||
@@ -47,8 +47,8 @@ var ntfMessagesColumns = NtfMessagesColumns{
|
||||
IsRead: "is_read",
|
||||
IsBroadcast: "is_broadcast",
|
||||
Metadata: "metadata",
|
||||
TargetRoles: "target_roles",
|
||||
CreatedAt: "created_at",
|
||||
TargetRoles: "target_roles",
|
||||
}
|
||||
|
||||
// NewNtfMessagesDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PlgExampleLogsDao is the data access object for the table plg_example_logs.
|
||||
type PlgExampleLogsDao 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 PlgExampleLogsColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// PlgExampleLogsColumns defines and stores column names for the table plg_example_logs.
|
||||
type PlgExampleLogsColumns struct {
|
||||
Id string //
|
||||
TenantId string //
|
||||
Action string //
|
||||
Detail string //
|
||||
CreatedAt string //
|
||||
}
|
||||
|
||||
// plgExampleLogsColumns holds the columns for the table plg_example_logs.
|
||||
var plgExampleLogsColumns = PlgExampleLogsColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
Action: "action",
|
||||
Detail: "detail",
|
||||
CreatedAt: "created_at",
|
||||
}
|
||||
|
||||
// NewPlgExampleLogsDao creates and returns a new DAO object for table data access.
|
||||
func NewPlgExampleLogsDao(handlers ...gdb.ModelHandler) *PlgExampleLogsDao {
|
||||
return &PlgExampleLogsDao{
|
||||
group: "default",
|
||||
table: "plg_example_logs",
|
||||
columns: plgExampleLogsColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *PlgExampleLogsDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *PlgExampleLogsDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *PlgExampleLogsDao) Columns() PlgExampleLogsColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *PlgExampleLogsDao) 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 *PlgExampleLogsDao) 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 *PlgExampleLogsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
+18
-18
@@ -11,16 +11,16 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// TskAsyncTasksDao is the data access object for the table tsk_async_tasks.
|
||||
type TskAsyncTasksDao struct {
|
||||
// TskModelTasksDao is the data access object for the table tsk_model_tasks.
|
||||
type TskModelTasksDao 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 TskAsyncTasksColumns // columns contains all the column names of Table for convenient usage.
|
||||
columns TskModelTasksColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// TskAsyncTasksColumns defines and stores column names for the table tsk_async_tasks.
|
||||
type TskAsyncTasksColumns struct {
|
||||
// TskModelTasksColumns defines and stores column names for the table tsk_model_tasks.
|
||||
type TskModelTasksColumns struct {
|
||||
Id string //
|
||||
PublicTaskId string //
|
||||
Platform string //
|
||||
@@ -47,8 +47,8 @@ type TskAsyncTasksColumns struct {
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// tskAsyncTasksColumns holds the columns for the table tsk_async_tasks.
|
||||
var tskAsyncTasksColumns = TskAsyncTasksColumns{
|
||||
// tskModelTasksColumns holds the columns for the table tsk_model_tasks.
|
||||
var tskModelTasksColumns = TskModelTasksColumns{
|
||||
Id: "id",
|
||||
PublicTaskId: "public_task_id",
|
||||
Platform: "platform",
|
||||
@@ -75,38 +75,38 @@ var tskAsyncTasksColumns = TskAsyncTasksColumns{
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewTskAsyncTasksDao creates and returns a new DAO object for table data access.
|
||||
func NewTskAsyncTasksDao(handlers ...gdb.ModelHandler) *TskAsyncTasksDao {
|
||||
return &TskAsyncTasksDao{
|
||||
// NewTskModelTasksDao creates and returns a new DAO object for table data access.
|
||||
func NewTskModelTasksDao(handlers ...gdb.ModelHandler) *TskModelTasksDao {
|
||||
return &TskModelTasksDao{
|
||||
group: "default",
|
||||
table: "tsk_async_tasks",
|
||||
columns: tskAsyncTasksColumns,
|
||||
table: "tsk_model_tasks",
|
||||
columns: tskModelTasksColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *TskAsyncTasksDao) DB() gdb.DB {
|
||||
func (dao *TskModelTasksDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *TskAsyncTasksDao) Table() string {
|
||||
func (dao *TskModelTasksDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *TskAsyncTasksDao) Columns() TskAsyncTasksColumns {
|
||||
func (dao *TskModelTasksDao) Columns() TskModelTasksColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *TskAsyncTasksDao) Group() string {
|
||||
func (dao *TskModelTasksDao) 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 *TskAsyncTasksDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
func (dao *TskModelTasksDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
@@ -120,6 +120,6 @@ func (dao *TskAsyncTasksDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *TskAsyncTasksDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
func (dao *TskModelTasksDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -8,20 +8,15 @@ import (
|
||||
"github.com/qianfree/team-api/internal/dao/internal"
|
||||
)
|
||||
|
||||
// internalTskAsyncTasksDao is an internal type for wrapping the internal DAO implementation.
|
||||
type internalTskAsyncTasksDao = *internal.TskAsyncTasksDao
|
||||
|
||||
// tskAsyncTasksDao is the data access object for the table tsk_async_tasks.
|
||||
// plgExampleLogsDao is the data access object for the table plg_example_logs.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type tskAsyncTasksDao struct {
|
||||
internalTskAsyncTasksDao
|
||||
type plgExampleLogsDao struct {
|
||||
*internal.PlgExampleLogsDao
|
||||
}
|
||||
|
||||
var (
|
||||
// TskAsyncTasks is a globally accessible object for table tsk_async_tasks operations.
|
||||
TskAsyncTasks = tskAsyncTasksDao{
|
||||
internal.NewTskAsyncTasksDao(),
|
||||
}
|
||||
// PlgExampleLogs is a globally accessible object for table plg_example_logs operations.
|
||||
PlgExampleLogs = plgExampleLogsDao{internal.NewPlgExampleLogsDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
// tskModelTasksDao is the data access object for the table tsk_model_tasks.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type tskModelTasksDao struct {
|
||||
*internal.TskModelTasksDao
|
||||
}
|
||||
|
||||
var (
|
||||
// TskModelTasks is a globally accessible object for table tsk_model_tasks operations.
|
||||
TskModelTasks = tskModelTasksDao{internal.NewTskModelTasksDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -14,28 +14,34 @@ import (
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
)
|
||||
|
||||
// TaskList 任务列表
|
||||
// TaskList 大模型异步任务列表
|
||||
func (s *sAdmin) TaskList(ctx context.Context, req *v1.TaskListReq) (*v1.TaskListRes, error) {
|
||||
m := dao.TskTasks.Ctx(ctx).OrderDesc("id")
|
||||
m := dao.TskModelTasks.Ctx(ctx).OrderDesc("id")
|
||||
if req.Status != "" {
|
||||
m = m.Where("status", req.Status)
|
||||
}
|
||||
if req.Handler != "" {
|
||||
m = m.Where("handler", req.Handler)
|
||||
if req.Platform != "" {
|
||||
m = m.Where("platform", req.Platform)
|
||||
}
|
||||
|
||||
var tasks []struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Handler string `json:"handler"`
|
||||
Status string `json:"status"`
|
||||
MaxRetries int `json:"max_retries"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
StartedAt *gtime.Time `json:"started_at"`
|
||||
FinishedAt *gtime.Time `json:"finished_at"`
|
||||
ScheduledAt *gtime.Time `json:"scheduled_at"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
PublicTaskId string `json:"public_task_id"`
|
||||
Platform string `json:"platform"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Progress string `json:"progress"`
|
||||
ModelName string `json:"model_name"`
|
||||
FailReason string `json:"fail_reason"`
|
||||
PreDeductAmount float64 `json:"pre_deduct_amount"`
|
||||
ActualCost float64 `json:"actual_cost"`
|
||||
BillingSettled bool `json:"billing_settled"`
|
||||
ResultUrl string `json:"result_url"`
|
||||
TenantId int64 `json:"tenant_id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
SubmitTime *gtime.Time `json:"submit_time"`
|
||||
FinishTime *gtime.Time `json:"finish_time"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
var total int
|
||||
@@ -44,26 +50,30 @@ func (s *sAdmin) TaskList(ctx context.Context, req *v1.TaskListReq) (*v1.TaskLis
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]v1.TaskItem, 0, len(tasks))
|
||||
list := make([]v1.ModelTaskItem, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
item := v1.TaskItem{
|
||||
ID: t.Id,
|
||||
Name: t.Name,
|
||||
Handler: t.Handler,
|
||||
Status: t.Status,
|
||||
MaxRetries: t.MaxRetries,
|
||||
RetryCount: t.RetryCount,
|
||||
ErrorMessage: t.ErrorMessage,
|
||||
CreatedAt: t.CreatedAt.Format("Y-m-d H:i:s"),
|
||||
item := v1.ModelTaskItem{
|
||||
ID: t.Id,
|
||||
PublicTaskID: t.PublicTaskId,
|
||||
Platform: t.Platform,
|
||||
Action: t.Action,
|
||||
Status: t.Status,
|
||||
Progress: t.Progress,
|
||||
ModelName: t.ModelName,
|
||||
FailReason: t.FailReason,
|
||||
PreDeductAmount: t.PreDeductAmount,
|
||||
ActualCost: t.ActualCost,
|
||||
BillingSettled: t.BillingSettled,
|
||||
ResultURL: t.ResultUrl,
|
||||
TenantID: t.TenantId,
|
||||
UserID: t.UserId,
|
||||
CreatedAt: t.CreatedAt.Format("Y-m-d H:i:s"),
|
||||
}
|
||||
if t.StartedAt != nil {
|
||||
item.StartedAt = t.StartedAt.Format("Y-m-d H:i:s")
|
||||
if t.SubmitTime != nil {
|
||||
item.SubmitTime = t.SubmitTime.Format("Y-m-d H:i:s")
|
||||
}
|
||||
if t.FinishedAt != nil {
|
||||
item.FinishedAt = t.FinishedAt.Format("Y-m-d H:i:s")
|
||||
}
|
||||
if t.ScheduledAt != nil {
|
||||
item.ScheduledAt = t.ScheduledAt.Format("Y-m-d H:i:s")
|
||||
if t.FinishTime != nil {
|
||||
item.FinishTime = t.FinishTime.Format("Y-m-d H:i:s")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
@@ -76,22 +86,30 @@ func (s *sAdmin) TaskList(ctx context.Context, req *v1.TaskListReq) (*v1.TaskLis
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TaskDetail 任务详情
|
||||
// TaskDetail 大模型异步任务详情
|
||||
func (s *sAdmin) TaskDetail(ctx context.Context, req *v1.TaskDetailReq) (*v1.TaskDetailRes, error) {
|
||||
var task *struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Handler string `json:"handler"`
|
||||
Status string `json:"status"`
|
||||
MaxRetries int `json:"max_retries"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
StartedAt *gtime.Time `json:"started_at"`
|
||||
FinishedAt *gtime.Time `json:"finished_at"`
|
||||
ScheduledAt *gtime.Time `json:"scheduled_at"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
PublicTaskId string `json:"public_task_id"`
|
||||
Platform string `json:"platform"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Progress string `json:"progress"`
|
||||
ModelName string `json:"model_name"`
|
||||
UpstreamModel string `json:"upstream_model"`
|
||||
FailReason string `json:"fail_reason"`
|
||||
PreDeductAmount float64 `json:"pre_deduct_amount"`
|
||||
ActualCost float64 `json:"actual_cost"`
|
||||
BillingSettled bool `json:"billing_settled"`
|
||||
ResultUrl string `json:"result_url"`
|
||||
TenantId int64 `json:"tenant_id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
SubmitTime *gtime.Time `json:"submit_time"`
|
||||
StartTime *gtime.Time `json:"start_time"`
|
||||
FinishTime *gtime.Time `json:"finish_time"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
err := dao.TskTasks.Ctx(ctx).Where("id", req.ID).Scan(&task)
|
||||
err := dao.TskModelTasks.Ctx(ctx).Where("id", req.ID).Scan(&task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,106 +117,56 @@ func (s *sAdmin) TaskDetail(ctx context.Context, req *v1.TaskDetailReq) (*v1.Tas
|
||||
return nil, gerror.NewCode(gcode.New(consts.CodeNotFound, consts.MsgNotFound, nil), consts.MsgNotFound)
|
||||
}
|
||||
|
||||
taskItem := v1.TaskItem{
|
||||
ID: task.Id,
|
||||
Name: task.Name,
|
||||
Handler: task.Handler,
|
||||
Status: task.Status,
|
||||
MaxRetries: task.MaxRetries,
|
||||
RetryCount: task.RetryCount,
|
||||
ErrorMessage: task.ErrorMessage,
|
||||
CreatedAt: task.CreatedAt.Format("Y-m-d H:i:s"),
|
||||
item := v1.ModelTaskItem{
|
||||
ID: task.Id,
|
||||
PublicTaskID: task.PublicTaskId,
|
||||
Platform: task.Platform,
|
||||
Action: task.Action,
|
||||
Status: task.Status,
|
||||
Progress: task.Progress,
|
||||
ModelName: task.ModelName,
|
||||
FailReason: task.FailReason,
|
||||
PreDeductAmount: task.PreDeductAmount,
|
||||
ActualCost: task.ActualCost,
|
||||
BillingSettled: task.BillingSettled,
|
||||
ResultURL: task.ResultUrl,
|
||||
TenantID: task.TenantId,
|
||||
UserID: task.UserId,
|
||||
CreatedAt: task.CreatedAt.Format("Y-m-d H:i:s"),
|
||||
}
|
||||
if task.StartedAt != nil {
|
||||
taskItem.StartedAt = task.StartedAt.Format("Y-m-d H:i:s")
|
||||
if task.SubmitTime != nil {
|
||||
item.SubmitTime = task.SubmitTime.Format("Y-m-d H:i:s")
|
||||
}
|
||||
if task.FinishedAt != nil {
|
||||
taskItem.FinishedAt = task.FinishedAt.Format("Y-m-d H:i:s")
|
||||
}
|
||||
if task.ScheduledAt != nil {
|
||||
taskItem.ScheduledAt = task.ScheduledAt.Format("Y-m-d H:i:s")
|
||||
}
|
||||
|
||||
// 查询最近日志
|
||||
var logs []struct {
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
_ = dao.TskTaskLogs.Ctx(ctx).
|
||||
Where("task_id", req.ID).
|
||||
OrderDesc("id").
|
||||
Limit(50).
|
||||
Scan(&logs)
|
||||
|
||||
logList := make([]v1.TaskLogItem, 0, len(logs))
|
||||
for _, l := range logs {
|
||||
logList = append(logList, v1.TaskLogItem{
|
||||
Level: l.Level,
|
||||
Message: l.Message,
|
||||
CreatedAt: l.CreatedAt.Format("Y-m-d H:i:s"),
|
||||
})
|
||||
if task.FinishTime != nil {
|
||||
item.FinishTime = task.FinishTime.Format("Y-m-d H:i:s")
|
||||
}
|
||||
|
||||
return &v1.TaskDetailRes{
|
||||
Task: taskItem,
|
||||
RecentLogs: logList,
|
||||
Task: item,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TaskCancel 取消任务
|
||||
// TaskCancel 取消大模型异步任务
|
||||
func (s *sAdmin) TaskCancel(ctx context.Context, req *v1.TaskCancelReq) (*v1.TaskCancelRes, error) {
|
||||
result, err := dao.TskTasks.Ctx(ctx).
|
||||
result, err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("id", req.ID).
|
||||
WhereIn("status", []string{"pending", "running"}).
|
||||
Data(do.TskTasks{
|
||||
Status: "cancelled",
|
||||
FinishedAt: gtime.Now(),
|
||||
WhereIn("status", []string{"NOT_START", "SUBMITTED", "IN_PROGRESS"}).
|
||||
Data(do.TskModelTasks{
|
||||
Status: "FAILURE",
|
||||
FailReason: "管理员手动取消",
|
||||
FinishTime: gtime.Now(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsAffected, _ := result.RowsAffected()
|
||||
if rowsAffected == 0 {
|
||||
return nil, gerror.New("任务无法取消(可能已完成或已取消)")
|
||||
return nil, gerror.New("任务无法取消(可能已完成或已失败)")
|
||||
}
|
||||
return &v1.TaskCancelRes{}, nil
|
||||
}
|
||||
|
||||
// TaskRetry 重试失败任务
|
||||
func (s *sAdmin) TaskRetry(ctx context.Context, req *v1.TaskRetryReq) (*v1.TaskRetryRes, error) {
|
||||
var task *struct {
|
||||
Id int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
MaxRetries int `json:"max_retries"`
|
||||
}
|
||||
err := dao.TskTasks.Ctx(ctx).Where("id", req.ID).Scan(&task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if task == nil {
|
||||
return nil, gerror.NewCode(gcode.New(consts.CodeNotFound, consts.MsgNotFound, nil), consts.MsgNotFound)
|
||||
}
|
||||
if task.Status != "failed" {
|
||||
return nil, gerror.NewCode(gcode.New(consts.CodeTaskNotRetryable, consts.MsgTaskNotRetryable, nil),
|
||||
consts.MsgTaskNotRetryable)
|
||||
}
|
||||
|
||||
_, err = dao.TskTasks.Ctx(ctx).Where("id", req.ID).Data(do.TskTasks{
|
||||
Status: "pending",
|
||||
RetryCount: task.RetryCount + 1,
|
||||
ErrorMessage: "",
|
||||
StartedAt: nil,
|
||||
FinishedAt: nil,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.TaskRetryRes{TaskID: task.Id}, nil
|
||||
}
|
||||
|
||||
// MarkStuckTasksFailed 标记卡住的任务为失败(定时任务)
|
||||
// MarkStuckTasksFailed 标记卡住的系统任务为失败(定时任务,操作 tsk_tasks 表)
|
||||
func MarkStuckTasksFailed(ctx context.Context) error {
|
||||
threshold := time.Now().Add(-30 * time.Minute)
|
||||
_, err := dao.TskTasks.Ctx(ctx).
|
||||
|
||||
@@ -30,7 +30,7 @@ func HasActiveTasks() bool {
|
||||
|
||||
// InitActiveCount initializes the counter from the database at startup.
|
||||
func InitActiveCount(ctx context.Context) {
|
||||
count, err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
count, err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("status NOT IN (?, ?)", "SUCCESS", "FAILURE").
|
||||
Count()
|
||||
if err != nil {
|
||||
|
||||
@@ -19,7 +19,7 @@ var DefaultAsyncProvider = &AsyncProvider{}
|
||||
|
||||
// CreateTask 创建异步任务记录
|
||||
func (p *AsyncProvider) CreateTask(ctx context.Context, task *common.AsyncTask) error {
|
||||
_, err := dao.TskAsyncTasks.Ctx(ctx).Insert(map[string]any{
|
||||
_, err := dao.TskModelTasks.Ctx(ctx).Insert(map[string]any{
|
||||
"public_task_id": task.PublicTaskID,
|
||||
"platform": task.Platform,
|
||||
"action": task.Action,
|
||||
@@ -49,7 +49,7 @@ func (p *AsyncProvider) CreateTask(ctx context.Context, task *common.AsyncTask)
|
||||
|
||||
// UpdateTask 更新任务记录
|
||||
func (p *AsyncProvider) UpdateTask(ctx context.Context, task *common.AsyncTask) error {
|
||||
_, err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
_, err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("id", task.ID).
|
||||
Update(map[string]any{
|
||||
"status": task.Status,
|
||||
@@ -71,7 +71,7 @@ func (p *AsyncProvider) UpdateTask(ctx context.Context, task *common.AsyncTask)
|
||||
|
||||
// UpdateTaskCAS CAS 状态更新
|
||||
func (p *AsyncProvider) UpdateTaskCAS(ctx context.Context, task *common.AsyncTask, oldStatus string) error {
|
||||
result, err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
result, err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("id", task.ID).
|
||||
Where("status", oldStatus).
|
||||
Update(map[string]any{
|
||||
@@ -124,7 +124,7 @@ func (p *AsyncProvider) GetTaskByPublicID(ctx context.Context, publicTaskID stri
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("public_task_id", publicTaskID).
|
||||
Scan(&row)
|
||||
if err != nil {
|
||||
@@ -188,7 +188,7 @@ func (p *AsyncProvider) GetTaskByPublicIDAndUser(ctx context.Context, publicTask
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("public_task_id", publicTaskID).
|
||||
Where("user_id", userID).
|
||||
Scan(&row)
|
||||
@@ -248,7 +248,7 @@ func (p *AsyncProvider) GetNonTerminalTasks(ctx context.Context, limit int) ([]*
|
||||
SubmitTime *time.Time `json:"submit_time"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("status NOT IN (?, ?)", "SUCCESS", "FAILURE").
|
||||
Order("submit_time ASC").
|
||||
Limit(limit).
|
||||
@@ -301,7 +301,7 @@ func (p *AsyncProvider) GetTimedOutTasks(ctx context.Context, cutoffUnix int64,
|
||||
PrivateData json.RawMessage `json:"private_data"`
|
||||
SubmitTime *time.Time `json:"submit_time"`
|
||||
}
|
||||
err := dao.TskAsyncTasks.Ctx(ctx).
|
||||
err := dao.TskModelTasks.Ctx(ctx).
|
||||
Where("status NOT IN (?, ?)", "SUCCESS", "FAILURE").
|
||||
Where("submit_time < ?", cutoffTime).
|
||||
Order("submit_time ASC").
|
||||
|
||||
@@ -20,7 +20,7 @@ type ChnChannelKeys struct {
|
||||
LastUsedAt *gtime.Time // 最后使用时间
|
||||
LastError any // 最后一次错误信息
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
KeyType any // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌)
|
||||
TokenExpiresAt *gtime.Time // OAuth access_token 过期时间(仅 key_type=oauth 时有值)
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
}
|
||||
@@ -21,10 +21,10 @@ type MdlModels struct {
|
||||
MaxOutputTokens any // 最大输出 token 数
|
||||
Description any // 模型描述
|
||||
Tags []string // 标签(如 reasoning、vision、function_calling)
|
||||
Capabilities any // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
DeprecatedAt *gtime.Time // 标记弃用的时间(NULL表示未弃用)
|
||||
SunsetDate *gtime.Time // 计划下线日期(到达后返回410 Gone,NULL表示未设置)
|
||||
ReplacementModel any // 推荐替代模型名(NULL表示无替代)
|
||||
Capabilities any // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
}
|
||||
@@ -22,6 +22,6 @@ type NtfMessages struct {
|
||||
IsRead any // 是否已读:0=未读, 1=已读
|
||||
IsBroadcast any // 是否广播消息:0=个人消息, 1=广播消息
|
||||
Metadata any // 附加元数据(JSONB,如关联资源ID、跳转链接等)
|
||||
TargetRoles any // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
TargetRoles any // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// PlgExampleLogs is the golang structure of table plg_example_logs for DAO operations like Where/Data.
|
||||
type PlgExampleLogs struct {
|
||||
g.Meta `orm:"table:plg_example_logs, do:true"`
|
||||
Id any //
|
||||
TenantId any //
|
||||
Action any //
|
||||
Detail any //
|
||||
CreatedAt *gtime.Time //
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// TskAsyncTasks is the golang structure of table tsk_async_tasks for DAO operations like Where/Data.
|
||||
type TskAsyncTasks struct {
|
||||
g.Meta `orm:"table:tsk_async_tasks, do:true"`
|
||||
// TskModelTasks is the golang structure of table tsk_model_tasks for DAO operations like Where/Data.
|
||||
type TskModelTasks struct {
|
||||
g.Meta `orm:"table:tsk_model_tasks, do:true"`
|
||||
Id any //
|
||||
PublicTaskId any //
|
||||
Platform any //
|
||||
@@ -18,7 +18,7 @@ type ChnChannelKeys struct {
|
||||
LastUsedAt *gtime.Time `json:"last_used_at" orm:"last_used_at" description:"最后使用时间"` // 最后使用时间
|
||||
LastError string `json:"last_error" orm:"last_error" description:"最后一次错误信息"` // 最后一次错误信息
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间
|
||||
KeyType string `json:"key_type" orm:"key_type" description:"Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌)"` // Key 类型:apikey(传统静态密钥)/ oauth(OAuth 令牌)
|
||||
TokenExpiresAt *gtime.Time `json:"token_expires_at" orm:"token_expires_at" description:"OAuth access_token 过期时间(仅 key_type=oauth 时有值)"` // OAuth access_token 过期时间(仅 key_type=oauth 时有值)
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间
|
||||
}
|
||||
@@ -19,10 +19,10 @@ type MdlModels struct {
|
||||
MaxOutputTokens int `json:"max_output_tokens" orm:"max_output_tokens" description:"最大输出 token 数"` // 最大输出 token 数
|
||||
Description string `json:"description" orm:"description" description:"模型描述"` // 模型描述
|
||||
Tags []string `json:"tags" orm:"tags" description:"标签(如 reasoning、vision、function_calling)"` // 标签(如 reasoning、vision、function_calling)
|
||||
Capabilities string `json:"capabilities" orm:"capabilities" description:"模型能力特性(如 vision、function_calling、reasoning 等)"` // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间
|
||||
DeprecatedAt *gtime.Time `json:"deprecated_at" orm:"deprecated_at" description:"标记弃用的时间(NULL表示未弃用)"` // 标记弃用的时间(NULL表示未弃用)
|
||||
SunsetDate *gtime.Time `json:"sunset_date" orm:"sunset_date" description:"计划下线日期(到达后返回410 Gone,NULL表示未设置)"` // 计划下线日期(到达后返回410 Gone,NULL表示未设置)
|
||||
ReplacementModel string `json:"replacement_model" orm:"replacement_model" description:"推荐替代模型名(NULL表示无替代)"` // 推荐替代模型名(NULL表示无替代)
|
||||
Capabilities string `json:"capabilities" orm:"capabilities" description:"模型能力特性(如 vision、function_calling、reasoning 等)"` // 模型能力特性(如 vision、function_calling、reasoning 等)
|
||||
}
|
||||
@@ -20,6 +20,6 @@ type NtfMessages struct {
|
||||
IsRead int `json:"is_read" orm:"is_read" description:"是否已读:0=未读, 1=已读"` // 是否已读:0=未读, 1=已读
|
||||
IsBroadcast int `json:"is_broadcast" orm:"is_broadcast" description:"是否广播消息:0=个人消息, 1=广播消息"` // 是否广播消息:0=个人消息, 1=广播消息
|
||||
Metadata string `json:"metadata" orm:"metadata" description:"附加元数据(JSONB,如关联资源ID、跳转链接等)"` // 附加元数据(JSONB,如关联资源ID、跳转链接等)
|
||||
TargetRoles string `json:"target_roles" orm:"target_roles" description:"目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)"` // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间
|
||||
TargetRoles string `json:"target_roles" orm:"target_roles" description:"目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)"` // 目标角色(NULL=全部角色,逗号分隔如 owner,admin 表示仅限这些角色)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// PlgExampleLogs is the golang structure for table plg_example_logs.
|
||||
type PlgExampleLogs struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` //
|
||||
Action string `json:"action" orm:"action" description:""` //
|
||||
Detail string `json:"detail" orm:"detail" description:""` //
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// TskAsyncTasks is the golang structure for table tsk_async_tasks.
|
||||
type TskAsyncTasks struct {
|
||||
// TskModelTasks is the golang structure for table tsk_model_tasks.
|
||||
type TskModelTasks struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
PublicTaskId string `json:"public_task_id" orm:"public_task_id" description:""` //
|
||||
Platform string `json:"platform" orm:"platform" description:""` //
|
||||
@@ -337,14 +337,12 @@ type (
|
||||
GetSettings(ctx context.Context, req *v1.AdminSettingsGetReq) (*v1.AdminSettingsGetRes, error)
|
||||
// UpdateSettings batch-updates settings for a given category.
|
||||
UpdateSettings(ctx context.Context, req *v1.AdminSettingsUpdateReq) (*v1.AdminSettingsUpdateRes, error)
|
||||
// TaskList 任务列表
|
||||
// TaskList 大模型异步任务列表
|
||||
TaskList(ctx context.Context, req *v1.TaskListReq) (*v1.TaskListRes, error)
|
||||
// TaskDetail 任务详情
|
||||
// TaskDetail 大模型异步任务详情
|
||||
TaskDetail(ctx context.Context, req *v1.TaskDetailReq) (*v1.TaskDetailRes, error)
|
||||
// TaskCancel 取消任务
|
||||
// TaskCancel 取消大模型异步任务
|
||||
TaskCancel(ctx context.Context, req *v1.TaskCancelReq) (*v1.TaskCancelRes, error)
|
||||
// TaskRetry 重试失败任务
|
||||
TaskRetry(ctx context.Context, req *v1.TaskRetryReq) (*v1.TaskRetryRes, error)
|
||||
// TenantSelect returns a lightweight paginated tenant list for dropdown selectors.
|
||||
TenantSelect(ctx context.Context, req *v1.TenantSelectReq) (*v1.TenantSelectRes, error)
|
||||
// CreateTenant creates a new tenant with its owner user and wallet.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
-- +goose Up
|
||||
-- 租户模型分配增加自定义缓存定价覆盖字段和阶梯定价支持
|
||||
|
||||
-- 缓存定价覆盖
|
||||
ALTER TABLE mdl_tenant_models
|
||||
ADD COLUMN IF NOT EXISTS custom_cache_read_price NUMERIC(20,10),
|
||||
ADD COLUMN IF NOT EXISTS custom_cache_creation_price NUMERIC(20,10);
|
||||
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_cache_read_price IS '自定义缓存读取价格($/1M token),NULL 表示使用基础定价';
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_cache_creation_price IS '自定义缓存创建价格($/1M token),NULL 表示使用基础定价';
|
||||
|
||||
-- 阶梯定价
|
||||
ALTER TABLE mdl_tenant_models
|
||||
ADD COLUMN IF NOT EXISTS custom_pricing_tiers JSONB;
|
||||
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_pricing_tiers IS '自定义阶梯定价(JSONB 数组),格式: [{"min_tokens":0,"max_tokens":100000,"input_price":0.5,"output_price":1.5,"cache_read_price":0.1,"cache_creation_price":0.2}]';
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE mdl_tenant_models
|
||||
DROP COLUMN IF EXISTS custom_cache_read_price,
|
||||
DROP COLUMN IF EXISTS custom_cache_creation_price,
|
||||
DROP COLUMN IF EXISTS custom_pricing_tiers;
|
||||
@@ -0,0 +1,97 @@
|
||||
-- +goose Up
|
||||
-- 租户模型自定义定价 + 异步任务表重命名
|
||||
|
||||
-- 1. 租户模型分配增加自定义缓存定价覆盖字段和阶梯定价支持
|
||||
ALTER TABLE mdl_tenant_models
|
||||
ADD COLUMN IF NOT EXISTS custom_cache_read_price NUMERIC(20,10),
|
||||
ADD COLUMN IF NOT EXISTS custom_cache_creation_price NUMERIC(20,10);
|
||||
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_cache_read_price IS '自定义缓存读取价格($/1M token),NULL 表示使用基础定价';
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_cache_creation_price IS '自定义缓存创建价格($/1M token),NULL 表示使用基础定价';
|
||||
|
||||
ALTER TABLE mdl_tenant_models
|
||||
ADD COLUMN IF NOT EXISTS custom_pricing_tiers JSONB;
|
||||
|
||||
COMMENT ON COLUMN mdl_tenant_models.custom_pricing_tiers IS '自定义阶梯定价(JSONB 数组),格式: [{"min_tokens":0,"max_tokens":100000,"input_price":0.5,"output_price":1.5,"cache_read_price":0.1,"cache_creation_price":0.2}]';
|
||||
|
||||
-- 2. 将 tsk_async_tasks 重命名为 tsk_model_tasks,并添加表和字段注释
|
||||
ALTER TABLE tsk_async_tasks RENAME TO tsk_model_tasks;
|
||||
|
||||
ALTER TABLE tsk_model_tasks RENAME CONSTRAINT uk_tsk_async_tasks_public_id TO uk_tsk_model_tasks_public_id;
|
||||
|
||||
ALTER INDEX idx_tsk_async_tasks_active RENAME TO idx_tsk_model_tasks_active;
|
||||
ALTER INDEX idx_tsk_async_tasks_status RENAME TO idx_tsk_model_tasks_status;
|
||||
ALTER INDEX idx_tsk_async_tasks_submit_time RENAME TO idx_tsk_model_tasks_submit_time;
|
||||
ALTER INDEX idx_tsk_async_tasks_user RENAME TO idx_tsk_model_tasks_user;
|
||||
|
||||
COMMENT ON TABLE tsk_model_tasks IS '异步模型任务表,存储 AI 模型异步调用任务(如视频生成、图像生成、音乐生成等)的状态和结果';
|
||||
COMMENT ON COLUMN tsk_model_tasks.id IS '主键ID';
|
||||
COMMENT ON COLUMN tsk_model_tasks.public_task_id IS '对外公开的任务ID(task_xxxxx 格式),用于 API 响应和客户端查询';
|
||||
COMMENT ON COLUMN tsk_model_tasks.platform IS '任务所属平台:sora、kling、suno、midjourney、volcengine';
|
||||
COMMENT ON COLUMN tsk_model_tasks.action IS '任务动作类型。通用:generate;Suno:music、lyrics;';
|
||||
COMMENT ON COLUMN tsk_model_tasks.status IS '任务状态:NOT_START-未开始、SUBMITTED-已提交、IN_PROGRESS-进行中、SUCCESS-成功、FAILURE-失败';
|
||||
COMMENT ON COLUMN tsk_model_tasks.progress IS '任务进度,字符串格式百分比(如 0%、50%、100%)';
|
||||
COMMENT ON COLUMN tsk_model_tasks.fail_reason IS '任务失败原因文本';
|
||||
COMMENT ON COLUMN tsk_model_tasks.tenant_id IS '发起任务的租户ID';
|
||||
COMMENT ON COLUMN tsk_model_tasks.user_id IS '发起任务的租户用户ID';
|
||||
COMMENT ON COLUMN tsk_model_tasks.api_key_id IS '调用方使用的 API Key ID';
|
||||
COMMENT ON COLUMN tsk_model_tasks.channel_id IS '转发请求的渠道ID';
|
||||
COMMENT ON COLUMN tsk_model_tasks.model_name IS '用户请求的模型名称(如 sora-1.0-turbo、midjourney 等)';
|
||||
COMMENT ON COLUMN tsk_model_tasks.upstream_model IS '上游供应商实际使用的模型名称,可能与请求模型不同';
|
||||
COMMENT ON COLUMN tsk_model_tasks.pre_deduct_amount IS '提交任务时的预扣金额(USD),任务完成后根据实际用量结算差额';
|
||||
COMMENT ON COLUMN tsk_model_tasks.actual_cost IS '任务实际消费金额(USD),成功时按此结算,失败时退还预扣金额';
|
||||
COMMENT ON COLUMN tsk_model_tasks.billing_settled IS '计费是否已结算(true 表示已完成预扣与实际金额的多退少补)';
|
||||
COMMENT ON COLUMN tsk_model_tasks.result_url IS '任务完成后的结果资源 URL(如生成的视频/图片/音频的下载地址)';
|
||||
COMMENT ON COLUMN tsk_model_tasks.data IS '上游供应商返回的完整响应数据(JSONB),可返回给用户查看';
|
||||
COMMENT ON COLUMN tsk_model_tasks.private_data IS '内部私有数据(JSONB),含 upstream_task_id 等敏感字段,用于轮询上游状态,不返回给用户';
|
||||
COMMENT ON COLUMN tsk_model_tasks.submit_time IS '任务提交到上游供应商的时间';
|
||||
COMMENT ON COLUMN tsk_model_tasks.start_time IS '上游供应商开始执行任务的时间';
|
||||
COMMENT ON COLUMN tsk_model_tasks.finish_time IS '任务完成(成功或失败)的时间';
|
||||
COMMENT ON COLUMN tsk_model_tasks.created_at IS '记录创建时间';
|
||||
COMMENT ON COLUMN tsk_model_tasks.updated_at IS '记录更新时间';
|
||||
|
||||
-- +goose Down
|
||||
-- 移除注释
|
||||
COMMENT ON TABLE tsk_model_tasks IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.public_task_id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.platform IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.action IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.status IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.progress IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.fail_reason IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.tenant_id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.user_id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.api_key_id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.channel_id IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.model_name IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.upstream_model IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.pre_deduct_amount IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.actual_cost IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.billing_settled IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.result_url IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.data IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.private_data IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.submit_time IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.start_time IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.finish_time IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.created_at IS NULL;
|
||||
COMMENT ON COLUMN tsk_model_tasks.updated_at IS NULL;
|
||||
|
||||
-- 恢复索引名
|
||||
ALTER INDEX idx_tsk_model_tasks_user RENAME TO idx_tsk_async_tasks_user;
|
||||
ALTER INDEX idx_tsk_model_tasks_submit_time RENAME TO idx_tsk_async_tasks_submit_time;
|
||||
ALTER INDEX idx_tsk_model_tasks_status RENAME TO idx_tsk_async_tasks_status;
|
||||
ALTER INDEX idx_tsk_model_tasks_active RENAME TO idx_tsk_async_tasks_active;
|
||||
|
||||
-- 恢复约束名
|
||||
ALTER TABLE tsk_model_tasks RENAME CONSTRAINT uk_tsk_model_tasks_public_id TO uk_tsk_async_tasks_public_id;
|
||||
|
||||
-- 恢复表名
|
||||
ALTER TABLE tsk_model_tasks RENAME TO tsk_async_tasks;
|
||||
|
||||
-- 移除定价字段
|
||||
ALTER TABLE mdl_tenant_models
|
||||
DROP COLUMN IF EXISTS custom_cache_read_price,
|
||||
DROP COLUMN IF EXISTS custom_cache_creation_price,
|
||||
DROP COLUMN IF EXISTS custom_pricing_tiers;
|
||||
@@ -18,38 +18,66 @@ const pagination = reactive({
|
||||
})
|
||||
|
||||
const filterStatus = ref<string | null>(null)
|
||||
const filterHandler = ref('')
|
||||
const filterDateRange = ref<string[]>([])
|
||||
const filterPlatform = ref<string | null>(null)
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '待执行', value: 'pending' },
|
||||
{ label: '执行中', value: 'running' },
|
||||
{ label: '成功', value: 'succeeded' },
|
||||
{ label: '失败', value: 'failed' },
|
||||
{ label: '已取消', value: 'cancelled' },
|
||||
{ label: '未开始', value: 'NOT_START' },
|
||||
{ label: '已提交', value: 'SUBMITTED' },
|
||||
{ label: '进行中', value: 'IN_PROGRESS' },
|
||||
{ label: '成功', value: 'SUCCESS' },
|
||||
{ label: '失败', value: 'FAILURE' },
|
||||
]
|
||||
|
||||
const platformOptions = [
|
||||
{ label: '全部平台', value: '' },
|
||||
{ label: 'Sora', value: 'sora' },
|
||||
{ label: 'Kling', value: 'kling' },
|
||||
{ label: 'Midjourney', value: 'midjourney' },
|
||||
{ label: 'Suno', value: 'suno' },
|
||||
]
|
||||
|
||||
const statusTagColor: Record<string, string | undefined> = {
|
||||
pending: 'gray',
|
||||
running: 'arcoblue',
|
||||
succeeded: 'green',
|
||||
failed: 'red',
|
||||
cancelled: 'orangered',
|
||||
NOT_START: 'gray',
|
||||
SUBMITTED: 'arcoblue',
|
||||
IN_PROGRESS: 'arcoblue',
|
||||
SUCCESS: 'green',
|
||||
FAILURE: 'red',
|
||||
}
|
||||
|
||||
const statusTagLabel: Record<string, string> = {
|
||||
pending: '待执行',
|
||||
running: '执行中',
|
||||
succeeded: '成功',
|
||||
failed: '失败',
|
||||
cancelled: '已取消',
|
||||
NOT_START: '未开始',
|
||||
SUBMITTED: '已提交',
|
||||
IN_PROGRESS: '进行中',
|
||||
SUCCESS: '成功',
|
||||
FAILURE: '失败',
|
||||
}
|
||||
|
||||
const platformLabel: Record<string, string> = {
|
||||
sora: 'Sora',
|
||||
kling: 'Kling',
|
||||
midjourney: 'Midjourney',
|
||||
suno: 'Suno',
|
||||
}
|
||||
|
||||
const columns: TableColumnData[] = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 70 },
|
||||
{ title: '任务名称', dataIndex: 'name', width: 180, ellipsis: true },
|
||||
{ title: 'Handler', dataIndex: 'handler', width: 220, ellipsis: true },
|
||||
{ title: '任务ID', dataIndex: 'public_task_id', width: 160, ellipsis: true, tooltip: true },
|
||||
{
|
||||
title: '平台',
|
||||
dataIndex: 'platform',
|
||||
width: 110,
|
||||
render({ record }) {
|
||||
return platformLabel[record.platform] || record.platform
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '动作',
|
||||
dataIndex: 'action',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
@@ -62,26 +90,43 @@ const columns: TableColumnData[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '重试',
|
||||
dataIndex: 'retry_count',
|
||||
title: '进度',
|
||||
dataIndex: 'progress',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: 'model_name',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '费用',
|
||||
dataIndex: 'actual_cost',
|
||||
width: 100,
|
||||
render({ record }) {
|
||||
return `${record.retry_count}/${record.max_retries}`
|
||||
if (record.billing_settled && record.actual_cost > 0) {
|
||||
return `$${record.actual_cost.toFixed(4)}`
|
||||
}
|
||||
if (record.pre_deduct_amount > 0) {
|
||||
return `$${record.pre_deduct_amount.toFixed(4)} (预扣)`
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
{ title: '开始时间', dataIndex: 'started_at', width: 170 },
|
||||
{ title: '完成时间', dataIndex: 'finished_at', width: 170 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', width: 170 },
|
||||
{ title: '提交时间', dataIndex: 'submit_time', width: 170 },
|
||||
{ title: '完成时间', dataIndex: 'finish_time', width: 170 },
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'actions',
|
||||
width: 160,
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
render({ record }) {
|
||||
const buttons: any[] = [
|
||||
h(Button, { size: 'small', type: 'text', onClick: () => openDetail(record) }, () => '详情'),
|
||||
]
|
||||
if (record.status === 'pending' || record.status === 'running') {
|
||||
if (record.status === 'NOT_START' || record.status === 'SUBMITTED' || record.status === 'IN_PROGRESS') {
|
||||
buttons.push(
|
||||
h(Popconfirm, {
|
||||
content: '确定取消该任务?',
|
||||
@@ -89,14 +134,6 @@ const columns: TableColumnData[] = [
|
||||
}, () => h(Button, { size: 'small', type: 'text', status: 'warning' }, () => '取消')),
|
||||
)
|
||||
}
|
||||
if (record.status === 'failed') {
|
||||
buttons.push(
|
||||
h(Popconfirm, {
|
||||
content: '确定重试该任务?',
|
||||
onOk: () => retryTask(record),
|
||||
}, () => h(Button, { size: 'small', type: 'text', status: 'success' }, () => '重试')),
|
||||
)
|
||||
}
|
||||
return h(Space, { size: 0 }, () => buttons)
|
||||
},
|
||||
},
|
||||
@@ -110,11 +147,7 @@ async function fetchData() {
|
||||
page_size: pagination.pageSize,
|
||||
}
|
||||
if (filterStatus.value) params.status = filterStatus.value
|
||||
if (filterHandler.value) params.handler = filterHandler.value
|
||||
if (filterDateRange.value.length === 2) {
|
||||
params.start_date = filterDateRange.value[0]
|
||||
params.end_date = filterDateRange.value[1]
|
||||
}
|
||||
if (filterPlatform.value) params.platform = filterPlatform.value
|
||||
const res: any = await request.get('/admin/tasks', { params })
|
||||
const raw = res.data?.data
|
||||
data.value = (raw?.list || []).filter(Boolean)
|
||||
@@ -134,8 +167,7 @@ function handleFilter() {
|
||||
|
||||
function resetFilter() {
|
||||
filterStatus.value = null
|
||||
filterHandler.value = ''
|
||||
filterDateRange.value = []
|
||||
filterPlatform.value = null
|
||||
pagination.current = 1
|
||||
fetchData()
|
||||
}
|
||||
@@ -150,36 +182,20 @@ async function cancelTask(row: any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function retryTask(row: any) {
|
||||
try {
|
||||
await request.post(`/admin/tasks/${row.id}/retry`)
|
||||
Message.success('任务已重新排队')
|
||||
fetchData()
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
}
|
||||
}
|
||||
|
||||
// === Detail Drawer ===
|
||||
const showDetail = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailData = ref<any>(null)
|
||||
const detailExecutions = ref<any[]>([])
|
||||
const detailLogs = ref<any[]>([])
|
||||
|
||||
async function openDetail(row: any) {
|
||||
detailData.value = row
|
||||
detailExecutions.value = []
|
||||
detailLogs.value = []
|
||||
showDetail.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const res: any = await request.get(`/admin/tasks/${row.id}`)
|
||||
const raw = res.data?.data
|
||||
if (raw) {
|
||||
detailData.value = raw.task || row
|
||||
detailExecutions.value = (raw.executions || []).filter(Boolean)
|
||||
detailLogs.value = (raw.recent_logs || []).filter(Boolean)
|
||||
if (raw?.task) {
|
||||
detailData.value = raw.task
|
||||
}
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
@@ -188,40 +204,6 @@ async function openDetail(row: any) {
|
||||
}
|
||||
}
|
||||
|
||||
const logLevelColor: Record<string, string | undefined> = {
|
||||
info: 'arcoblue',
|
||||
warn: 'orangered',
|
||||
error: 'red',
|
||||
}
|
||||
|
||||
const execColumns: TableColumnData[] = [
|
||||
{ title: '#', dataIndex: 'execution_order', width: 50 },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render({ record }) {
|
||||
return h(Tag, {
|
||||
color: statusTagColor[record.status],
|
||||
size: 'small',
|
||||
}, () => record.status)
|
||||
},
|
||||
},
|
||||
{ title: '开始时间', dataIndex: 'started_at', width: 170 },
|
||||
{ title: '完成时间', dataIndex: 'finished_at', width: 170 },
|
||||
{
|
||||
title: '耗时',
|
||||
dataIndex: 'duration_ms',
|
||||
width: 100,
|
||||
render({ record }) {
|
||||
if (!record.duration_ms) return '-'
|
||||
if (record.duration_ms < 1000) return `${record.duration_ms}ms`
|
||||
return `${(record.duration_ms / 1000).toFixed(1)}s`
|
||||
},
|
||||
},
|
||||
{ title: '错误信息', dataIndex: 'error_message', ellipsis: true },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
@@ -229,7 +211,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="page-table">
|
||||
<PageHeader title="任务日志" description="系统异步任务的执行记录与管理" />
|
||||
<PageHeader title="任务日志" description="大模型异步生成任务(视频/图片/音乐)的执行记录与管理" />
|
||||
|
||||
<!-- Filters -->
|
||||
<ACard :bordered="false" class="mb-4">
|
||||
@@ -242,18 +224,12 @@ onMounted(() => {
|
||||
style="width: 130px"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
<AInput
|
||||
v-model="filterHandler"
|
||||
placeholder="搜索 Handler..."
|
||||
<ASelect
|
||||
v-model="filterPlatform"
|
||||
:options="platformOptions"
|
||||
placeholder="平台"
|
||||
allow-clear
|
||||
style="width: 220px"
|
||||
@keydown.enter="handleFilter"
|
||||
@clear="handleFilter"
|
||||
/>
|
||||
<ARangePicker
|
||||
v-model="filterDateRange"
|
||||
style="width: 260px"
|
||||
format="YYYY-MM-DD"
|
||||
style="width: 130px"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
<AButton type="primary" @click="handleFilter">搜索</AButton>
|
||||
@@ -267,7 +243,7 @@ onMounted(() => {
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 1300 }"
|
||||
:scroll="{ x: 1400 }"
|
||||
:bordered="false"
|
||||
:stripe="true"
|
||||
:pagination="false"
|
||||
@@ -290,51 +266,51 @@ onMounted(() => {
|
||||
<ADrawer v-model:visible="showDetail" :width="620" title="任务详情">
|
||||
<ASpin :loading="detailLoading">
|
||||
<template v-if="detailData">
|
||||
<!-- Basic Info -->
|
||||
<h3 class="detail-section-title">基本信息</h3>
|
||||
<ADescriptions :column="2" bordered size="small">
|
||||
<ADescriptionsItem label="任务名称">{{ detailData.name }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="任务ID">{{ detailData.public_task_id }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="状态">
|
||||
<Tag :color="statusTagColor[detailData.status]" size="small">
|
||||
{{ statusTagLabel[detailData.status] || detailData.status }}
|
||||
</Tag>
|
||||
</ADescriptionsItem>
|
||||
<ADescriptionsItem label="Handler" :span="2">{{ detailData.handler }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="重试次数">{{ detailData.retry_count }} / {{ detailData.max_retries }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="计划时间">{{ detailData.scheduled_at || '-' }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="开始时间">{{ detailData.started_at || '-' }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="完成时间">{{ detailData.finished_at || '-' }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="平台">{{ platformLabel[detailData.platform] || detailData.platform }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="动作">{{ detailData.action }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="模型">{{ detailData.model_name }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="进度">{{ detailData.progress }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="预扣金额">
|
||||
<span v-if="detailData.pre_deduct_amount > 0">${{ detailData.pre_deduct_amount.toFixed(4) }}</span>
|
||||
<span v-else>-</span>
|
||||
</ADescriptionsItem>
|
||||
<ADescriptionsItem label="实际费用">
|
||||
<span v-if="detailData.billing_settled">${{ detailData.actual_cost.toFixed(4) }}</span>
|
||||
<span v-else>未结算</span>
|
||||
</ADescriptionsItem>
|
||||
<ADescriptionsItem label="提交时间">{{ detailData.submit_time || '-' }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="完成时间">{{ detailData.finish_time || '-' }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="创建时间">{{ detailData.created_at }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="错误信息" :span="2">
|
||||
<span v-if="detailData.error_message" style="color: var(--color-danger-6)">{{ detailData.error_message }}</span>
|
||||
<span v-else style="color: var(--color-text-4)">-</span>
|
||||
<ADescriptionsItem label="租户ID">{{ detailData.tenant_id }}</ADescriptionsItem>
|
||||
<ADescriptionsItem label="用户ID">{{ detailData.user_id }}</ADescriptionsItem>
|
||||
<ADescriptionsItem v-if="detailData.fail_reason" label="失败原因" :span="2">
|
||||
<span style="color: var(--color-danger-6)">{{ detailData.fail_reason }}</span>
|
||||
</ADescriptionsItem>
|
||||
</ADescriptions>
|
||||
|
||||
<!-- Executions -->
|
||||
<h3 class="detail-section-title">执行记录</h3>
|
||||
<ATable
|
||||
v-if="detailExecutions.length > 0"
|
||||
:columns="execColumns"
|
||||
:data="detailExecutions"
|
||||
:bordered="false"
|
||||
:stripe="true"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
row-key="execution_order"
|
||||
/>
|
||||
<p v-else style="color: var(--color-text-4); font-size: 13px;">暂无执行记录</p>
|
||||
|
||||
<!-- Logs -->
|
||||
<h3 class="detail-section-title">最近日志</h3>
|
||||
<div v-if="detailLogs.length > 0" class="log-list">
|
||||
<div v-for="log in detailLogs" :key="log.created_at + log.message" class="log-item">
|
||||
<Tag :color="logLevelColor[log.level]" size="small" class="log-level-tag">{{ log.level }}</Tag>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
<span class="log-time">{{ log.created_at }}</span>
|
||||
<!-- Result -->
|
||||
<template v-if="detailData.result_url">
|
||||
<h3 class="detail-section-title">结果</h3>
|
||||
<div class="result-preview">
|
||||
<img
|
||||
v-if="detailData.result_url && /\.(jpg|jpeg|png|gif|webp)(\?|$)/i.test(detailData.result_url)"
|
||||
:src="detailData.result_url"
|
||||
alt="任务结果"
|
||||
style="max-width: 100%; border-radius: 6px;"
|
||||
/>
|
||||
<a v-else :href="detailData.result_url" target="_blank" class="result-link">
|
||||
{{ detailData.result_url }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else style="color: var(--color-text-4); font-size: 13px;">暂无日志</p>
|
||||
</template>
|
||||
</template>
|
||||
</ASpin>
|
||||
</ADrawer>
|
||||
@@ -361,42 +337,14 @@ onMounted(() => {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.log-list {
|
||||
.result-preview {
|
||||
background: var(--color-fill-1);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 6px 4px;
|
||||
border-bottom: 1px solid var(--color-fill-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.log-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-level-tag {
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
color: var(--ta-text-primary);
|
||||
.result-link {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-4);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user