-
Notifications
You must be signed in to change notification settings - Fork 11.1k
即梦和vidu支持 OpenAI sdk生成和查询视频 #2018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -158,7 +158,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *rela | |||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| c.JSON(http.StatusOK, gin.H{"task_id": jResp.Data.TaskID}) | ||||||||||||||||||||||||||||
| ov := relaycommon.NewOpenAIVideo() | ||||||||||||||||||||||||||||
| ov.ID = jResp.Data.TaskID | ||||||||||||||||||||||||||||
| ov.TaskID = jResp.Data.TaskID | ||||||||||||||||||||||||||||
| ov.CreatedAt = time.Now().Unix() | ||||||||||||||||||||||||||||
| ov.Model = info.OriginModelName | ||||||||||||||||||||||||||||
| c.JSON(http.StatusOK, ov) | ||||||||||||||||||||||||||||
|
Comment on lines
+161
to
+166
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set creation response status to “queued”
ov := relaycommon.NewOpenAIVideo()
ov.ID = jResp.Data.TaskID
ov.TaskID = jResp.Data.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
+ ov.Status = relaycommon.VideoStatusQueued
c.JSON(http.StatusOK, ov)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| return jResp.Data.TaskID, responseBody, nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -400,6 +405,30 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e | |||||||||||||||||||||||||||
| return &taskResult, nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon.OpenAIVideo, error) { | ||||||||||||||||||||||||||||
| var jimengResp responseTask | ||||||||||||||||||||||||||||
| if err := json.Unmarshal(originTask.Data, &jimengResp); err != nil { | ||||||||||||||||||||||||||||
| return nil, errors.Wrap(err, "unmarshal jimeng task data failed") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| openAIVideo := relaycommon.NewOpenAIVideo() | ||||||||||||||||||||||||||||
| openAIVideo.ID = originTask.TaskID | ||||||||||||||||||||||||||||
| openAIVideo.Status = originTask.Status.ToVideoStatus() | ||||||||||||||||||||||||||||
| openAIVideo.SetProgressStr(originTask.Progress) | ||||||||||||||||||||||||||||
| openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl) | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Check VideoUrl before setting metadata. Unlike the vidu and kling adapters (which verify URL presence), this unconditionally sets metadata even when Apply this diff to align with the other adapters: - openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
+ if jimengResp.Data.VideoUrl != "" {
+ openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| openAIVideo.CreatedAt = originTask.CreatedAt | ||||||||||||||||||||||||||||
| openAIVideo.CompletedAt = originTask.UpdatedAt | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if jimengResp.Code != 10000 { | ||||||||||||||||||||||||||||
| openAIVideo.Error = &relaycommon.OpenAIVideoError{ | ||||||||||||||||||||||||||||
| Message: jimengResp.Message, | ||||||||||||||||||||||||||||
| Code: fmt.Sprintf("%d", jimengResp.Code), | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return openAIVideo, nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func isNewAPIRelay(apiKey string) bool { | ||||||||||||||||||||||||||||
| return strings.HasPrefix(apiKey, "sk-") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,13 +6,11 @@ import ( | |
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/QuantumNous/new-api/model" | ||
|
|
||
| "github.com/bytedance/gopkg/util/logger" | ||
| "github.com/samber/lo" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
|
|
@@ -190,8 +188,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *rela | |
| taskErr = service.TaskErrorWrapperLocal(fmt.Errorf(kResp.Message), "task_failed", http.StatusBadRequest) | ||
| return | ||
| } | ||
| kResp.TaskId = kResp.Data.TaskId | ||
| c.JSON(http.StatusOK, kResp) | ||
| ov := relaycommon.NewOpenAIVideo() | ||
| ov.ID = kResp.Data.TaskId | ||
| ov.TaskID = kResp.Data.TaskId | ||
| ov.CreatedAt = time.Now().Unix() | ||
| ov.Model = info.OriginModelName | ||
| c.JSON(http.StatusOK, ov) | ||
| return kResp.Data.TaskId, responseBody, nil | ||
|
Comment on lines
+191
to
197
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Populate OpenAIVideo status on creation responses
ov := relaycommon.NewOpenAIVideo()
ov.ID = kResp.Data.TaskId
ov.TaskID = kResp.Data.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
+ ov.Status = relaycommon.VideoStatusQueued
c.JSON(http.StatusOK, ov)🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
|
|
@@ -371,31 +373,17 @@ func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon | |
| return nil, errors.Wrap(err, "unmarshal kling task data failed") | ||
| } | ||
|
|
||
| convertProgress := func(progress string) int { | ||
| progress = strings.TrimSuffix(progress, "%") | ||
| p, err := strconv.Atoi(progress) | ||
| if err != nil { | ||
| logger.Warnf("convert progress failed, progress: %s, err: %v", progress, err) | ||
| } | ||
| return p | ||
| } | ||
|
|
||
| openAIVideo := &relaycommon.OpenAIVideo{ | ||
| ID: klingResp.Data.TaskId, | ||
| Object: "video", | ||
| //Model: "kling-v1", //todo save model | ||
| Status: string(originTask.Status), | ||
| CreatedAt: klingResp.Data.CreatedAt, | ||
| CompletedAt: klingResp.Data.UpdatedAt, | ||
| Metadata: make(map[string]any), | ||
| Progress: convertProgress(originTask.Progress), | ||
| } | ||
| openAIVideo := relaycommon.NewOpenAIVideo() | ||
| openAIVideo.ID = originTask.TaskID | ||
| openAIVideo.Status = originTask.Status.ToVideoStatus() | ||
| openAIVideo.SetProgressStr(originTask.Progress) | ||
| openAIVideo.CreatedAt = klingResp.Data.CreatedAt | ||
| openAIVideo.CompletedAt = klingResp.Data.UpdatedAt | ||
|
|
||
| // 处理视频 URL | ||
| if len(klingResp.Data.TaskResult.Videos) > 0 { | ||
| video := klingResp.Data.TaskResult.Videos[0] | ||
| if video.Url != "" { | ||
| openAIVideo.Metadata["url"] = video.Url | ||
| openAIVideo.SetMetadata("url", video.Url) | ||
| } | ||
| if video.Duration != "" { | ||
| openAIVideo.Seconds = video.Duration | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| "github.com/gin-gonic/gin" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -135,7 +136,7 @@ func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, req | |||||||||||||||||||||||||||||||
| return channel.DoTaskApiRequest(a, c, info, requestBody) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, _ *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) { | ||||||||||||||||||||||||||||||||
| func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) { | ||||||||||||||||||||||||||||||||
| responseBody, err := io.ReadAll(resp.Body) | ||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||
| taskErr = service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError) | ||||||||||||||||||||||||||||||||
|
|
@@ -154,7 +155,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, _ *relayco | |||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| c.JSON(http.StatusOK, vResp) | ||||||||||||||||||||||||||||||||
| ov := relaycommon.NewOpenAIVideo() | ||||||||||||||||||||||||||||||||
| ov.ID = vResp.TaskId | ||||||||||||||||||||||||||||||||
| ov.TaskID = vResp.TaskId | ||||||||||||||||||||||||||||||||
| ov.CreatedAt = time.Now().Unix() | ||||||||||||||||||||||||||||||||
| ov.Model = info.OriginModelName | ||||||||||||||||||||||||||||||||
| c.JSON(http.StatusOK, ov) | ||||||||||||||||||||||||||||||||
| return vResp.TaskId, responseBody, nil | ||||||||||||||||||||||||||||||||
|
Comment on lines
+158
to
164
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return a valid status in the creation response The OpenAI video schema expects ov := relaycommon.NewOpenAIVideo()
ov.ID = vResp.TaskId
ov.TaskID = vResp.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
+ ov.Status = relaycommon.VideoStatusQueued
c.JSON(http.StatusOK, ov)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -256,3 +262,30 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e | |||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return taskInfo, nil | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon.OpenAIVideo, error) { | ||||||||||||||||||||||||||||||||
| var viduResp taskResultResponse | ||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(originTask.Data, &viduResp); err != nil { | ||||||||||||||||||||||||||||||||
| return nil, errors.Wrap(err, "unmarshal vidu task data failed") | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| openAIVideo := relaycommon.NewOpenAIVideo() | ||||||||||||||||||||||||||||||||
| openAIVideo.ID = originTask.TaskID | ||||||||||||||||||||||||||||||||
| openAIVideo.Status = originTask.Status.ToVideoStatus() | ||||||||||||||||||||||||||||||||
| openAIVideo.SetProgressStr(originTask.Progress) | ||||||||||||||||||||||||||||||||
| openAIVideo.CreatedAt = originTask.CreatedAt | ||||||||||||||||||||||||||||||||
| openAIVideo.CompletedAt = originTask.UpdatedAt | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if len(viduResp.Creations) > 0 && viduResp.Creations[0].URL != "" { | ||||||||||||||||||||||||||||||||
| openAIVideo.SetMetadata("url", viduResp.Creations[0].URL) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if viduResp.State == "failed" && viduResp.ErrCode != "" { | ||||||||||||||||||||||||||||||||
| openAIVideo.Error = &relaycommon.OpenAIVideoError{ | ||||||||||||||||||||||||||||||||
| Message: viduResp.ErrCode, | ||||||||||||||||||||||||||||||||
| Code: viduResp.ErrCode, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return openAIVideo, nil | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||||||||||||||||||
| package common | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import ( | ||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const ( | ||||||||||||||||||||||
| VideoStatusUnknown = "unknown" | ||||||||||||||||||||||
| VideoStatusQueued = "queued" | ||||||||||||||||||||||
| VideoStatusInProgress = "in_progress" | ||||||||||||||||||||||
| VideoStatusCompleted = "completed" | ||||||||||||||||||||||
| VideoStatusFailed = "failed" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type OpenAIVideo struct { | ||||||||||||||||||||||
| ID string `json:"id"` | ||||||||||||||||||||||
| TaskID string `json:"task_id,omitempty"` //兼容旧接口 待废弃 | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarify deprecation timeline for TaskID. The deprecation comment lacks guidance on when Example: TaskID string `json:"task_id,omitempty"` // Deprecated: use ID instead. Will be removed in v2.0.🤖 Prompt for AI Agents |
||||||||||||||||||||||
| Object string `json:"object"` | ||||||||||||||||||||||
| Model string `json:"model"` | ||||||||||||||||||||||
| Status string `json:"status"` // Should use VideoStatus constants: VideoStatusQueued, VideoStatusInProgress, VideoStatusCompleted, VideoStatusFailed | ||||||||||||||||||||||
| Progress int `json:"progress"` | ||||||||||||||||||||||
| CreatedAt int64 `json:"created_at"` | ||||||||||||||||||||||
| CompletedAt int64 `json:"completed_at,omitempty"` | ||||||||||||||||||||||
| ExpiresAt int64 `json:"expires_at,omitempty"` | ||||||||||||||||||||||
| Seconds string `json:"seconds,omitempty"` | ||||||||||||||||||||||
| Size string `json:"size,omitempty"` | ||||||||||||||||||||||
| RemixedFromVideoID string `json:"remixed_from_video_id,omitempty"` | ||||||||||||||||||||||
| Error *OpenAIVideoError `json:"error,omitempty"` | ||||||||||||||||||||||
| Metadata map[string]any `json:"metadata,omitempty"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (m *OpenAIVideo) SetProgressStr(progress string) { | ||||||||||||||||||||||
| progress = strings.TrimSuffix(progress, "%") | ||||||||||||||||||||||
| m.Progress, _ = strconv.Atoi(progress) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+33
to
+36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle parsing errors in SetProgressStr. The error from Consider one of these approaches: Option 1: Return the error -func (m *OpenAIVideo) SetProgressStr(progress string) {
+func (m *OpenAIVideo) SetProgressStr(progress string) error {
progress = strings.TrimSuffix(progress, "%")
- m.Progress, _ = strconv.Atoi(progress)
+ var err error
+ m.Progress, err = strconv.Atoi(progress)
+ return err
}Option 2: Log the error and set a default +import "log"
+
func (m *OpenAIVideo) SetProgressStr(progress string) {
progress = strings.TrimSuffix(progress, "%")
- m.Progress, _ = strconv.Atoi(progress)
+ var err error
+ m.Progress, err = strconv.Atoi(progress)
+ if err != nil {
+ log.Printf("Failed to parse progress '%s': %v", progress, err)
+ m.Progress = 0
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| func (m *OpenAIVideo) SetMetadata(k string, v any) { | ||||||||||||||||||||||
| if m.Metadata == nil { | ||||||||||||||||||||||
| m.Metadata = make(map[string]any) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| m.Metadata[k] = v | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| func NewOpenAIVideo() *OpenAIVideo { | ||||||||||||||||||||||
| return &OpenAIVideo{ | ||||||||||||||||||||||
| Object: "video", | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type OpenAIVideoError struct { | ||||||||||||||||||||||
| Message string `json:"message"` | ||||||||||||||||||||||
| Code string `json:"code"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map NOT_START to a valid video status
InitTaskinitializes tasks withTaskStatusNotStart, butToVideoStatus()currently drops that into the default branch, returning"unknown". Downstream OpenAI-compatible clients expect queued/in-progress/etc.; returning unknown for every freshly created task is misleading and breaks status-based flows. Please mapTaskStatusNotStart(and optionally any other pre-start aliases) toVideoStatusQueued.📝 Committable suggestion
🤖 Prompt for AI Agents