feat(workflow): 工作流提供方 - 集成 RunningHub 与 ComfyUI Bridge
This commit is contained in:
583
backend/internal/service/comfy_bridge.go
Normal file
583
backend/internal/service/comfy_bridge.go
Normal file
@@ -0,0 +1,583 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
// 心跳会携带本机工作流字段和拓扑;允许大型 ComfyUI 工作流,但保留硬上限。
|
||||
comfyBridgeCapabilitiesLimit = 4 << 20
|
||||
// 请求主要是工作流和参考素材描述,保持较小上限控制排队内存;结果可能含 base64 视频,单独放宽但仍有硬上限。
|
||||
comfyBridgeRequestPayloadLimit = 16 << 20
|
||||
comfyBridgeResultPayloadLimit = 64 << 20
|
||||
comfyBridgeMaxPending = 32
|
||||
comfyBridgeRequestTTL = 2 * time.Hour
|
||||
comfyBridgePollMaxWait = 60 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
// ComfyBridgeRequestKindGenerate 是现有生成任务的请求类型。空 Kind 会在入队时归一化为该值,
|
||||
// 这样可以继续兼容已有调用方。
|
||||
ComfyBridgeRequestKindGenerate = "generate"
|
||||
// ComfyBridgeRequestKindWorkflowList 和 ComfyBridgeRequestKindWorkflowGet 只用于管理页按需发现。
|
||||
ComfyBridgeRequestKindWorkflowList = "workflow.list"
|
||||
ComfyBridgeRequestKindWorkflowGet = "workflow.get"
|
||||
)
|
||||
|
||||
// CreateComfyBridgeRequest 是用户注册本地 Bridge 时提交的公开信息。
|
||||
// Bridge Token 由服务端生成,不接受客户端自带 Token,避免复用或弱 Token。
|
||||
type CreateComfyBridgeRequest struct {
|
||||
Name string `json:"name"`
|
||||
Capabilities map[string]any `json:"capabilities,omitempty"`
|
||||
}
|
||||
type ComfyBridgeSummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Online bool `json:"online"`
|
||||
LastSeenAt *time.Time `json:"lastSeenAt,omitempty"`
|
||||
LastTaskAt *time.Time `json:"lastTaskAt,omitempty"`
|
||||
Capabilities map[string]any `json:"capabilities,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ComfyBridgeRegistration struct {
|
||||
Bridge ComfyBridgeSummary `json:"bridge"`
|
||||
// Token 只在注册响应中出现一次,后续接口不会重新返回明文。
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ComfyBridgeRequest 是云端后端交给本地 Bridge 的一次请求。
|
||||
// Payload 的协议由 Bridge 按 Kind 解释;生成请求和管理页发现请求共用同一条长轮询通道。
|
||||
type ComfyBridgeRequest struct {
|
||||
ID string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
TaskID string `json:"taskId,omitempty"`
|
||||
BridgeID string `json:"bridgeId"`
|
||||
Payload map[string]any `json:"payload"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ComfyBridgeCompletion struct {
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Result map[string]any `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type comfyBridgeEnvelope struct {
|
||||
request ComfyBridgeRequest
|
||||
done chan ComfyBridgeCompletion
|
||||
}
|
||||
|
||||
// comfyBridgeQueue 只保存长轮询唤醒和当前进程等待者;请求状态与结果以数据库为准。
|
||||
type comfyBridgeQueue struct {
|
||||
mu sync.Mutex
|
||||
pending map[string]*comfyBridgeEnvelope
|
||||
notify map[string]chan struct{}
|
||||
}
|
||||
|
||||
var comfyBridgeRuntimes sync.Map // map[*Service]*comfyBridgeQueue
|
||||
|
||||
func (s *Service) comfyBridgeRuntime() *comfyBridgeQueue {
|
||||
if value, ok := comfyBridgeRuntimes.Load(s); ok {
|
||||
return value.(*comfyBridgeQueue)
|
||||
}
|
||||
runtime := &comfyBridgeQueue{
|
||||
pending: make(map[string]*comfyBridgeEnvelope),
|
||||
notify: make(map[string]chan struct{}),
|
||||
}
|
||||
actual, _ := comfyBridgeRuntimes.LoadOrStore(s, runtime)
|
||||
return actual.(*comfyBridgeQueue)
|
||||
}
|
||||
|
||||
func (s *Service) CreateComfyBridge(userID string, req CreateComfyBridgeRequest) (*ComfyBridgeRegistration, error) {
|
||||
if strings.TrimSpace(userID) == "" {
|
||||
return nil, Unauthorized("请先登录")
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
name = "本地 ComfyUI Bridge"
|
||||
}
|
||||
if len([]rune(name)) > 80 {
|
||||
return nil, BadAuthRequest("Bridge 名称不能超过 80 个字符")
|
||||
}
|
||||
capabilitiesJSON := ""
|
||||
if req.Capabilities != nil {
|
||||
encoded, err := json.Marshal(req.Capabilities)
|
||||
if err != nil {
|
||||
return nil, BadAuthRequest("Bridge 能力信息格式无效")
|
||||
}
|
||||
if len(encoded) > comfyBridgeCapabilitiesLimit {
|
||||
return nil, BadAuthRequest("Bridge 能力信息不能超过 4MB")
|
||||
}
|
||||
capabilitiesJSON = string(encoded)
|
||||
}
|
||||
token := randomToken()
|
||||
now := time.Now()
|
||||
bridge := &model.ComfyBridge{
|
||||
ID: newID(),
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
TokenHash: hashToken(token),
|
||||
Enabled: true,
|
||||
CapabilitiesJSON: capabilitiesJSON,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if err := s.repo.Create(bridge); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ComfyBridgeRegistration{Bridge: comfyBridgeSummary(*bridge), Token: token}, nil
|
||||
}
|
||||
|
||||
func (s *Service) ComfyBridges(userID string) ([]ComfyBridgeSummary, error) {
|
||||
bridges, err := s.repo.ComfyBridgesForUser(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]ComfyBridgeSummary, 0, len(bridges))
|
||||
for _, bridge := range bridges {
|
||||
items = append(items, comfyBridgeSummary(bridge))
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Service) RevokeComfyBridge(userID string, bridgeID string) error {
|
||||
bridgeID = strings.TrimSpace(bridgeID)
|
||||
if bridgeID == "" {
|
||||
return BadAuthRequest("Bridge ID 不能为空")
|
||||
}
|
||||
if err := s.repo.DisableComfyBridge(userID, bridgeID, time.Now()); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return BadAuthRequest("Bridge 不存在或已撤销")
|
||||
}
|
||||
return err
|
||||
}
|
||||
_ = s.repo.FailComfyBridgeRequests(bridgeID, "Bridge 已撤销", time.Now())
|
||||
s.comfyBridgeRuntime().failBridge(bridgeID, errors.New("Bridge 已撤销"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthenticateComfyBridge 校验 Bridge 专用 Token,并刷新心跳。
|
||||
func (s *Service) AuthenticateComfyBridge(token string) (*model.ComfyBridge, error) {
|
||||
token = strings.TrimSpace(token)
|
||||
if len(token) < 32 || len(token) > 256 {
|
||||
return nil, Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
bridge, err := s.repo.ComfyBridgeByTokenHash(hashToken(token))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.repo.TouchComfyBridge(bridge.ID, time.Now()); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return bridge, nil
|
||||
}
|
||||
|
||||
func (s *Service) TouchComfyBridgeHeartbeat(bridgeID string, capabilities map[string]any) error {
|
||||
capabilitiesJSON := ""
|
||||
if capabilities != nil {
|
||||
encoded, err := json.Marshal(capabilities)
|
||||
if err != nil || len(encoded) > comfyBridgeCapabilitiesLimit {
|
||||
return BadAuthRequest("Bridge 能力信息格式无效或超过 4MB")
|
||||
}
|
||||
capabilitiesJSON = string(encoded)
|
||||
}
|
||||
if err := s.repo.UpdateComfyBridgeHeartbeat(strings.TrimSpace(bridgeID), capabilitiesJSON, time.Now()); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnqueueComfyBridgeRequest 将工作流请求持久化,并返回请求 ID。
|
||||
func (s *Service) EnqueueComfyBridgeRequest(ctx context.Context, userID string, bridgeID string, taskID string, payload map[string]any) (*ComfyBridgeRequest, error) {
|
||||
return s.enqueueComfyBridgeRequest(ctx, userID, bridgeID, taskID, "", payload)
|
||||
}
|
||||
|
||||
func (s *Service) enqueueComfyBridgeRequest(ctx context.Context, userID string, bridgeID string, taskID string, requestID string, payload map[string]any) (*ComfyBridgeRequest, error) {
|
||||
if err := contextError(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bridgeID = strings.TrimSpace(bridgeID)
|
||||
taskID = strings.TrimSpace(taskID)
|
||||
if bridgeID == "" || taskID == "" {
|
||||
return nil, BadAuthRequest("Bridge ID 和任务 ID 不能为空")
|
||||
}
|
||||
if payload == nil {
|
||||
return nil, BadAuthRequest("Bridge 请求内容不能为空")
|
||||
}
|
||||
bridge, err := s.repo.ComfyBridgeForUser(userID, bridgeID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, BadAuthRequest("Bridge 不存在或已撤销")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if !bridge.Enabled {
|
||||
return nil, BadAuthRequest("Bridge 不存在或已撤销")
|
||||
}
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, BadAuthRequest("Bridge 请求内容格式无效")
|
||||
}
|
||||
if len(encoded) > comfyBridgeRequestPayloadLimit {
|
||||
return nil, BadAuthRequest("Bridge 请求内容超过 16MB")
|
||||
}
|
||||
// Marshal/Unmarshal 生成独立副本,避免调用方在入队后继续修改 map 导致任务内容漂移。
|
||||
var copied map[string]any
|
||||
if err := json.Unmarshal(encoded, &copied); err != nil {
|
||||
return nil, BadAuthRequest("Bridge 请求内容必须是 JSON 对象")
|
||||
}
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
if count, err := s.repo.PendingComfyBridgeRequestCount(bridgeID, time.Now()); err != nil {
|
||||
return nil, err
|
||||
} else if count >= comfyBridgeMaxPending {
|
||||
return nil, BadAuthRequest("该 Bridge 待处理请求过多,请稍后重试")
|
||||
}
|
||||
requestID = newID()
|
||||
}
|
||||
now := time.Now()
|
||||
record, created, err := s.repo.CreateOrGetComfyBridgeRequest(&model.ComfyBridgeRequest{
|
||||
ID: requestID, TaskID: taskID, UserID: userID, BridgeID: bridgeID, Kind: ComfyBridgeRequestKindGenerate,
|
||||
Status: "queued", PayloadJSON: string(encoded), ExpiresAt: now.Add(comfyBridgeRequestTTL), CreatedAt: now, UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record.UserID != userID || record.BridgeID != bridgeID || record.TaskID != taskID {
|
||||
return nil, Forbidden("Bridge 请求 ID 已被其他任务使用")
|
||||
}
|
||||
request, err := comfyBridgeRequestFromModel(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime := s.comfyBridgeRuntime()
|
||||
runtime.ensure(request)
|
||||
if created {
|
||||
runtime.signal(bridgeID)
|
||||
}
|
||||
if err := s.repo.MarkComfyBridgeTask(bridgeID, now); err != nil {
|
||||
if created {
|
||||
_ = s.repo.CancelComfyBridgeRequest(request.ID, time.Now())
|
||||
runtime.remove(request.ID)
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, BadAuthRequest("Bridge 不存在或已撤销")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// PollComfyBridgeRequest 由本地 Bridge 调用。wait 为 0 时只检查当前队列。
|
||||
func (s *Service) PollComfyBridgeRequest(ctx context.Context, bridge *model.ComfyBridge, wait time.Duration) (*ComfyBridgeRequest, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if bridge == nil || strings.TrimSpace(bridge.ID) == "" || !bridge.Enabled {
|
||||
return nil, Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
if wait < 0 {
|
||||
wait = 0
|
||||
}
|
||||
if wait > comfyBridgePollMaxWait {
|
||||
wait = comfyBridgePollMaxWait
|
||||
}
|
||||
runtime := s.comfyBridgeRuntime()
|
||||
deadline := time.Now().Add(wait)
|
||||
for {
|
||||
record, err := s.repo.ClaimNextComfyBridgeRequest(bridge.ID, time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record != nil {
|
||||
request, err := comfyBridgeRequestFromModel(*record)
|
||||
if err != nil {
|
||||
_ = s.repo.CancelComfyBridgeRequest(record.ID, time.Now())
|
||||
return nil, err
|
||||
}
|
||||
runtime.ensure(request)
|
||||
if err := s.repo.MarkComfyBridgeTask(bridge.ID, time.Now()); err != nil {
|
||||
_ = s.repo.CancelComfyBridgeRequest(request.ID, time.Now())
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, Unauthorized("Bridge 令牌无效或已撤销")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
if wait == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
remaining := time.Until(deadline)
|
||||
if remaining <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
channel := runtime.channel(bridge.ID)
|
||||
timer := time.NewTimer(remaining)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return nil, ctx.Err()
|
||||
case <-timer.C:
|
||||
return nil, nil
|
||||
case <-channel:
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteComfyBridgeRequest 将 Bridge 结果投递给等待该请求的后端 worker。
|
||||
func (s *Service) CompleteComfyBridgeRequest(bridgeID string, completion ComfyBridgeCompletion) error {
|
||||
bridgeID = strings.TrimSpace(bridgeID)
|
||||
completion.RequestID = strings.TrimSpace(completion.RequestID)
|
||||
if bridgeID == "" || completion.RequestID == "" {
|
||||
return BadAuthRequest("Bridge ID 和请求 ID 不能为空")
|
||||
}
|
||||
completion.Status = strings.ToLower(strings.TrimSpace(completion.Status))
|
||||
if completion.Status == "" {
|
||||
completion.Status = "succeeded"
|
||||
}
|
||||
switch completion.Status {
|
||||
case "succeeded", "failed", "cancelled":
|
||||
default:
|
||||
return BadAuthRequest("Bridge 结果状态无效")
|
||||
}
|
||||
if len([]rune(completion.Error)) > 4000 {
|
||||
completion.Error = truncateRunes(completion.Error, 4000)
|
||||
}
|
||||
if completion.Result != nil {
|
||||
encoded, err := json.Marshal(completion.Result)
|
||||
if err != nil || len(encoded) > comfyBridgeResultPayloadLimit {
|
||||
return BadAuthRequest("Bridge 结果内容无效或超过 64MB")
|
||||
}
|
||||
}
|
||||
resultJSON := ""
|
||||
if completion.Result != nil {
|
||||
encoded, _ := json.Marshal(completion.Result)
|
||||
resultJSON = string(encoded)
|
||||
}
|
||||
if _, err := s.repo.CompleteComfyBridgeRequest(bridgeID, completion.RequestID, completion.Status, resultJSON, completion.Error, time.Now()); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return BadAuthRequest("Bridge 请求不存在或已过期")
|
||||
}
|
||||
if errors.Is(err, repository.ErrComfyBridgeRequestOwnership) {
|
||||
return Forbidden("Bridge 不能提交其他设备的请求")
|
||||
}
|
||||
if errors.Is(err, repository.ErrComfyBridgeRequestState) {
|
||||
return BadAuthRequest("Bridge 请求尚未领取")
|
||||
}
|
||||
return err
|
||||
}
|
||||
runtime := s.comfyBridgeRuntime()
|
||||
runtime.mu.Lock()
|
||||
envelope := runtime.pending[completion.RequestID]
|
||||
if envelope != nil {
|
||||
select {
|
||||
case envelope.done <- completion:
|
||||
default:
|
||||
}
|
||||
}
|
||||
runtime.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitComfyBridgeRequest 由生成 worker 调用,直到 Bridge 回传结果或任务被取消。
|
||||
func (s *Service) WaitComfyBridgeRequest(ctx context.Context, requestID string) (ComfyBridgeCompletion, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
return ComfyBridgeCompletion{}, BadAuthRequest("Bridge 请求 ID 不能为空")
|
||||
}
|
||||
runtime := s.comfyBridgeRuntime()
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
record, err := s.repo.ComfyBridgeRequest(requestID)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ComfyBridgeCompletion{}, BadAuthRequest("Bridge 请求不存在或已过期")
|
||||
}
|
||||
if err != nil {
|
||||
return ComfyBridgeCompletion{}, err
|
||||
}
|
||||
completion, terminal, err := comfyBridgeCompletionFromModel(*record)
|
||||
if err != nil {
|
||||
return ComfyBridgeCompletion{}, err
|
||||
}
|
||||
if terminal {
|
||||
runtime.remove(requestID)
|
||||
return completion, nil
|
||||
}
|
||||
request, err := comfyBridgeRequestFromModel(*record)
|
||||
if err != nil {
|
||||
return ComfyBridgeCompletion{}, err
|
||||
}
|
||||
envelope := runtime.ensure(request)
|
||||
select {
|
||||
case <-envelope.done:
|
||||
// 数据库已经在结果接口事务中落盘;下一轮统一从持久化记录读取。
|
||||
case <-ticker.C:
|
||||
case <-ctx.Done():
|
||||
_ = s.repo.CancelComfyBridgeRequest(requestID, time.Now())
|
||||
runtime.remove(requestID)
|
||||
return ComfyBridgeCompletion{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CancelComfyBridgeRequest 供任务取消路径清理尚未完成的 Bridge 请求。
|
||||
func (s *Service) CancelComfyBridgeRequest(requestID string) {
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
return
|
||||
}
|
||||
_ = s.repo.CancelComfyBridgeRequest(requestID, time.Now())
|
||||
runtime := s.comfyBridgeRuntime()
|
||||
runtime.remove(requestID)
|
||||
}
|
||||
|
||||
func comfyBridgeRequestFromModel(record model.ComfyBridgeRequest) (*ComfyBridgeRequest, error) {
|
||||
payload := map[string]any{}
|
||||
if err := json.Unmarshal([]byte(record.PayloadJSON), &payload); err != nil {
|
||||
return nil, errors.New("Bridge 持久化请求内容无效")
|
||||
}
|
||||
return &ComfyBridgeRequest{ID: record.ID, Kind: record.Kind, TaskID: record.TaskID, BridgeID: record.BridgeID, Payload: payload, CreatedAt: record.CreatedAt}, nil
|
||||
}
|
||||
|
||||
func comfyBridgeCompletionFromModel(record model.ComfyBridgeRequest) (ComfyBridgeCompletion, bool, error) {
|
||||
if record.Status != "succeeded" && record.Status != "failed" && record.Status != "cancelled" {
|
||||
return ComfyBridgeCompletion{}, false, nil
|
||||
}
|
||||
completion := ComfyBridgeCompletion{RequestID: record.ID, Status: record.Status, Error: record.Error}
|
||||
if strings.TrimSpace(record.ResultJSON) != "" {
|
||||
if err := json.Unmarshal([]byte(record.ResultJSON), &completion.Result); err != nil {
|
||||
return ComfyBridgeCompletion{}, false, errors.New("Bridge 持久化结果内容无效")
|
||||
}
|
||||
}
|
||||
return completion, true, nil
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) ensure(request *ComfyBridgeRequest) *comfyBridgeEnvelope {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if envelope := q.pending[request.ID]; envelope != nil {
|
||||
return envelope
|
||||
}
|
||||
envelope := &comfyBridgeEnvelope{request: *request, done: make(chan ComfyBridgeCompletion, 1)}
|
||||
q.pending[request.ID] = envelope
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) channel(bridgeID string) <-chan struct{} {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
channel := q.notify[bridgeID]
|
||||
if channel == nil {
|
||||
channel = make(chan struct{}, 1)
|
||||
q.notify[bridgeID] = channel
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) signal(bridgeID string) {
|
||||
q.mu.Lock()
|
||||
q.signalLocked(bridgeID)
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) remove(requestID string) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
delete(q.pending, requestID)
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) failBridge(bridgeID string, err error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
message := "Bridge 请求失败"
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
}
|
||||
for id, envelope := range q.pending {
|
||||
if envelope == nil {
|
||||
delete(q.pending, id)
|
||||
continue
|
||||
}
|
||||
if envelope.request.BridgeID != bridgeID {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case envelope.done <- ComfyBridgeCompletion{RequestID: id, Status: "failed", Error: message}:
|
||||
default:
|
||||
}
|
||||
delete(q.pending, id)
|
||||
}
|
||||
q.signalLocked(bridgeID)
|
||||
}
|
||||
|
||||
func (q *comfyBridgeQueue) signalLocked(bridgeID string) {
|
||||
channel := q.notify[bridgeID]
|
||||
if channel == nil {
|
||||
channel = make(chan struct{}, 1)
|
||||
q.notify[bridgeID] = channel
|
||||
}
|
||||
select {
|
||||
case channel <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func comfyBridgeSummary(bridge model.ComfyBridge) ComfyBridgeSummary {
|
||||
result := ComfyBridgeSummary{ID: bridge.ID, Name: bridge.Name, Enabled: bridge.Enabled, LastSeenAt: bridge.LastSeenAt, LastTaskAt: bridge.LastTaskAt, CreatedAt: bridge.CreatedAt, UpdatedAt: bridge.UpdatedAt}
|
||||
result.Online = bridge.Enabled && bridge.LastSeenAt != nil && time.Since(*bridge.LastSeenAt) <= 90*time.Second
|
||||
if strings.TrimSpace(bridge.CapabilitiesJSON) != "" {
|
||||
var capabilities map[string]any
|
||||
if json.Unmarshal([]byte(bridge.CapabilitiesJSON), &capabilities) == nil && capabilities != nil {
|
||||
result.Capabilities = capabilities
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func contextError(ctx context.Context) error {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user