- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录 - endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支 - ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
312 lines
7.5 KiB
Go
312 lines
7.5 KiB
Go
package admin
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type WindsurfHandler struct {
|
|
authService *service.WindsurfAuthService
|
|
lsService *service.WindsurfLSService
|
|
probeService *service.WindsurfProbeService
|
|
}
|
|
|
|
func NewWindsurfHandler(
|
|
authService *service.WindsurfAuthService,
|
|
lsService *service.WindsurfLSService,
|
|
probeService *service.WindsurfProbeService,
|
|
) *WindsurfHandler {
|
|
return &WindsurfHandler{
|
|
authService: authService,
|
|
lsService: lsService,
|
|
probeService: probeService,
|
|
}
|
|
}
|
|
|
|
func (h *WindsurfHandler) Login(c *gin.Context) {
|
|
var req dto.WindsurfLoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
concurrency := 1
|
|
if req.Concurrency != nil && *req.Concurrency > 0 {
|
|
concurrency = *req.Concurrency
|
|
}
|
|
priority := 0
|
|
if req.Priority != nil {
|
|
priority = *req.Priority
|
|
}
|
|
probeAfter := false
|
|
if req.ProbeAfter != nil {
|
|
probeAfter = *req.ProbeAfter
|
|
}
|
|
|
|
input := &service.WindsurfLoginInput{
|
|
Email: req.Email,
|
|
Password: req.Password,
|
|
Name: req.Name,
|
|
Notes: req.Notes,
|
|
ProxyID: req.ProxyID,
|
|
GroupIDs: req.GroupIDs,
|
|
Concurrency: concurrency,
|
|
Priority: priority,
|
|
ProbeAfter: probeAfter,
|
|
LSInstanceID: req.LSInstanceID,
|
|
}
|
|
|
|
output, err := h.authService.Login(c.Request.Context(), input)
|
|
if err != nil {
|
|
response.Error(c, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, dto.WindsurfLoginResponse{
|
|
AccountID: output.AccountID,
|
|
Platform: "windsurf",
|
|
Type: "windsurf-session",
|
|
Email: output.Email,
|
|
Tier: output.Tier,
|
|
AuthMethod: output.AuthMethod,
|
|
APIKeyPresent: output.APIKeyPresent,
|
|
RefreshTokenPresent: output.RefreshTokenPresent,
|
|
})
|
|
}
|
|
|
|
func (h *WindsurfHandler) BatchLogin(c *gin.Context) {
|
|
var req dto.WindsurfBatchLoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
concurrency := 1
|
|
if req.Concurrency != nil && *req.Concurrency > 0 {
|
|
concurrency = *req.Concurrency
|
|
}
|
|
priority := 0
|
|
if req.Priority != nil {
|
|
priority = *req.Priority
|
|
}
|
|
probeAfter := false
|
|
if req.ProbeAfter != nil {
|
|
probeAfter = *req.ProbeAfter
|
|
}
|
|
|
|
results, err := h.authService.BatchLogin(
|
|
c.Request.Context(),
|
|
req.Items,
|
|
req.ProxyID,
|
|
req.GroupIDs,
|
|
concurrency,
|
|
priority,
|
|
probeAfter,
|
|
)
|
|
if err != nil {
|
|
response.Error(c, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
successCount := 0
|
|
failCount := 0
|
|
batchResults := make([]dto.WindsurfBatchLoginResult, 0, len(results))
|
|
|
|
for _, r := range results {
|
|
br := dto.WindsurfBatchLoginResult{
|
|
Email: r.Email,
|
|
Success: r.Success,
|
|
Error: r.Error,
|
|
}
|
|
if r.Success && r.Output != nil {
|
|
successCount++
|
|
br.Account = &dto.WindsurfLoginResponse{
|
|
AccountID: r.Output.AccountID,
|
|
Platform: "windsurf",
|
|
Type: "windsurf-session",
|
|
Email: r.Output.Email,
|
|
Tier: r.Output.Tier,
|
|
AuthMethod: r.Output.AuthMethod,
|
|
APIKeyPresent: r.Output.APIKeyPresent,
|
|
RefreshTokenPresent: r.Output.RefreshTokenPresent,
|
|
}
|
|
} else {
|
|
failCount++
|
|
}
|
|
batchResults = append(batchResults, br)
|
|
}
|
|
|
|
response.Success(c, dto.WindsurfBatchLoginResponse{
|
|
Results: batchResults,
|
|
Total: len(results),
|
|
SuccessCount: successCount,
|
|
FailCount: failCount,
|
|
})
|
|
}
|
|
|
|
func (h *WindsurfHandler) RefreshToken(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
response.BadRequest(c, "invalid account id")
|
|
return
|
|
}
|
|
|
|
if err := h.authService.RefreshToken(c.Request.Context(), id); err != nil {
|
|
response.Error(c, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, dto.WindsurfRefreshTokenResponse{
|
|
Refreshed: true,
|
|
})
|
|
}
|
|
|
|
func (h *WindsurfHandler) BatchRefreshTokens(c *gin.Context) {
|
|
var req dto.WindsurfBatchIDsRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
successCount := 0
|
|
failCount := 0
|
|
|
|
for _, id := range req.AccountIDs {
|
|
if err := h.authService.RefreshToken(c.Request.Context(), id); err != nil {
|
|
failCount++
|
|
} else {
|
|
successCount++
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"total": len(req.AccountIDs),
|
|
"success_count": successCount,
|
|
"fail_count": failCount,
|
|
})
|
|
}
|
|
|
|
func (h *WindsurfHandler) GetLSStatus(c *gin.Context) {
|
|
if h.lsService == nil {
|
|
response.Success(c, dto.WindsurfLSStatusResponse{
|
|
Mode: "disabled",
|
|
Healthy: false,
|
|
})
|
|
return
|
|
}
|
|
|
|
status := h.lsService.Status()
|
|
resp := dto.WindsurfLSStatusResponse{
|
|
Mode: status.Mode,
|
|
Healthy: status.Healthy,
|
|
Instances: status.Instances,
|
|
Endpoint: status.Endpoint,
|
|
}
|
|
|
|
if dc, ok := h.lsService.Connector().(*windsurf.DockerDiscoveryConnector); ok {
|
|
for _, inst := range dc.InstanceStatuses() {
|
|
resp.Details = append(resp.Details, dto.WindsurfLSInstanceDetail{
|
|
ContainerID: inst.ContainerID,
|
|
ContainerName: inst.ContainerName,
|
|
Host: inst.Host,
|
|
Port: inst.Port,
|
|
Healthy: inst.Healthy,
|
|
DiscoveredAt: inst.DiscoveredAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
LastProbeAt: inst.LastProbeAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
LastProbeErr: inst.LastProbeErr,
|
|
})
|
|
}
|
|
}
|
|
|
|
response.Success(c, resp)
|
|
}
|
|
|
|
func (h *WindsurfHandler) ListModels(c *gin.Context) {
|
|
models := windsurf.ListModelsOpenAI()
|
|
response.Success(c, models)
|
|
}
|
|
|
|
func (h *WindsurfHandler) Probe(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
response.BadRequest(c, "invalid account id")
|
|
return
|
|
}
|
|
|
|
result, err := h.probeService.ProbeAccount(c.Request.Context(), id)
|
|
if err != nil {
|
|
response.Error(c, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, result)
|
|
}
|
|
|
|
func (h *WindsurfHandler) BatchProbe(c *gin.Context) {
|
|
var req dto.WindsurfBatchIDsRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
type probeResult struct {
|
|
AccountID int64 `json:"account_id"`
|
|
Success bool `json:"success"`
|
|
Tier string `json:"tier,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
results := make([]probeResult, 0, len(req.AccountIDs))
|
|
successCount := 0
|
|
failCount := 0
|
|
|
|
for _, id := range req.AccountIDs {
|
|
r, err := h.probeService.ProbeAccount(c.Request.Context(), id)
|
|
if err != nil {
|
|
failCount++
|
|
results = append(results, probeResult{AccountID: id, Error: err.Error()})
|
|
continue
|
|
}
|
|
if r.Error != "" {
|
|
failCount++
|
|
results = append(results, probeResult{AccountID: id, Error: r.Error})
|
|
continue
|
|
}
|
|
successCount++
|
|
results = append(results, probeResult{AccountID: id, Success: true, Tier: r.Tier})
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"results": results,
|
|
"total": len(req.AccountIDs),
|
|
"success_count": successCount,
|
|
"fail_count": failCount,
|
|
})
|
|
}
|
|
|
|
func (h *WindsurfHandler) GetRuntime(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
response.BadRequest(c, "invalid account id")
|
|
return
|
|
}
|
|
|
|
result, err := h.probeService.GetRuntime(c.Request.Context(), id)
|
|
if err != nil {
|
|
response.Error(c, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
response.Success(c, result)
|
|
}
|