Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -2415,6 +2415,110 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/admin/system/client-customization/settings": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "获取客户端自定义设置",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.ClientCustomizationSettings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "更新客户端名称、英文名称、平台名和图标路径。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "更新客户端自定义设置",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "客户端自定义设置",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.ClientCustomizationSettingsInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.ClientCustomizationSettings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/admin/system/file-storage/channels": {
|
"/api/admin/system/file-storage/channels": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -5031,6 +5135,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/public/client-customization": {
|
||||||
|
"get": {
|
||||||
|
"description": "无需鉴权返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"summary": "获取公开客户端自定义设置",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.ClientCustomizationSettings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.ErrorEnvelope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/reranks": {
|
"/api/v1/reranks": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -10872,6 +11002,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"store.ClientCustomizationSettings": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"clientEnglishName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"iconPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"platformName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"store.ClientCustomizationSettingsInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"clientEnglishName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"clientName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"iconPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"platformName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"store.CreateAPIKeyInput": {
|
"store.CreateAPIKeyInput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1209,6 +1209,28 @@ definitions:
|
|||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
store.ClientCustomizationSettings:
|
||||||
|
properties:
|
||||||
|
clientEnglishName:
|
||||||
|
type: string
|
||||||
|
clientName:
|
||||||
|
type: string
|
||||||
|
iconPath:
|
||||||
|
type: string
|
||||||
|
platformName:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
store.ClientCustomizationSettingsInput:
|
||||||
|
properties:
|
||||||
|
clientEnglishName:
|
||||||
|
type: string
|
||||||
|
clientName:
|
||||||
|
type: string
|
||||||
|
iconPath:
|
||||||
|
type: string
|
||||||
|
platformName:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
store.CreateAPIKeyInput:
|
store.CreateAPIKeyInput:
|
||||||
properties:
|
properties:
|
||||||
expiresAt:
|
expiresAt:
|
||||||
@@ -4061,6 +4083,72 @@ paths:
|
|||||||
summary: 更新 Runner 策略
|
summary: 更新 Runner 策略
|
||||||
tags:
|
tags:
|
||||||
- runtime
|
- runtime
|
||||||
|
/api/admin/system/client-customization/settings:
|
||||||
|
get:
|
||||||
|
description: 返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/store.ClientCustomizationSettings'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取客户端自定义设置
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 更新客户端名称、英文名称、平台名和图标路径。
|
||||||
|
parameters:
|
||||||
|
- description: 客户端自定义设置
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/store.ClientCustomizationSettingsInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/store.ClientCustomizationSettings'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 更新客户端自定义设置
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
/api/admin/system/file-storage/channels:
|
/api/admin/system/file-storage/channels:
|
||||||
get:
|
get:
|
||||||
description: 返回所有未删除的文件存储通道,用于管理上传与生成资源回传策略。
|
description: 返回所有未删除的文件存储通道,用于管理上传与生成资源回传策略。
|
||||||
@@ -5741,6 +5829,23 @@ paths:
|
|||||||
summary: 列出目录供应商
|
summary: 列出目录供应商
|
||||||
tags:
|
tags:
|
||||||
- catalog
|
- catalog
|
||||||
|
/api/v1/public/client-customization:
|
||||||
|
get:
|
||||||
|
description: 无需鉴权返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/store.ClientCustomizationSettings'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.ErrorEnvelope'
|
||||||
|
summary: 获取公开客户端自定义设置
|
||||||
|
tags:
|
||||||
|
- system
|
||||||
/api/v1/reranks:
|
/api/v1/reranks:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
|||||||
mux.Handle("GET /api/v1/me", server.auth.Require(auth.PermissionBasic, http.HandlerFunc(server.me)))
|
mux.Handle("GET /api/v1/me", server.auth.Require(auth.PermissionBasic, http.HandlerFunc(server.me)))
|
||||||
mux.Handle("GET /api/v1/public/catalog/providers", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listCatalogProviders)))
|
mux.Handle("GET /api/v1/public/catalog/providers", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listCatalogProviders)))
|
||||||
mux.Handle("GET /api/v1/public/catalog/base-models", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listBaseModels)))
|
mux.Handle("GET /api/v1/public/catalog/base-models", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listBaseModels)))
|
||||||
|
mux.Handle("GET /api/v1/public/client-customization", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.getPublicClientCustomizationSettings)))
|
||||||
mux.Handle("GET /api/admin/catalog/providers", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listCatalogProviders)))
|
mux.Handle("GET /api/admin/catalog/providers", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listCatalogProviders)))
|
||||||
mux.Handle("POST /api/admin/catalog/providers", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createCatalogProvider)))
|
mux.Handle("POST /api/admin/catalog/providers", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createCatalogProvider)))
|
||||||
mux.Handle("PATCH /api/admin/catalog/providers/{providerID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateCatalogProvider)))
|
mux.Handle("PATCH /api/admin/catalog/providers/{providerID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateCatalogProvider)))
|
||||||
@@ -133,6 +134,8 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
|||||||
mux.Handle("GET /api/admin/config/network-proxy", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.getNetworkProxyConfig)))
|
mux.Handle("GET /api/admin/config/network-proxy", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.getNetworkProxyConfig)))
|
||||||
mux.Handle("GET /api/admin/system/file-storage/settings", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.getFileStorageSettings)))
|
mux.Handle("GET /api/admin/system/file-storage/settings", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.getFileStorageSettings)))
|
||||||
mux.Handle("PATCH /api/admin/system/file-storage/settings", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateFileStorageSettings)))
|
mux.Handle("PATCH /api/admin/system/file-storage/settings", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateFileStorageSettings)))
|
||||||
|
mux.Handle("GET /api/admin/system/client-customization/settings", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.getClientCustomizationSettings)))
|
||||||
|
mux.Handle("PATCH /api/admin/system/client-customization/settings", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateClientCustomizationSettings)))
|
||||||
mux.Handle("GET /api/admin/system/file-storage/channels", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listFileStorageChannels)))
|
mux.Handle("GET /api/admin/system/file-storage/channels", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listFileStorageChannels)))
|
||||||
mux.Handle("POST /api/admin/system/file-storage/channels", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createFileStorageChannel)))
|
mux.Handle("POST /api/admin/system/file-storage/channels", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createFileStorageChannel)))
|
||||||
mux.Handle("PATCH /api/admin/system/file-storage/channels/{channelID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateFileStorageChannel)))
|
mux.Handle("PATCH /api/admin/system/file-storage/channels/{channelID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateFileStorageChannel)))
|
||||||
|
|||||||
@@ -83,6 +83,76 @@ func (s *Server) updateFileStorageSettings(w http.ResponseWriter, r *http.Reques
|
|||||||
writeJSON(w, http.StatusOK, settings)
|
writeJSON(w, http.StatusOK, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getClientCustomizationSettings godoc
|
||||||
|
// @Summary 获取客户端自定义设置
|
||||||
|
// @Description 返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。
|
||||||
|
// @Tags system
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Success 200 {object} store.ClientCustomizationSettings
|
||||||
|
// @Failure 401 {object} ErrorEnvelope
|
||||||
|
// @Failure 403 {object} ErrorEnvelope
|
||||||
|
// @Failure 500 {object} ErrorEnvelope
|
||||||
|
// @Router /api/admin/system/client-customization/settings [get]
|
||||||
|
func (s *Server) getClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.writeClientCustomizationSettings(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPublicClientCustomizationSettings godoc
|
||||||
|
// @Summary 获取公开客户端自定义设置
|
||||||
|
// @Description 无需鉴权返回客户端名称、英文名称、平台名和图标路径;数据库对象尚未创建时返回默认设置。
|
||||||
|
// @Tags system
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} store.ClientCustomizationSettings
|
||||||
|
// @Failure 500 {object} ErrorEnvelope
|
||||||
|
// @Router /api/v1/public/client-customization [get]
|
||||||
|
func (s *Server) getPublicClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.writeClientCustomizationSettings(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) writeClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
settings, err := s.store.GetClientCustomizationSettings(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
if store.IsUndefinedDatabaseObject(err) {
|
||||||
|
writeJSON(w, http.StatusOK, store.DefaultClientCustomizationSettings())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.logger.Error("get client customization settings failed", "error", err)
|
||||||
|
writeError(w, http.StatusInternalServerError, "get client customization settings failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateClientCustomizationSettings godoc
|
||||||
|
// @Summary 更新客户端自定义设置
|
||||||
|
// @Description 更新客户端名称、英文名称、平台名和图标路径。
|
||||||
|
// @Tags system
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param body body store.ClientCustomizationSettingsInput true "客户端自定义设置"
|
||||||
|
// @Success 200 {object} store.ClientCustomizationSettings
|
||||||
|
// @Failure 400 {object} ErrorEnvelope
|
||||||
|
// @Failure 401 {object} ErrorEnvelope
|
||||||
|
// @Failure 403 {object} ErrorEnvelope
|
||||||
|
// @Failure 500 {object} ErrorEnvelope
|
||||||
|
// @Router /api/admin/system/client-customization/settings [patch]
|
||||||
|
func (s *Server) updateClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var input store.ClientCustomizationSettingsInput
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settings, err := s.store.UpdateClientCustomizationSettings(r.Context(), input)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("update client customization settings failed", "error", err)
|
||||||
|
writeError(w, http.StatusInternalServerError, "update client customization settings failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, settings)
|
||||||
|
}
|
||||||
|
|
||||||
// createFileStorageChannel godoc
|
// createFileStorageChannel godoc
|
||||||
// @Summary 创建文件存储通道
|
// @Summary 创建文件存储通道
|
||||||
// @Description 创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。
|
// @Description 创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const SystemSettingFileStorage = "file_storage"
|
const SystemSettingFileStorage = "file_storage"
|
||||||
|
const SystemSettingClientCustomization = "client_customization"
|
||||||
|
|
||||||
const fileStorageChannelColumns = `
|
const fileStorageChannelColumns = `
|
||||||
id::text, channel_key, name, provider, COALESCE(upload_url, ''), credentials,
|
id::text, channel_key, name, provider, COALESCE(upload_url, ''), credentials,
|
||||||
@@ -72,6 +73,20 @@ type FileStorageSettingsInput struct {
|
|||||||
ResultUploadPolicy string `json:"resultUploadPolicy"`
|
ResultUploadPolicy string `json:"resultUploadPolicy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientCustomizationSettings struct {
|
||||||
|
ClientName string `json:"clientName"`
|
||||||
|
ClientEnglishName string `json:"clientEnglishName"`
|
||||||
|
PlatformName string `json:"platformName"`
|
||||||
|
IconPath string `json:"iconPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCustomizationSettingsInput struct {
|
||||||
|
ClientName *string `json:"clientName"`
|
||||||
|
ClientEnglishName *string `json:"clientEnglishName"`
|
||||||
|
PlatformName *string `json:"platformName"`
|
||||||
|
IconPath *string `json:"iconPath"`
|
||||||
|
}
|
||||||
|
|
||||||
type fileStorageChannelScanner interface {
|
type fileStorageChannelScanner interface {
|
||||||
Scan(dest ...any) error
|
Scan(dest ...any) error
|
||||||
}
|
}
|
||||||
@@ -348,6 +363,15 @@ func DefaultFileStorageSettings() FileStorageSettings {
|
|||||||
return FileStorageSettings{ResultUploadPolicy: FileStorageResultUploadPolicyDefault}
|
return FileStorageSettings{ResultUploadPolicy: FileStorageResultUploadPolicyDefault}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultClientCustomizationSettings() ClientCustomizationSettings {
|
||||||
|
return ClientCustomizationSettings{
|
||||||
|
ClientName: "EasyAI AI Gateway",
|
||||||
|
ClientEnglishName: "EasyAI AI Gateway",
|
||||||
|
PlatformName: "EasyAI",
|
||||||
|
IconPath: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetFileStorageSettings(ctx context.Context) (FileStorageSettings, error) {
|
func (s *Store) GetFileStorageSettings(ctx context.Context) (FileStorageSettings, error) {
|
||||||
var value []byte
|
var value []byte
|
||||||
err := s.pool.QueryRow(ctx, `
|
err := s.pool.QueryRow(ctx, `
|
||||||
@@ -380,13 +404,53 @@ func (s *Store) UpdateFileStorageSettings(ctx context.Context, input FileStorage
|
|||||||
return fileStorageSettingsFromValue(decodeObject(saved)), nil
|
return fileStorageSettingsFromValue(decodeObject(saved)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetClientCustomizationSettings(ctx context.Context) (ClientCustomizationSettings, error) {
|
||||||
|
var value []byte
|
||||||
|
err := s.pool.QueryRow(ctx, `
|
||||||
|
SELECT value
|
||||||
|
FROM system_settings
|
||||||
|
WHERE setting_key = $1`, SystemSettingClientCustomization).Scan(&value)
|
||||||
|
if err != nil {
|
||||||
|
if IsNotFound(err) {
|
||||||
|
return DefaultClientCustomizationSettings(), nil
|
||||||
|
}
|
||||||
|
return ClientCustomizationSettings{}, err
|
||||||
|
}
|
||||||
|
return clientCustomizationSettingsFromValue(decodeObject(value)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateClientCustomizationSettings(ctx context.Context, input ClientCustomizationSettingsInput) (ClientCustomizationSettings, error) {
|
||||||
|
current, err := s.GetClientCustomizationSettings(ctx)
|
||||||
|
if err != nil && !IsUndefinedDatabaseObject(err) {
|
||||||
|
return ClientCustomizationSettings{}, err
|
||||||
|
}
|
||||||
|
settings := normalizeClientCustomizationSettings(input, current)
|
||||||
|
value, _ := json.Marshal(settings)
|
||||||
|
var saved []byte
|
||||||
|
err = s.upsertSystemSetting(ctx, SystemSettingClientCustomization, value, &saved)
|
||||||
|
if err != nil && IsUndefinedDatabaseObject(err) {
|
||||||
|
if ensureErr := s.ensureSystemSettingsTable(ctx); ensureErr != nil {
|
||||||
|
return ClientCustomizationSettings{}, ensureErr
|
||||||
|
}
|
||||||
|
err = s.upsertSystemSetting(ctx, SystemSettingClientCustomization, value, &saved)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return ClientCustomizationSettings{}, err
|
||||||
|
}
|
||||||
|
return clientCustomizationSettingsFromValue(decodeObject(saved)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) upsertFileStorageSettings(ctx context.Context, value []byte, saved *[]byte) error {
|
func (s *Store) upsertFileStorageSettings(ctx context.Context, value []byte, saved *[]byte) error {
|
||||||
|
return s.upsertSystemSetting(ctx, SystemSettingFileStorage, value, saved)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) upsertSystemSetting(ctx context.Context, settingKey string, value []byte, saved *[]byte) error {
|
||||||
return s.pool.QueryRow(ctx, `
|
return s.pool.QueryRow(ctx, `
|
||||||
INSERT INTO system_settings (setting_key, value)
|
INSERT INTO system_settings (setting_key, value)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
ON CONFLICT (setting_key)
|
ON CONFLICT (setting_key)
|
||||||
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
|
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
|
||||||
RETURNING value`, SystemSettingFileStorage, value).Scan(saved)
|
RETURNING value`, settingKey, value).Scan(saved)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ensureSystemSettingsTable(ctx context.Context) error {
|
func (s *Store) ensureSystemSettingsTable(ctx context.Context) error {
|
||||||
@@ -409,6 +473,53 @@ func fileStorageSettingsFromValue(value map[string]any) FileStorageSettings {
|
|||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clientCustomizationSettingsFromValue(value map[string]any) ClientCustomizationSettings {
|
||||||
|
settings := DefaultClientCustomizationSettings()
|
||||||
|
if value == nil {
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
if clientName := stringFromAny(value["clientName"]); clientName != "" {
|
||||||
|
settings.ClientName = clientName
|
||||||
|
}
|
||||||
|
if clientEnglishName := stringFromAny(value["clientEnglishName"]); clientEnglishName != "" {
|
||||||
|
settings.ClientEnglishName = clientEnglishName
|
||||||
|
}
|
||||||
|
if platformName := stringFromAny(value["platformName"]); platformName != "" {
|
||||||
|
settings.PlatformName = platformName
|
||||||
|
}
|
||||||
|
settings.IconPath = stringFromAny(value["iconPath"])
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeClientCustomizationSettings(input ClientCustomizationSettingsInput, current ClientCustomizationSettings) ClientCustomizationSettings {
|
||||||
|
settings := current
|
||||||
|
if settings.ClientName == "" && settings.ClientEnglishName == "" && settings.PlatformName == "" {
|
||||||
|
settings = DefaultClientCustomizationSettings()
|
||||||
|
}
|
||||||
|
if input.ClientName != nil {
|
||||||
|
settings.ClientName = strings.TrimSpace(*input.ClientName)
|
||||||
|
}
|
||||||
|
if input.ClientEnglishName != nil {
|
||||||
|
settings.ClientEnglishName = strings.TrimSpace(*input.ClientEnglishName)
|
||||||
|
}
|
||||||
|
if input.PlatformName != nil {
|
||||||
|
settings.PlatformName = strings.TrimSpace(*input.PlatformName)
|
||||||
|
}
|
||||||
|
if input.IconPath != nil {
|
||||||
|
settings.IconPath = strings.TrimSpace(*input.IconPath)
|
||||||
|
}
|
||||||
|
if settings.ClientName == "" {
|
||||||
|
settings.ClientName = DefaultClientCustomizationSettings().ClientName
|
||||||
|
}
|
||||||
|
if settings.ClientEnglishName == "" {
|
||||||
|
settings.ClientEnglishName = DefaultClientCustomizationSettings().ClientEnglishName
|
||||||
|
}
|
||||||
|
if settings.PlatformName == "" {
|
||||||
|
settings.PlatformName = DefaultClientCustomizationSettings().PlatformName
|
||||||
|
}
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
func NormalizeFileStorageResultUploadPolicy(policy string) string {
|
func NormalizeFileStorageResultUploadPolicy(policy string) string {
|
||||||
normalized := strings.ToLower(strings.TrimSpace(policy))
|
normalized := strings.ToLower(strings.TrimSpace(policy))
|
||||||
normalized = strings.ReplaceAll(normalized, "-", "_")
|
normalized = strings.ReplaceAll(normalized, "-", "_")
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
INSERT INTO system_settings (setting_key, value)
|
||||||
|
VALUES (
|
||||||
|
'client_customization',
|
||||||
|
'{"clientName":"EasyAI AI Gateway","clientEnglishName":"EasyAI AI Gateway","platformName":"EasyAI","iconPath":""}'::jsonb
|
||||||
|
)
|
||||||
|
ON CONFLICT (setting_key) DO NOTHING;
|
||||||
+28
-2
@@ -2,6 +2,8 @@ import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
|
|||||||
import type {
|
import type {
|
||||||
BaseModelCatalogItem,
|
BaseModelCatalogItem,
|
||||||
CatalogProvider,
|
CatalogProvider,
|
||||||
|
ClientCustomizationSettings,
|
||||||
|
ClientCustomizationSettingsUpdateRequest,
|
||||||
FileStorageChannel,
|
FileStorageChannel,
|
||||||
FileStorageSettings,
|
FileStorageSettings,
|
||||||
FileStorageSettingsUpdateRequest,
|
FileStorageSettingsUpdateRequest,
|
||||||
@@ -52,6 +54,7 @@ import {
|
|||||||
deleteTenant,
|
deleteTenant,
|
||||||
deleteUserGroup,
|
deleteUserGroup,
|
||||||
GatewayApiError,
|
GatewayApiError,
|
||||||
|
getClientCustomizationSettings,
|
||||||
getHealth,
|
getHealth,
|
||||||
listFileStorageChannels,
|
listFileStorageChannels,
|
||||||
getCurrentUser,
|
getCurrentUser,
|
||||||
@@ -92,6 +95,7 @@ import {
|
|||||||
type HealthResponse,
|
type HealthResponse,
|
||||||
updateAccessRule,
|
updateAccessRule,
|
||||||
updateApiKeyScopes,
|
updateApiKeyScopes,
|
||||||
|
updateClientCustomizationSettings,
|
||||||
updateFileStorageChannel,
|
updateFileStorageChannel,
|
||||||
updateFileStorageSettings,
|
updateFileStorageSettings,
|
||||||
updateGatewayUser,
|
updateGatewayUser,
|
||||||
@@ -154,6 +158,7 @@ type DataKey =
|
|||||||
| 'playgroundModels'
|
| 'playgroundModels'
|
||||||
| 'modelCatalog'
|
| 'modelCatalog'
|
||||||
| 'networkProxyConfig'
|
| 'networkProxyConfig'
|
||||||
|
| 'clientCustomizationSettings'
|
||||||
| 'fileStorageChannels'
|
| 'fileStorageChannels'
|
||||||
| 'fileStorageSettings'
|
| 'fileStorageSettings'
|
||||||
| 'platforms'
|
| 'platforms'
|
||||||
@@ -202,6 +207,7 @@ export function App() {
|
|||||||
});
|
});
|
||||||
const [playgroundModels, setPlaygroundModels] = useState<PlatformModel[]>([]);
|
const [playgroundModels, setPlaygroundModels] = useState<PlatformModel[]>([]);
|
||||||
const [networkProxyConfig, setNetworkProxyConfig] = useState<GatewayNetworkProxyConfig | null>(null);
|
const [networkProxyConfig, setNetworkProxyConfig] = useState<GatewayNetworkProxyConfig | null>(null);
|
||||||
|
const [clientCustomizationSettings, setClientCustomizationSettings] = useState<ClientCustomizationSettings | null>(null);
|
||||||
const [fileStorageChannels, setFileStorageChannels] = useState<FileStorageChannel[]>([]);
|
const [fileStorageChannels, setFileStorageChannels] = useState<FileStorageChannel[]>([]);
|
||||||
const [fileStorageSettings, setFileStorageSettings] = useState<FileStorageSettings | null>(null);
|
const [fileStorageSettings, setFileStorageSettings] = useState<FileStorageSettings | null>(null);
|
||||||
const [providers, setProviders] = useState<CatalogProvider[]>([]);
|
const [providers, setProviders] = useState<CatalogProvider[]>([]);
|
||||||
@@ -335,6 +341,7 @@ export function App() {
|
|||||||
auditLogs,
|
auditLogs,
|
||||||
apiKeys,
|
apiKeys,
|
||||||
baseModels,
|
baseModels,
|
||||||
|
clientCustomizationSettings,
|
||||||
currentUser,
|
currentUser,
|
||||||
currentUserGroups,
|
currentUserGroups,
|
||||||
fileStorageChannels,
|
fileStorageChannels,
|
||||||
@@ -358,7 +365,7 @@ export function App() {
|
|||||||
users,
|
users,
|
||||||
walletAccounts,
|
walletAccounts,
|
||||||
walletTransactions,
|
walletTransactions,
|
||||||
}), [accessRules, apiKeys, auditLogs, baseModels, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
}), [accessRules, apiKeys, auditLogs, baseModels, clientCustomizationSettings, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
||||||
|
|
||||||
async function refresh(nextToken = token) {
|
async function refresh(nextToken = token) {
|
||||||
await ensureRouteData(nextToken, true);
|
await ensureRouteData(nextToken, true);
|
||||||
@@ -438,6 +445,9 @@ export function App() {
|
|||||||
case 'networkProxyConfig':
|
case 'networkProxyConfig':
|
||||||
setNetworkProxyConfig(await getNetworkProxyConfig(nextToken));
|
setNetworkProxyConfig(await getNetworkProxyConfig(nextToken));
|
||||||
return;
|
return;
|
||||||
|
case 'clientCustomizationSettings':
|
||||||
|
setClientCustomizationSettings(await getClientCustomizationSettings(nextToken));
|
||||||
|
return;
|
||||||
case 'fileStorageChannels':
|
case 'fileStorageChannels':
|
||||||
setFileStorageChannels((await listFileStorageChannels(nextToken)).items);
|
setFileStorageChannels((await listFileStorageChannels(nextToken)).items);
|
||||||
return;
|
return;
|
||||||
@@ -966,6 +976,21 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveClientCustomizationSettings(input: ClientCustomizationSettingsUpdateRequest) {
|
||||||
|
setCoreState('loading');
|
||||||
|
setCoreMessage('');
|
||||||
|
try {
|
||||||
|
const settings = await updateClientCustomizationSettings(token, input);
|
||||||
|
setClientCustomizationSettings(settings);
|
||||||
|
setCoreState('ready');
|
||||||
|
setCoreMessage('客户端自定义设置已更新。');
|
||||||
|
} catch (err) {
|
||||||
|
setCoreState('error');
|
||||||
|
setCoreMessage(err instanceof Error ? err.message : '更新客户端自定义设置失败');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function removeFileStorageChannel(channelId: string) {
|
async function removeFileStorageChannel(channelId: string) {
|
||||||
setCoreState('loading');
|
setCoreState('loading');
|
||||||
setCoreMessage('');
|
setCoreMessage('');
|
||||||
@@ -1259,6 +1284,7 @@ export function App() {
|
|||||||
onSaveAccessRule={saveAccessRule}
|
onSaveAccessRule={saveAccessRule}
|
||||||
onSaveFileStorageChannel={saveFileStorageChannel}
|
onSaveFileStorageChannel={saveFileStorageChannel}
|
||||||
onSaveFileStorageSettings={saveFileStorageSettings}
|
onSaveFileStorageSettings={saveFileStorageSettings}
|
||||||
|
onSaveClientCustomizationSettings={saveClientCustomizationSettings}
|
||||||
onSaveTenant={saveTenant}
|
onSaveTenant={saveTenant}
|
||||||
onSaveUser={saveUser}
|
onSaveUser={saveUser}
|
||||||
onRechargeUserWalletBalance={rechargeUserWallet}
|
onRechargeUserWalletBalance={rechargeUserWallet}
|
||||||
@@ -1480,7 +1506,7 @@ function dataKeysForRoute(
|
|||||||
case 'accessRules':
|
case 'accessRules':
|
||||||
return ['accessRules', 'userGroups', 'platforms', 'models'];
|
return ['accessRules', 'userGroups', 'platforms', 'models'];
|
||||||
case 'systemSettings':
|
case 'systemSettings':
|
||||||
return ['fileStorageSettings', 'fileStorageChannels'];
|
return ['clientCustomizationSettings', 'fileStorageSettings', 'fileStorageChannels'];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type {
|
|||||||
BaseModelUpsertRequest,
|
BaseModelUpsertRequest,
|
||||||
CatalogProvider,
|
CatalogProvider,
|
||||||
CatalogProviderUpsertRequest,
|
CatalogProviderUpsertRequest,
|
||||||
|
ClientCustomizationSettings,
|
||||||
|
ClientCustomizationSettingsUpdateRequest,
|
||||||
CreatedGatewayApiKey,
|
CreatedGatewayApiKey,
|
||||||
FileStorageChannel,
|
FileStorageChannel,
|
||||||
FileStorageSettings,
|
FileStorageSettings,
|
||||||
@@ -974,6 +976,25 @@ export async function updateFileStorageSettings(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getClientCustomizationSettings(token: string): Promise<ClientCustomizationSettings> {
|
||||||
|
return request<ClientCustomizationSettings>('/api/admin/system/client-customization/settings', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateClientCustomizationSettings(
|
||||||
|
token: string,
|
||||||
|
input: ClientCustomizationSettingsUpdateRequest,
|
||||||
|
): Promise<ClientCustomizationSettings> {
|
||||||
|
return request<ClientCustomizationSettings>('/api/admin/system/client-customization/settings', {
|
||||||
|
body: input,
|
||||||
|
method: 'PATCH',
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPublicClientCustomization(): Promise<ClientCustomizationSettings> {
|
||||||
|
return request<ClientCustomizationSettings>('/api/v1/public/client-customization', { auth: false });
|
||||||
|
}
|
||||||
|
|
||||||
export async function createFileStorageChannel(
|
export async function createFileStorageChannel(
|
||||||
token: string,
|
token: string,
|
||||||
input: FileStorageChannelUpsertRequest,
|
input: FileStorageChannelUpsertRequest,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
AuthUser,
|
AuthUser,
|
||||||
BaseModelCatalogItem,
|
BaseModelCatalogItem,
|
||||||
CatalogProvider,
|
CatalogProvider,
|
||||||
|
ClientCustomizationSettings,
|
||||||
FileStorageChannel,
|
FileStorageChannel,
|
||||||
FileStorageSettings,
|
FileStorageSettings,
|
||||||
GatewayAccessRule,
|
GatewayAccessRule,
|
||||||
@@ -32,6 +33,7 @@ export interface ConsoleData {
|
|||||||
baseModels: BaseModelCatalogItem[];
|
baseModels: BaseModelCatalogItem[];
|
||||||
currentUser: AuthUser | null;
|
currentUser: AuthUser | null;
|
||||||
currentUserGroups: UserGroup[];
|
currentUserGroups: UserGroup[];
|
||||||
|
clientCustomizationSettings: ClientCustomizationSettings | null;
|
||||||
fileStorageChannels: FileStorageChannel[];
|
fileStorageChannels: FileStorageChannel[];
|
||||||
fileStorageSettings: FileStorageSettings | null;
|
fileStorageSettings: FileStorageSettings | null;
|
||||||
modelCatalog: ModelCatalogResponse;
|
modelCatalog: ModelCatalogResponse;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Boxes, Building2, Gauge, History, KeyRound, Route, ServerCog, Settings,
|
|||||||
import type {
|
import type {
|
||||||
BaseModelUpsertRequest,
|
BaseModelUpsertRequest,
|
||||||
CatalogProviderUpsertRequest,
|
CatalogProviderUpsertRequest,
|
||||||
|
ClientCustomizationSettingsUpdateRequest,
|
||||||
FileStorageChannelUpsertRequest,
|
FileStorageChannelUpsertRequest,
|
||||||
FileStorageSettingsUpdateRequest,
|
FileStorageSettingsUpdateRequest,
|
||||||
GatewayAccessRuleBatchRequest,
|
GatewayAccessRuleBatchRequest,
|
||||||
@@ -78,6 +79,7 @@ export function AdminPage(props: {
|
|||||||
onSaveRunnerPolicy: (input: GatewayRunnerPolicyUpsertRequest) => Promise<void>;
|
onSaveRunnerPolicy: (input: GatewayRunnerPolicyUpsertRequest) => Promise<void>;
|
||||||
onSaveRuntimePolicySet: (input: RuntimePolicySetUpsertRequest, policySetId?: string) => Promise<void>;
|
onSaveRuntimePolicySet: (input: RuntimePolicySetUpsertRequest, policySetId?: string) => Promise<void>;
|
||||||
onSaveAccessRule: (input: GatewayAccessRuleUpsertRequest, ruleId?: string) => Promise<void>;
|
onSaveAccessRule: (input: GatewayAccessRuleUpsertRequest, ruleId?: string) => Promise<void>;
|
||||||
|
onSaveClientCustomizationSettings: (input: ClientCustomizationSettingsUpdateRequest) => Promise<void>;
|
||||||
onSaveFileStorageChannel: (input: FileStorageChannelUpsertRequest, channelId?: string) => Promise<void>;
|
onSaveFileStorageChannel: (input: FileStorageChannelUpsertRequest, channelId?: string) => Promise<void>;
|
||||||
onSaveFileStorageSettings: (input: FileStorageSettingsUpdateRequest) => Promise<void>;
|
onSaveFileStorageSettings: (input: FileStorageSettingsUpdateRequest) => Promise<void>;
|
||||||
onSaveTenant: (input: GatewayTenantUpsertRequest, tenantId?: string) => Promise<void>;
|
onSaveTenant: (input: GatewayTenantUpsertRequest, tenantId?: string) => Promise<void>;
|
||||||
@@ -185,12 +187,14 @@ export function AdminPage(props: {
|
|||||||
{props.section === 'systemSettings' && (
|
{props.section === 'systemSettings' && (
|
||||||
<SystemSettingsPanel
|
<SystemSettingsPanel
|
||||||
channels={props.data.fileStorageChannels}
|
channels={props.data.fileStorageChannels}
|
||||||
|
clientCustomizationSettings={props.data.clientCustomizationSettings}
|
||||||
settings={props.data.fileStorageSettings}
|
settings={props.data.fileStorageSettings}
|
||||||
message={props.operationMessage}
|
message={props.operationMessage}
|
||||||
state={props.state}
|
state={props.state}
|
||||||
onDeleteFileStorageChannel={props.onDeleteFileStorageChannel}
|
onDeleteFileStorageChannel={props.onDeleteFileStorageChannel}
|
||||||
onSaveFileStorageChannel={props.onSaveFileStorageChannel}
|
onSaveFileStorageChannel={props.onSaveFileStorageChannel}
|
||||||
onSaveFileStorageSettings={props.onSaveFileStorageSettings}
|
onSaveFileStorageSettings={props.onSaveFileStorageSettings}
|
||||||
|
onSaveClientCustomizationSettings={props.onSaveClientCustomizationSettings}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { useEffect, useState, type FormEvent } from 'react';
|
import { useEffect, useState, type FormEvent } from 'react';
|
||||||
import { Database, Pencil, Plus, RotateCcw, Save, ServerCog, Trash2 } from 'lucide-react';
|
import { Database, Pencil, Plus, RotateCcw, Save, ServerCog, Settings2, Trash2 } from 'lucide-react';
|
||||||
import type { FileStorageChannel, FileStorageChannelUpsertRequest, FileStorageSettings, FileStorageSettingsUpdateRequest } from '@easyai-ai-gateway/contracts';
|
import type { ClientCustomizationSettings, ClientCustomizationSettingsUpdateRequest, FileStorageChannel, FileStorageChannelUpsertRequest, FileStorageSettings, FileStorageSettingsUpdateRequest } from '@easyai-ai-gateway/contracts';
|
||||||
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, Input, Label, Select, Tabs, Textarea } from '../../components/ui';
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, Input, Label, Select, Tabs, Textarea } from '../../components/ui';
|
||||||
import type { LoadState } from '../../types';
|
import type { LoadState } from '../../types';
|
||||||
|
|
||||||
type SystemSettingsTab = 'fileStorage';
|
type SystemSettingsTab = 'fileStorage' | 'clientCustomization';
|
||||||
|
|
||||||
|
type ClientCustomizationForm = {
|
||||||
|
clientEnglishName: string;
|
||||||
|
clientName: string;
|
||||||
|
iconPath: string;
|
||||||
|
platformName: string;
|
||||||
|
};
|
||||||
|
|
||||||
type FileStorageChannelForm = {
|
type FileStorageChannelForm = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
@@ -48,10 +55,12 @@ const resultUploadPolicyOptions = [
|
|||||||
|
|
||||||
export function SystemSettingsPanel(props: {
|
export function SystemSettingsPanel(props: {
|
||||||
channels: FileStorageChannel[];
|
channels: FileStorageChannel[];
|
||||||
|
clientCustomizationSettings: ClientCustomizationSettings | null;
|
||||||
message: string;
|
message: string;
|
||||||
settings: FileStorageSettings | null;
|
settings: FileStorageSettings | null;
|
||||||
state: LoadState;
|
state: LoadState;
|
||||||
onDeleteFileStorageChannel: (channelId: string) => Promise<void>;
|
onDeleteFileStorageChannel: (channelId: string) => Promise<void>;
|
||||||
|
onSaveClientCustomizationSettings: (input: ClientCustomizationSettingsUpdateRequest) => Promise<void>;
|
||||||
onSaveFileStorageChannel: (input: FileStorageChannelUpsertRequest, channelId?: string) => Promise<void>;
|
onSaveFileStorageChannel: (input: FileStorageChannelUpsertRequest, channelId?: string) => Promise<void>;
|
||||||
onSaveFileStorageSettings: (input: FileStorageSettingsUpdateRequest) => Promise<void>;
|
onSaveFileStorageSettings: (input: FileStorageSettingsUpdateRequest) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
@@ -60,6 +69,7 @@ export function SystemSettingsPanel(props: {
|
|||||||
const [editingChannel, setEditingChannel] = useState<FileStorageChannel | null>(null);
|
const [editingChannel, setEditingChannel] = useState<FileStorageChannel | null>(null);
|
||||||
const [pendingDeleteChannel, setPendingDeleteChannel] = useState<FileStorageChannel | null>(null);
|
const [pendingDeleteChannel, setPendingDeleteChannel] = useState<FileStorageChannel | null>(null);
|
||||||
const [form, setForm] = useState<FileStorageChannelForm>(() => defaultChannelForm());
|
const [form, setForm] = useState<FileStorageChannelForm>(() => defaultChannelForm());
|
||||||
|
const [clientCustomizationForm, setClientCustomizationForm] = useState<ClientCustomizationForm>(() => clientCustomizationSettingsToForm(props.clientCustomizationSettings));
|
||||||
const [settingsPolicy, setSettingsPolicy] = useState(() => normalizeResultUploadPolicy(props.settings?.resultUploadPolicy));
|
const [settingsPolicy, setSettingsPolicy] = useState(() => normalizeResultUploadPolicy(props.settings?.resultUploadPolicy));
|
||||||
const [localError, setLocalError] = useState('');
|
const [localError, setLocalError] = useState('');
|
||||||
|
|
||||||
@@ -67,6 +77,10 @@ export function SystemSettingsPanel(props: {
|
|||||||
setSettingsPolicy(normalizeResultUploadPolicy(props.settings?.resultUploadPolicy));
|
setSettingsPolicy(normalizeResultUploadPolicy(props.settings?.resultUploadPolicy));
|
||||||
}, [props.settings?.resultUploadPolicy]);
|
}, [props.settings?.resultUploadPolicy]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setClientCustomizationForm(clientCustomizationSettingsToForm(props.clientCustomizationSettings));
|
||||||
|
}, [props.clientCustomizationSettings]);
|
||||||
|
|
||||||
function openCreateDialog() {
|
function openCreateDialog() {
|
||||||
setEditingChannel(null);
|
setEditingChannel(null);
|
||||||
setForm(defaultChannelForm(`server-main-${Date.now().toString(36)}`));
|
setForm(defaultChannelForm(`server-main-${Date.now().toString(36)}`));
|
||||||
@@ -122,6 +136,15 @@ export function SystemSettingsPanel(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveClientCustomization() {
|
||||||
|
setLocalError('');
|
||||||
|
try {
|
||||||
|
await props.onSaveClientCustomizationSettings(clientCustomizationFormToPayload(clientCustomizationForm));
|
||||||
|
} catch (err) {
|
||||||
|
setLocalError(err instanceof Error ? err.message : '客户端自定义设置保存失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pageStack">
|
<div className="pageStack">
|
||||||
<Card>
|
<Card>
|
||||||
@@ -136,7 +159,10 @@ export function SystemSettingsPanel(props: {
|
|||||||
{(props.message || localError) && <p className="formMessage">{localError || props.message}</p>}
|
{(props.message || localError) && <p className="formMessage">{localError || props.message}</p>}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
tabs={[{ value: 'fileStorage', label: '文件存储', icon: <Database size={15} /> }]}
|
tabs={[
|
||||||
|
{ value: 'fileStorage', label: '文件存储', icon: <Database size={15} /> },
|
||||||
|
{ value: 'clientCustomization', label: '客户端自定义', icon: <Settings2 size={15} /> },
|
||||||
|
]}
|
||||||
onValueChange={setActiveTab}
|
onValueChange={setActiveTab}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -216,6 +242,39 @@ export function SystemSettingsPanel(props: {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'clientCustomization' && (
|
||||||
|
<section className="fileStoragePanel">
|
||||||
|
<div className="fileStorageSettingsCard">
|
||||||
|
<div>
|
||||||
|
<strong>客户端自定义</strong>
|
||||||
|
<span>配置公开展示用的客户端名称、平台名和图标路径;公开 API 无需鉴权读取。</span>
|
||||||
|
</div>
|
||||||
|
<div className="formGrid two spanTwo">
|
||||||
|
<Label>
|
||||||
|
客户端名
|
||||||
|
<Input value={clientCustomizationForm.clientName} onChange={(event) => setClientCustomizationForm({ ...clientCustomizationForm, clientName: event.target.value })} placeholder="EasyAI AI Gateway" />
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
客户端英文名
|
||||||
|
<Input value={clientCustomizationForm.clientEnglishName} onChange={(event) => setClientCustomizationForm({ ...clientCustomizationForm, clientEnglishName: event.target.value })} placeholder="EasyAI AI Gateway" />
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
平台名
|
||||||
|
<Input value={clientCustomizationForm.platformName} onChange={(event) => setClientCustomizationForm({ ...clientCustomizationForm, platformName: event.target.value })} placeholder="EasyAI" />
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
Icon 路径/URL
|
||||||
|
<Input value={clientCustomizationForm.iconPath} onChange={(event) => setClientCustomizationForm({ ...clientCustomizationForm, iconPath: event.target.value })} placeholder="/logo.svg 或 https://..." />
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<Button type="button" onClick={saveClientCustomization} disabled={props.state === 'loading'}>
|
||||||
|
<Save size={15} />
|
||||||
|
保存客户端自定义
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormDialog
|
<FormDialog
|
||||||
ariaLabel={editingChannel ? '编辑文件存储渠道' : '新增文件存储渠道'}
|
ariaLabel={editingChannel ? '编辑文件存储渠道' : '新增文件存储渠道'}
|
||||||
bodyClassName="fileStorageDialogBody"
|
bodyClassName="fileStorageDialogBody"
|
||||||
@@ -308,6 +367,34 @@ export function SystemSettingsPanel(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultClientCustomizationForm(): ClientCustomizationForm {
|
||||||
|
return {
|
||||||
|
clientEnglishName: 'EasyAI AI Gateway',
|
||||||
|
clientName: 'EasyAI AI Gateway',
|
||||||
|
iconPath: '',
|
||||||
|
platformName: 'EasyAI',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientCustomizationSettingsToForm(settings: ClientCustomizationSettings | null): ClientCustomizationForm {
|
||||||
|
const defaults = defaultClientCustomizationForm();
|
||||||
|
return {
|
||||||
|
clientEnglishName: settings?.clientEnglishName ?? defaults.clientEnglishName,
|
||||||
|
clientName: settings?.clientName ?? defaults.clientName,
|
||||||
|
iconPath: settings?.iconPath ?? defaults.iconPath,
|
||||||
|
platformName: settings?.platformName ?? defaults.platformName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientCustomizationFormToPayload(form: ClientCustomizationForm): ClientCustomizationSettingsUpdateRequest {
|
||||||
|
return {
|
||||||
|
clientEnglishName: form.clientEnglishName.trim(),
|
||||||
|
clientName: form.clientName.trim(),
|
||||||
|
iconPath: form.iconPath.trim(),
|
||||||
|
platformName: form.platformName.trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function defaultChannelForm(channelKey = ''): FileStorageChannelForm {
|
function defaultChannelForm(channelKey = ''): FileStorageChannelForm {
|
||||||
return {
|
return {
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
|
|||||||
+4
-1
@@ -10,7 +10,10 @@
|
|||||||
"lint": "nx run-many -t lint -p web contracts",
|
"lint": "nx run-many -t lint -p web contracts",
|
||||||
"db:create": "scripts/create-database.sh",
|
"db:create": "scripts/create-database.sh",
|
||||||
"migrate": "nx run api:migrate",
|
"migrate": "nx run api:migrate",
|
||||||
"openapi": "nx run api:openapi"
|
"openapi": "nx run api:openapi",
|
||||||
|
"docker:build": "docker compose -f docker-compose.yml build --pull api web",
|
||||||
|
"docker:push": "scripts/deploy-compose.sh push-only",
|
||||||
|
"docker:deploy": "pnpm docker:build && pnpm docker:push"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nx/vite": "^21.0.0",
|
"@nx/vite": "^21.0.0",
|
||||||
|
|||||||
@@ -943,6 +943,20 @@ export interface FileStorageSettingsUpdateRequest {
|
|||||||
resultUploadPolicy: 'default' | 'upload_all' | 'upload_none' | string;
|
resultUploadPolicy: 'default' | 'upload_all' | 'upload_none' | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClientCustomizationSettings {
|
||||||
|
clientName: string;
|
||||||
|
clientEnglishName: string;
|
||||||
|
platformName: string;
|
||||||
|
iconPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientCustomizationSettingsUpdateRequest {
|
||||||
|
clientName?: string;
|
||||||
|
clientEnglishName?: string;
|
||||||
|
platformName?: string;
|
||||||
|
iconPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GatewayTask {
|
export interface GatewayTask {
|
||||||
id: string;
|
id: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Usage:
|
|||||||
scripts/deploy-compose.sh Build, migrate, start, and verify
|
scripts/deploy-compose.sh Build, migrate, start, and verify
|
||||||
scripts/deploy-compose.sh deploy Same as default
|
scripts/deploy-compose.sh deploy Same as default
|
||||||
scripts/deploy-compose.sh push Build and push API/Web images
|
scripts/deploy-compose.sh push Build and push API/Web images
|
||||||
|
scripts/deploy-compose.sh push-only
|
||||||
|
Push already-built API/Web images
|
||||||
scripts/deploy-compose.sh down Stop containers, keep volumes
|
scripts/deploy-compose.sh down Stop containers, keep volumes
|
||||||
scripts/deploy-compose.sh clean Stop containers and remove volumes
|
scripts/deploy-compose.sh clean Stop containers and remove volumes
|
||||||
|
|
||||||
@@ -265,6 +267,10 @@ case "$ACTION" in
|
|||||||
build_images
|
build_images
|
||||||
push_images
|
push_images
|
||||||
;;
|
;;
|
||||||
|
push-only)
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
push_images
|
||||||
|
;;
|
||||||
down)
|
down)
|
||||||
cd "$PROJECT_ROOT"
|
cd "$PROJECT_ROOT"
|
||||||
"${compose[@]}" down --remove-orphans
|
"${compose[@]}" down --remove-orphans
|
||||||
|
|||||||
Reference in New Issue
Block a user