feat(system): 完成客户端自定义与系统设置

新增管理端客户端自定义标签页、公开与管理端系统设置接口、文件存储设置持久化、数据库迁移、契约类型和 OpenAPI 文档。
This commit is contained in:
2026-06-30 14:53:29 +08:00
parent 229ed6a669
commit 0dc9df759b
12 changed files with 2558 additions and 17 deletions
File diff suppressed because it is too large Load Diff
+864 -3
View File
@@ -138,13 +138,23 @@ definitions:
completion_tokens:
example: 8
type: integer
input_tokens_details:
$ref: '#/definitions/httpapi.ChatInputTokensDetails'
prompt_tokens:
example: 12
type: integer
prompt_tokens_details:
$ref: '#/definitions/httpapi.ChatPromptTokensDetails'
total_tokens:
example: 20
type: integer
type: object
httpapi.ChatInputTokensDetails:
properties:
cached_tokens:
example: 4
type: integer
type: object
httpapi.ChatMessage:
properties:
content:
@@ -154,6 +164,12 @@ definitions:
example: user
type: string
type: object
httpapi.ChatPromptTokensDetails:
properties:
cached_tokens:
example: 4
type: integer
type: object
httpapi.CompatibleResponse:
properties:
choices:
@@ -534,6 +550,24 @@ definitions:
example: 9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25
type: string
type: object
httpapi.TaskCancelResponse:
properties:
cancellable:
example: false
type: boolean
cancelled:
example: false
type: boolean
message:
example: 任务已提交上游,当前不可取消,请继续查询结果
type: string
submitted:
example: true
type: boolean
taskId:
example: 9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25
type: string
type: object
httpapi.TaskListResponse:
properties:
items:
@@ -704,6 +738,18 @@ definitions:
example: 42
type: integer
type: object
httpapi.desktopBillingConfigResponse:
properties:
gatewayBaseUrl:
type: string
gatewayBillingPath:
type: string
type: object
httpapi.desktopConfigResponse:
properties:
billing:
$ref: '#/definitions/httpapi.desktopBillingConfigResponse'
type: object
httpapi.updatePlatformDynamicPriorityRequest:
properties:
dynamicPriority:
@@ -1090,6 +1136,28 @@ definitions:
status:
type: string
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:
properties:
expiresAt:
@@ -1299,6 +1367,8 @@ definitions:
billings:
items: {}
type: array
cancellable:
type: boolean
conversationId:
type: string
createdAt:
@@ -1321,6 +1391,8 @@ definitions:
type: string
kind:
type: string
message:
type: string
metrics:
additionalProperties: {}
type: object
@@ -1359,6 +1431,8 @@ definitions:
type: string
status:
type: string
submitted:
type: boolean
tenantId:
type: string
tenantKey:
@@ -2086,6 +2160,9 @@ definitions:
type: object
store.RunnerPolicy:
properties:
cacheAffinityPolicy:
additionalProperties: {}
type: object
createdAt:
type: string
description:
@@ -2118,6 +2195,9 @@ definitions:
type: object
store.RunnerPolicyInput:
properties:
cacheAffinityPolicy:
additionalProperties: {}
type: object
description:
type: string
failoverPolicy:
@@ -3930,6 +4010,72 @@ paths:
summary: 更新 Runner 策略
tags:
- 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:
get:
description: 返回所有未删除的文件存储通道,用于管理上传与生成资源回传策略。
@@ -5238,7 +5384,6 @@ paths:
post:
consumes:
- application/json
- multipart/form-data
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
@@ -5611,6 +5756,23 @@ paths:
summary: 列出目录供应商
tags:
- 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:
post:
consumes:
@@ -5942,6 +6104,43 @@ paths:
summary: 获取任务详情
tags:
- tasks
/api/v1/tasks/{taskID}/cancel:
post:
description: 仅取消仍在网关本地排队且尚未提交上游的任务;任务已运行、已提交上游或已结束时返回不可取消,客户端应继续查询结果。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskCancelResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 取消异步任务
tags:
- tasks
/api/v1/tasks/{taskID}/events:
get:
description: 以 text/event-stream 返回指定任务的历史事件;无事件时返回 task.accepted 占位事件。
@@ -6069,6 +6268,152 @@ paths:
summary: 创建或执行 AI 任务
tags:
- tasks
/api/v1/voice_clone:
post:
consumes:
- application/json
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
- description: true 时异步创建任务并返回 202
in: header
name: X-Async
type: boolean
- description: AI 任务请求,字段随任务类型变化
in: body
name: input
required: true
schema:
$ref: '#/definitions/httpapi.TaskRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.CompatibleResponse'
"202":
description: Accepted
schema:
$ref: '#/definitions/httpapi.TaskAcceptedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"402":
description: Payment Required
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"429":
description: Too Many Requests
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 创建或执行 AI 任务
tags:
- tasks
/api/v1/voice_clone/voices:
get:
description: 返回当前用户在网关中维护的克隆音色,以及克隆时绑定的平台与平台模型。
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出当前用户克隆音色
tags:
- voice-clone
/api/v1/voice_clone/voices/{voiceID}:
delete:
description: 先从上游供应商删除音色,成功后再将网关数据库中的音色记录标记为 deleted。
parameters:
- description: 克隆音色记录 ID 或上游 voice_id
in: path
name: voiceID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"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'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 删除当前用户克隆音色
tags:
- voice-clone
/api/workspace/desktop-config:
get:
description: 返回桌面端需要的 Gateway Web 账单入口配置。
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.desktopConfigResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 获取桌面端配置
tags:
- workspace
/api/workspace/tasks:
get:
description: 按当前用户列出任务,支持关键字、模型类型、时间范围和分页过滤。
@@ -6156,6 +6501,43 @@ paths:
summary: 获取任务详情
tags:
- tasks
/api/workspace/tasks/{taskID}/cancel:
post:
description: 仅取消仍在网关本地排队且尚未提交上游的任务;任务已运行、已提交上游或已结束时返回不可取消,客户端应继续查询结果。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskCancelResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 取消异步任务
tags:
- tasks
/api/workspace/tasks/{taskID}/events:
get:
description: 以 text/event-stream 返回指定任务的历史事件;无事件时返回 task.accepted 占位事件。
@@ -6471,7 +6853,6 @@ paths:
post:
consumes:
- application/json
- multipart/form-data
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
@@ -6983,6 +7364,196 @@ paths:
summary: 获取本地上传资源
tags:
- static
/tasks:
get:
description: 按当前用户列出任务,支持关键字、模型类型、时间范围和分页过滤。
parameters:
- description: 搜索关键字,别名 query
in: query
name: q
type: string
- description: 模型类型,别名 type
in: query
name: modelType
type: string
- description: 创建时间起点,支持 RFC3339 或日期格式,别名 from
in: query
name: createdFrom
type: string
- description: 创建时间终点,支持 RFC3339 或日期格式,别名 to
in: query
name: createdTo
type: string
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 50
description: 每页数量,别名 limit
in: query
name: pageSize
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskListResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出任务
tags:
- tasks
/tasks/{taskID}:
get:
description: 返回指定任务的请求、状态、输出和执行摘要。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/store.GatewayTask'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 获取任务详情
tags:
- tasks
/tasks/{taskID}/cancel:
post:
description: 仅取消仍在网关本地排队且尚未提交上游的任务;任务已运行、已提交上游或已结束时返回不可取消,客户端应继续查询结果。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskCancelResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 取消异步任务
tags:
- tasks
/tasks/{taskID}/events:
get:
description: 以 text/event-stream 返回指定任务的历史事件;无事件时返回 task.accepted 占位事件。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- text/event-stream
responses:
"200":
description: Server-Sent Eventsdata 为 store.TaskEvent 或 TaskAcceptedEvent
schema:
type: string
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 订阅任务事件
tags:
- tasks
/tasks/{taskID}/param-preprocessing:
get:
description: 返回指定任务在执行前的参数改写、校验或模板处理日志。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskParamPreprocessingLogListResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 获取任务参数预处理日志
tags:
- tasks
/v1/chat/completions:
post:
consumes:
@@ -7153,7 +7724,6 @@ paths:
post:
consumes:
- application/json
- multipart/form-data
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
@@ -7577,6 +8147,297 @@ paths:
summary: 创建或执行 AI 任务
tags:
- tasks
/v1/tasks/{taskID}/cancel:
post:
description: 仅取消仍在网关本地排队且尚未提交上游的任务;任务已运行、已提交上游或已结束时返回不可取消,客户端应继续查询结果。
parameters:
- description: 任务 ID
in: path
name: taskID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.TaskCancelResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 取消异步任务
tags:
- tasks
/v1/voice_clone:
post:
consumes:
- application/json
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
- description: true 时异步创建任务并返回 202
in: header
name: X-Async
type: boolean
- description: AI 任务请求,字段随任务类型变化
in: body
name: input
required: true
schema:
$ref: '#/definitions/httpapi.TaskRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.CompatibleResponse'
"202":
description: Accepted
schema:
$ref: '#/definitions/httpapi.TaskAcceptedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"402":
description: Payment Required
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"429":
description: Too Many Requests
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 创建或执行 AI 任务
tags:
- tasks
/v1/voice_clone/voices:
get:
description: 返回当前用户在网关中维护的克隆音色,以及克隆时绑定的平台与平台模型。
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出当前用户克隆音色
tags:
- voice-clone
/v1/voice_clone/voices/{voiceID}:
delete:
description: 先从上游供应商删除音色,成功后再将网关数据库中的音色记录标记为 deleted。
parameters:
- description: 克隆音色记录 ID 或上游 voice_id
in: path
name: voiceID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"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'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 删除当前用户克隆音色
tags:
- voice-clone
/voice_clone:
post:
consumes:
- application/json
description: 网关任务接口按 model 选择平台模型;除 /api/v1/chat/completions 以外的 /api/v1 任务路径返回任务受理结果,OpenAI-compatible
路径同步返回兼容响应或 SSE 流。
parameters:
- description: true 时异步创建任务并返回 202
in: header
name: X-Async
type: boolean
- description: AI 任务请求,字段随任务类型变化
in: body
name: input
required: true
schema:
$ref: '#/definitions/httpapi.TaskRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.CompatibleResponse'
"202":
description: Accepted
schema:
$ref: '#/definitions/httpapi.TaskAcceptedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"402":
description: Payment Required
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"403":
description: Forbidden
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"429":
description: Too Many Requests
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 创建或执行 AI 任务
tags:
- tasks
/voice_clone/voices:
get:
description: 返回当前用户在网关中维护的克隆音色,以及克隆时绑定的平台与平台模型。
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出当前用户克隆音色
tags:
- voice-clone
/voice_clone/voices/{voiceID}:
delete:
description: 先从上游供应商删除音色,成功后再将网关数据库中的音色记录标记为 deleted。
parameters:
- description: 克隆音色记录 ID 或上游 voice_id
in: path
name: voiceID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"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'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 删除当前用户克隆音色
tags:
- voice-clone
schemes:
- http
- https
+3
View File
@@ -52,6 +52,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/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/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("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)))
@@ -115,6 +116,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/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("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("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)))
@@ -83,6 +83,76 @@ func (s *Server) updateFileStorageSettings(w http.ResponseWriter, r *http.Reques
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
// @Summary 创建文件存储通道
// @Description 创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。
@@ -24,6 +24,7 @@ const (
)
const SystemSettingFileStorage = "file_storage"
const SystemSettingClientCustomization = "client_customization"
const fileStorageChannelColumns = `
id::text, channel_key, name, provider, COALESCE(upload_url, ''), credentials,
@@ -72,6 +73,20 @@ type FileStorageSettingsInput struct {
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 {
Scan(dest ...any) error
}
@@ -348,6 +363,15 @@ func DefaultFileStorageSettings() FileStorageSettings {
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) {
var value []byte
err := s.pool.QueryRow(ctx, `
@@ -380,13 +404,53 @@ func (s *Store) UpdateFileStorageSettings(ctx context.Context, input FileStorage
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 {
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, `
INSERT INTO system_settings (setting_key, value)
VALUES ($1, $2)
ON CONFLICT (setting_key)
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 {
@@ -409,6 +473,53 @@ func fileStorageSettingsFromValue(value map[string]any) FileStorageSettings {
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 {
normalized := strings.ToLower(strings.TrimSpace(policy))
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;