feat(gateway): 补齐桌面端高级媒体直连接口
ci / verify (pull_request) Successful in 15m34s
ci / verify (pull_request) Successful in 15m34s
新增图片矢量化、视频超分、每日用量、计价与任务隔离能力,并通过环境变量解析平台凭据。 已通过 Go 全量门禁、迁移检查、镜像构建以及 Vectorizer 五格式和 Topaz 3 秒视频真实 DEV 验收。
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func TestAdvancedMediaScopeAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
kind string
|
||||
scope string
|
||||
}{
|
||||
{kind: "images.vectorize", scope: "image_vectorize"},
|
||||
{kind: "images.vectorize", scope: "image"},
|
||||
{kind: "images.vectorize", scope: "vectorize"},
|
||||
{kind: "videos.upscales", scope: "video_enhance"},
|
||||
{kind: "videos.upscales", scope: "video"},
|
||||
{kind: "videos.upscales", scope: "video_upscale"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
user := &auth.User{APIKeyID: "key", APIKeyScopes: []string{test.scope}}
|
||||
if !apiKeyScopeAllowed(user, test.kind) {
|
||||
t.Fatalf("scope %q should allow %q", test.scope, test.kind)
|
||||
}
|
||||
}
|
||||
if apiKeyScopeAllowed(&auth.User{APIKeyID: "key", APIKeyScopes: []string{"chat"}}, "videos.upscales") {
|
||||
t.Fatal("chat scope must not allow video upscale")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillDailyTokenUsageDaysKeepsGapsAndStreaks(t *testing.T) {
|
||||
location, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
from := time.Date(2026, 7, 20, 0, 0, 0, 0, location)
|
||||
to := time.Date(2026, 7, 24, 0, 0, 0, 0, location)
|
||||
items, summary := fillDailyTokenUsageDays(map[string]store.DailyTokenUsage{
|
||||
"2026-07-20": {Date: "2026-07-20", TotalTokens: 10, TaskCount: 1},
|
||||
"2026-07-22": {Date: "2026-07-22", TotalTokens: 20, TaskCount: 1},
|
||||
"2026-07-23": {Date: "2026-07-23", TotalTokens: 30, TaskCount: 2},
|
||||
"2026-07-24": {Date: "2026-07-24", TotalTokens: 40, TaskCount: 1},
|
||||
}, from, to)
|
||||
if len(items) != 5 || items[1].Date != "2026-07-21" || items[1].TaskCount != 0 {
|
||||
t.Fatalf("daily usage should contain continuous zero days: %+v", items)
|
||||
}
|
||||
if summary.CumulativeTokens != 100 || summary.PeakDailyTokens != 40 || summary.CurrentStreakDays != 3 || summary.LongestStreakDays != 3 {
|
||||
t.Fatalf("unexpected daily usage summary: %+v", summary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvancedMediaDefaultModels(t *testing.T) {
|
||||
if got := canonicalTaskModelName("images.vectorize", ""); got != "easy-image-vectorizer-1" {
|
||||
t.Fatalf("vectorizer default model=%q", got)
|
||||
}
|
||||
if got := canonicalTaskModelName("videos.upscales", ""); got != "easy-proteus-standard-4" {
|
||||
t.Fatalf("Topaz default model=%q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package httpapi
|
||||
|
||||
import "net/http"
|
||||
|
||||
// createImageVectorizeTask godoc
|
||||
// @Summary 图片矢量化
|
||||
// @Description 将 URL 位图转换为 SVG、EPS、PDF、DXF 或 PNG;设置 X-Async=true 时返回统一异步任务结构。可使用同一所有者历史任务的 vectorizerTaskId 复用上游 image token。
|
||||
// @Tags images
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param X-Async header bool false "true 时异步创建任务并返回 202"
|
||||
// @Param input body ImageVectorizeRequest true "图片矢量化请求"
|
||||
// @Success 200 {object} CompatibleResponse
|
||||
// @Success 202 {object} TaskAcceptedResponse
|
||||
// @Failure 400 {object} ErrorEnvelope
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 403 {object} ErrorEnvelope
|
||||
// @Failure 404 {object} ErrorEnvelope
|
||||
// @Failure 502 {object} ErrorEnvelope
|
||||
// @Router /api/v1/images/vectorize [post]
|
||||
func (s *Server) createImageVectorizeTask() http.Handler {
|
||||
return s.createTask("images.vectorize", true)
|
||||
}
|
||||
|
||||
// createVideoUpscaleTask godoc
|
||||
// @Summary 视频超分
|
||||
// @Description 原生执行 Topaz 视频增强流程;设置 X-Async=true 时返回统一异步任务结构,结果在任务完成前持久化到 Gateway 文件存储。
|
||||
// @Tags videos
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param X-Async header bool false "true 时异步创建任务并返回 202"
|
||||
// @Param input body VideoUpscaleRequest true "视频超分请求"
|
||||
// @Success 200 {object} CompatibleResponse
|
||||
// @Success 202 {object} TaskAcceptedResponse
|
||||
// @Failure 400 {object} ErrorEnvelope
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 403 {object} ErrorEnvelope
|
||||
// @Failure 404 {object} ErrorEnvelope
|
||||
// @Failure 502 {object} ErrorEnvelope
|
||||
// @Router /api/v1/videos/upscales [post]
|
||||
func (s *Server) createVideoUpscaleTask() http.Handler {
|
||||
return s.createTask("videos.upscales", true)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
type dailyTokenUsageSummary struct {
|
||||
CumulativeTokens int64 `json:"cumulativeTokens"`
|
||||
PeakDailyTokens int64 `json:"peakDailyTokens"`
|
||||
CurrentStreakDays int `json:"currentStreakDays"`
|
||||
LongestStreakDays int `json:"longestStreakDays"`
|
||||
}
|
||||
|
||||
// dailyTokenUsage godoc
|
||||
// @Summary 查询每日 Token 与资源点用量
|
||||
// @Description 按 IANA 时区返回连续自然日用量;API Key 仅统计当前 Key,JWT 统计当前用户。
|
||||
// @Tags workspace
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param from query string true "开始日期 YYYY-MM-DD"
|
||||
// @Param to query string true "结束日期 YYYY-MM-DD"
|
||||
// @Param timezone query string false "IANA 时区" default(UTC)
|
||||
// @Success 200 {object} DailyTokenUsageResponse
|
||||
// @Failure 400 {object} ErrorEnvelope
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 500 {object} ErrorEnvelope
|
||||
// @Router /api/workspace/token-usage/daily [get]
|
||||
func (s *Server) dailyTokenUsage(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := auth.UserFromContext(r.Context())
|
||||
if !ok {
|
||||
writeError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
timezone := strings.TrimSpace(r.URL.Query().Get("timezone"))
|
||||
if timezone == "" {
|
||||
timezone = "UTC"
|
||||
}
|
||||
location, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid IANA timezone")
|
||||
return
|
||||
}
|
||||
from, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(r.URL.Query().Get("from")), location)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid from date")
|
||||
return
|
||||
}
|
||||
to, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(r.URL.Query().Get("to")), location)
|
||||
if err != nil || to.Before(from) || to.Sub(from) > 399*24*time.Hour {
|
||||
writeError(w, http.StatusBadRequest, "invalid to date or date range exceeds 400 days")
|
||||
return
|
||||
}
|
||||
stored, err := s.store.ListDailyTokenUsage(r.Context(), user, from, to, timezone)
|
||||
if err != nil {
|
||||
s.logger.Error("list daily token usage failed", "error", err)
|
||||
writeError(w, http.StatusInternalServerError, "list daily token usage failed")
|
||||
return
|
||||
}
|
||||
byDate := make(map[string]store.DailyTokenUsage, len(stored))
|
||||
for _, item := range stored {
|
||||
byDate[item.Date] = item
|
||||
}
|
||||
items, summary := fillDailyTokenUsageDays(byDate, from, to)
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"items": items, "tokenDays": items, "summary": summary,
|
||||
"range": map[string]string{"from": from.Format("2006-01-02"), "to": to.Format("2006-01-02"), "timezone": timezone},
|
||||
})
|
||||
}
|
||||
|
||||
func fillDailyTokenUsageDays(byDate map[string]store.DailyTokenUsage, from, to time.Time) ([]store.DailyTokenUsage, dailyTokenUsageSummary) {
|
||||
items := make([]store.DailyTokenUsage, 0, 32)
|
||||
summary := dailyTokenUsageSummary{}
|
||||
currentRun := 0
|
||||
for day := from; !day.After(to); day = day.AddDate(0, 0, 1) {
|
||||
key := day.Format("2006-01-02")
|
||||
item, found := byDate[key]
|
||||
if !found {
|
||||
item.Date = key
|
||||
}
|
||||
items = append(items, item)
|
||||
summary.CumulativeTokens += item.TotalTokens
|
||||
if item.TotalTokens > summary.PeakDailyTokens {
|
||||
summary.PeakDailyTokens = item.TotalTokens
|
||||
}
|
||||
if item.TaskCount > 0 {
|
||||
currentRun++
|
||||
if currentRun > summary.LongestStreakDays {
|
||||
summary.LongestStreakDays = currentRun
|
||||
}
|
||||
} else {
|
||||
currentRun = 0
|
||||
}
|
||||
}
|
||||
summary.CurrentStreakDays = currentRun
|
||||
return items, summary
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -1141,6 +1141,10 @@ func canonicalCapabilityFilterValue(value string) string {
|
||||
return "text_embedding"
|
||||
case "rerank", "reranks":
|
||||
return "text_rerank"
|
||||
case "vectorize", "image_vectorizer":
|
||||
return "image_vectorize"
|
||||
case "video_upscale", "upscale":
|
||||
return "video_enhance"
|
||||
case "model":
|
||||
return "model_3d"
|
||||
default:
|
||||
@@ -1164,6 +1168,10 @@ func capabilityFilterValueForTag(tag string) string {
|
||||
return "structured_output"
|
||||
case "数字人":
|
||||
return "digital_human"
|
||||
case "图片矢量化":
|
||||
return "image_vectorize"
|
||||
case "视频增强":
|
||||
return "video_enhance"
|
||||
case "重排序":
|
||||
return "text_rerank"
|
||||
case "3D 模型":
|
||||
@@ -1194,11 +1202,13 @@ func capabilityLabel(value string) string {
|
||||
"image_generate": "图像生成",
|
||||
"image_edit": "图像编辑",
|
||||
"image_analysis": "图像分析",
|
||||
"image_vectorize": "图片矢量化",
|
||||
"video_generate": "视频生成",
|
||||
"image_to_video": "图生视频",
|
||||
"text_to_video": "文生视频",
|
||||
"video_edit": "视频编辑",
|
||||
"video_understanding": "视频理解",
|
||||
"video_enhance": "视频增强",
|
||||
"audio_generate": "音频生成",
|
||||
"text_to_speech": "语音合成",
|
||||
"voice_clone": "音色克隆",
|
||||
|
||||
@@ -210,17 +210,31 @@ type PricingEstimateResponse struct {
|
||||
RequestFingerprint string `json:"requestFingerprint" example:"76ef6a537de8e71bd1ca93acadc078dbdbfa9f17e45224e4f9df59f535d2886f"`
|
||||
}
|
||||
|
||||
type DailyTokenUsageResponse struct {
|
||||
Items []store.DailyTokenUsage `json:"items"`
|
||||
TokenDays []store.DailyTokenUsage `json:"tokenDays"`
|
||||
Summary dailyTokenUsageSummary `json:"summary"`
|
||||
Range map[string]string `json:"range"`
|
||||
}
|
||||
|
||||
type TaskRequest struct {
|
||||
Model string `json:"model" example:"gpt-4o-mini"`
|
||||
Messages []ChatMessage `json:"messages,omitempty"`
|
||||
Input interface{} `json:"input,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" example:"A watercolor robot reading a book"`
|
||||
Text string `json:"text,omitempty" example:"Hello from EasyAI audio synthesis."`
|
||||
TextFileID string `json:"text_file_id,omitempty" example:""`
|
||||
VoiceID string `json:"voice_id,omitempty" example:"female-shaonv"`
|
||||
Stream *bool `json:"stream,omitempty" example:"false"`
|
||||
RunMode string `json:"runMode,omitempty" example:"simulation"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty" example:"512"`
|
||||
Model string `json:"model" example:"gpt-4o-mini"`
|
||||
Messages []ChatMessage `json:"messages,omitempty"`
|
||||
Input interface{} `json:"input,omitempty"`
|
||||
Query string `json:"query,omitempty" example:"Which document mentions EasyAI Gateway?"`
|
||||
Documents []string `json:"documents,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" example:"A watercolor robot reading a book"`
|
||||
Text string `json:"text,omitempty" example:"Hello from EasyAI audio synthesis."`
|
||||
TextFileID string `json:"text_file_id,omitempty" example:""`
|
||||
VoiceID string `json:"voice_id,omitempty" example:"female-shaonv"`
|
||||
AudioURL string `json:"audio_url,omitempty" example:"https://example.com/source-voice.mp3"`
|
||||
DisplayName string `json:"display_name,omitempty" example:"EasyAI DEV acceptance"`
|
||||
PromptAudioURL string `json:"prompt_audio_url,omitempty" example:"https://example.com/prompt-voice.mp3"`
|
||||
PromptText string `json:"prompt_text,omitempty" example:"EasyAI voice clone prompt."`
|
||||
PreviewModel string `json:"preview_model,omitempty" example:"speech-2.8-hd"`
|
||||
Stream *bool `json:"stream,omitempty" example:"false"`
|
||||
RunMode string `json:"runMode,omitempty" example:"simulation"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty" example:"512"`
|
||||
// MaxCompletionTokens includes visible output and reasoning tokens.
|
||||
MaxCompletionTokens *int `json:"max_completion_tokens,omitempty" example:"512"`
|
||||
MaxOutputTokens *int `json:"max_output_tokens,omitempty" example:"512"`
|
||||
@@ -368,6 +382,34 @@ type ImageEditRequest struct {
|
||||
RunMode string `json:"runMode,omitempty" example:"simulation"`
|
||||
}
|
||||
|
||||
type ImageVectorizeSource struct {
|
||||
URL string `json:"url,omitempty" example:"https://example.com/source.png"`
|
||||
VectorizerTaskID string `json:"vectorizerTaskId,omitempty" example:"9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25"`
|
||||
}
|
||||
|
||||
type ImageVectorizeRequest struct {
|
||||
Model string `json:"model,omitempty" example:"easy-image-vectorizer-1"`
|
||||
Source ImageVectorizeSource `json:"source"`
|
||||
Format string `json:"format,omitempty" example:"svg" enums:"svg,eps,pdf,dxf,png"`
|
||||
MaxColors int `json:"maxColors,omitempty" example:"16" enums:"0,2,4,8,16,32"`
|
||||
CleanupLevel string `json:"cleanupLevel,omitempty" example:"standard" enums:"low,standard,strong"`
|
||||
}
|
||||
|
||||
type VideoUpscaleRequest struct {
|
||||
Model string `json:"model,omitempty" example:"easy-proteus-standard-4"`
|
||||
VideoURL string `json:"video_url" example:"https://example.com/source.mp4"`
|
||||
Operation string `json:"operation,omitempty" example:"upscale" enums:"upscale"`
|
||||
TargetResolution string `json:"target_resolution,omitempty" example:"1080p"`
|
||||
OutputWidth int `json:"output_width,omitempty" example:"1920"`
|
||||
OutputHeight int `json:"output_height,omitempty" example:"1080"`
|
||||
PreserveAudio *bool `json:"preserve_audio,omitempty" example:"true"`
|
||||
Duration float64 `json:"duration,omitempty" example:"3"`
|
||||
SourceResolution string `json:"source_resolution,omitempty" example:"480p"`
|
||||
SourceFrameRate float64 `json:"source_frame_rate,omitempty" example:"24"`
|
||||
TargetFrameRate float64 `json:"target_frame_rate,omitempty" example:"24"`
|
||||
SlowMotionRate float64 `json:"slow_motion_rate,omitempty" example:"1"`
|
||||
}
|
||||
|
||||
type VideoGenerationRequest struct {
|
||||
Model string `json:"model" example:"video-model"`
|
||||
Prompt string `json:"prompt" example:"A cinematic drone shot over mountains"`
|
||||
|
||||
@@ -161,7 +161,7 @@ func validPricingRuleSetInput(input store.PricingRuleSetInput) bool {
|
||||
return false
|
||||
}
|
||||
switch calculator := strings.TrimSpace(rule.CalculatorType); calculator {
|
||||
case "", "token_usage", "unit_weight", "duration_weight":
|
||||
case "", "token_usage", "unit_weight", "duration_weight", "transition_matrix":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -57,7 +57,11 @@ func TestOpenAPIPublicRoutesUseCanonicalV1Prefix(t *testing.T) {
|
||||
"/api/v1/chat/completions",
|
||||
"/api/v1/responses",
|
||||
"/api/v1/images/generations",
|
||||
"/api/v1/images/vectorize",
|
||||
"/api/v1/videos/generations",
|
||||
"/api/v1/videos/upscales",
|
||||
"/api/v1/pricing/estimate",
|
||||
"/api/workspace/token-usage/daily",
|
||||
"/api/v1/models/{model}:generateContent",
|
||||
"/api/v1/videos/omni-video",
|
||||
"/api/v1/kling/v1/videos/omni-video",
|
||||
|
||||
@@ -195,6 +195,7 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
mux.Handle("GET /api/workspace/user-groups", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.listCurrentUserGroups)))
|
||||
mux.Handle("GET /api/workspace/wallet", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.getWallet)))
|
||||
mux.Handle("GET /api/workspace/wallet/transactions", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.listWalletTransactions)))
|
||||
mux.Handle("GET /api/workspace/token-usage/daily", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.dailyTokenUsage)))
|
||||
mux.Handle("GET /api/workspace/tasks", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.listTasks)))
|
||||
mux.Handle("GET /api/workspace/tasks/{taskID}", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.getTask)))
|
||||
mux.Handle("POST /api/workspace/tasks/{taskID}/cancel", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.cancelTask)))
|
||||
@@ -261,7 +262,10 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
mux.Handle("POST /api/v1/reranks", server.requireUser(auth.PermissionBasic, server.createTask("reranks", true)))
|
||||
mux.Handle("POST /api/v1/images/generations", server.requireUser(auth.PermissionBasic, server.createTask("images.generations", true)))
|
||||
mux.Handle("POST /api/v1/images/edits", server.requireUser(auth.PermissionBasic, server.createTask("images.edits", true)))
|
||||
mux.Handle("POST /api/v1/images/vectorize", server.requireUser(auth.PermissionBasic, server.createImageVectorizeTask()))
|
||||
mux.Handle("POST /api/v1/videos/generations", server.requireUser(auth.PermissionBasic, server.createTask("videos.generations", true)))
|
||||
mux.Handle("POST /api/v1/videos/upscales", server.requireUser(auth.PermissionBasic, server.createVideoUpscaleTask()))
|
||||
mux.Handle("POST /api/v1/video/upscale", server.requireUser(auth.PermissionBasic, server.createVideoUpscaleTask()))
|
||||
mux.Handle("POST /api/v1/video/generations", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.createLegacyVolcesVideoGeneration)))
|
||||
mux.Handle("GET /api/v1/ai/result/{taskID}", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.getLegacyVolcesVideoResult)))
|
||||
mux.Handle("POST /api/v1/song/generations", server.requireUser(auth.PermissionBasic, server.createTask("song.generations", true)))
|
||||
@@ -311,6 +315,10 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
mux.Handle("POST /v1/images/generations", server.requireUser(auth.PermissionBasic, server.createTask("images.generations", true)))
|
||||
mux.Handle("POST /images/edits", server.requireUser(auth.PermissionBasic, server.createTask("images.edits", true)))
|
||||
mux.Handle("POST /v1/images/edits", server.requireUser(auth.PermissionBasic, server.createTask("images.edits", true)))
|
||||
mux.Handle("POST /images/vectorize", server.requireUser(auth.PermissionBasic, server.createImageVectorizeTask()))
|
||||
mux.Handle("POST /v1/images/vectorize", server.requireUser(auth.PermissionBasic, server.createImageVectorizeTask()))
|
||||
mux.Handle("POST /video/upscale", server.requireUser(auth.PermissionBasic, server.createVideoUpscaleTask()))
|
||||
mux.Handle("POST /v1/video/upscale", server.requireUser(auth.PermissionBasic, server.createVideoUpscaleTask()))
|
||||
mux.Handle("POST /v1/videos/omni-video", server.requireKelingAPIKey(http.HandlerFunc(server.createKelingOmniVideo)))
|
||||
mux.Handle("GET /v1/videos/omni-video/{taskID}", server.requireKelingAPIKey(http.HandlerFunc(server.getKelingOmniVideo)))
|
||||
mux.Handle("POST /api/v1/videos/omni-video", server.requireKelingAPIKey(http.HandlerFunc(server.createKelingOmniVideo)))
|
||||
|
||||
@@ -48,6 +48,7 @@ func (s *Server) createVolcesContentsGenerationTask(w http.ResponseWriter, r *ht
|
||||
// @Tags volces-compatible
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param taskID path string true "任务 ID"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /api/v1/contents/generations/tasks/{taskID} [get]
|
||||
func (s *Server) getVolcesContentsGenerationTask(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -104,6 +105,7 @@ func (s *Server) listVolcesContentsGenerationTasks(w http.ResponseWriter, r *htt
|
||||
// @Tags volces-compatible
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param taskID path string true "任务 ID"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /api/v1/contents/generations/tasks/{taskID} [delete]
|
||||
func (s *Server) deleteVolcesContentsGenerationTask(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -170,6 +172,7 @@ func (s *Server) createLegacyVolcesVideoGeneration(w http.ResponseWriter, r *htt
|
||||
// @Tags volces-compatible
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param taskID path string true "任务 ID"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /api/v1/ai/result/{taskID} [get]
|
||||
func (s *Server) getLegacyVolcesVideoResult(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user