feat(gateway): 补齐桌面端高级媒体直连接口
ci / verify (pull_request) Successful in 15m34s

新增图片矢量化、视频超分、每日用量、计价与任务隔离能力,并通过环境变量解析平台凭据。

已通过 Go 全量门禁、迁移检查、镜像构建以及 Vectorizer 五格式和 Topaz 3 秒视频真实 DEV 验收。
This commit is contained in:
2026-07-22 14:02:53 +08:00
parent cbebfd7baa
commit 3056cf8fca
37 changed files with 3336 additions and 29 deletions
+45
View File
@@ -1395,6 +1395,12 @@ func apiKeyScopeAllowed(user *auth.User, kind string) bool {
if required == "voice_clone" && (scope == "audio" || scope == "text_to_speech" || scope == "speech" || scope == "tts") {
return true
}
if required == "image_vectorize" && (scope == "image" || scope == "vectorize") {
return true
}
if required == "video_enhance" && (scope == "video" || scope == "video_upscale" || scope == "upscale") {
return true
}
}
return false
}
@@ -1408,6 +1414,14 @@ func requestModelName(body map[string]any) string {
func canonicalTaskModelName(kind string, model string) string {
model = strings.TrimSpace(model)
if model == "" {
switch kind {
case "images.vectorize":
return "easy-image-vectorizer-1"
case "videos.upscales":
return "easy-proteus-standard-4"
}
}
if kind != "videos.generations" {
return model
}
@@ -1441,8 +1455,12 @@ func scopeForTaskKind(kind string) string {
return "rerank"
case "images.generations", "images.edits":
return "image"
case "images.vectorize":
return "image_vectorize"
case "videos.generations":
return "video"
case "videos.upscales":
return "video_enhance"
case "song.generations", "music.generations":
return "music"
case "speech.generations":
@@ -1764,8 +1782,17 @@ func boolValue(body map[string]any, key string) bool {
// @Router /api/workspace/tasks/{taskID} [get]
// @Router /api/v1/tasks/{taskID} [get]
func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err == nil {
if !runner.TaskAccessibleToUser(task, user) {
writeError(w, http.StatusNotFound, "task not found")
return
}
cancelState := runner.DescribeTaskCancellation(task)
task.Cancellable = &cancelState.Cancellable
task.Submitted = &cancelState.Submitted
@@ -1832,6 +1859,11 @@ func (s *Server) cancelTask(w http.ResponseWriter, r *http.Request) {
// @Router /api/workspace/tasks/{taskID}/param-preprocessing [get]
// @Router /api/v1/tasks/{taskID}/param-preprocessing [get]
func (s *Server) taskParamPreprocessing(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err != nil {
if store.IsNotFound(err) {
@@ -1842,6 +1874,10 @@ func (s *Server) taskParamPreprocessing(w http.ResponseWriter, r *http.Request)
writeError(w, http.StatusInternalServerError, "get task failed")
return
}
if !runner.TaskAccessibleToUser(task, user) {
writeError(w, http.StatusNotFound, "task not found")
return
}
logs, err := s.store.ListTaskParamPreprocessingLogs(r.Context(), task.ID)
if err != nil {
s.logger.Error("list task parameter preprocessing logs failed", "taskID", task.ID, "error", err)
@@ -1865,6 +1901,11 @@ func (s *Server) taskParamPreprocessing(w http.ResponseWriter, r *http.Request)
// @Router /api/workspace/tasks/{taskID}/events [get]
// @Router /api/v1/tasks/{taskID}/events [get]
func (s *Server) taskEvents(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err != nil {
if store.IsNotFound(err) {
@@ -1874,6 +1915,10 @@ func (s *Server) taskEvents(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusInternalServerError, "get task failed")
return
}
if !runner.TaskAccessibleToUser(task, user) {
writeError(w, http.StatusNotFound, "task not found")
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")