Commit 3fcd741c by Seefs Committed by GitHub

refactor: codex usage ui (#5595)

* refactor: codex usage ui

* feat: show Codex reset credit details

* feat: add Codex usage reset flow
parent 6bd69f3e
...@@ -18,6 +18,46 @@ import ( ...@@ -18,6 +18,46 @@ import (
) )
func GetCodexChannelUsage(c *gin.Context) { func GetCodexChannelUsage(c *gin.Context) {
fetchCodexChannelWhamData(
c,
service.FetchCodexWhamUsage,
"failed to fetch codex usage",
"获取用量信息失败,请稍后重试",
)
}
func GetCodexChannelRateLimitResetCredits(c *gin.Context) {
fetchCodexChannelWhamData(
c,
service.FetchCodexWhamRateLimitResetCredits,
"failed to fetch codex reset credits",
"获取重置次数详情失败,请稍后重试",
)
}
func ResetCodexChannelUsage(c *gin.Context) {
fetchCodexChannelWhamData(
c,
service.ConsumeCodexWhamRateLimitResetCredit,
"failed to reset codex usage",
"重置用量失败,请稍后重试",
)
}
type codexWhamFetchFunc func(
ctx context.Context,
client *http.Client,
baseURL string,
accessToken string,
accountID string,
) (statusCode int, body []byte, err error)
func fetchCodexChannelWhamData(
c *gin.Context,
fetch codexWhamFetchFunc,
logPrefix string,
userMessage string,
) {
channelId, err := strconv.Atoi(c.Param("id")) channelId, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
common.ApiError(c, fmt.Errorf("invalid channel id: %w", err)) common.ApiError(c, fmt.Errorf("invalid channel id: %w", err))
...@@ -68,10 +108,10 @@ func GetCodexChannelUsage(c *gin.Context) { ...@@ -68,10 +108,10 @@ func GetCodexChannelUsage(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel() defer cancel()
statusCode, body, err := service.FetchCodexWhamUsage(ctx, client, ch.GetBaseURL(), accessToken, accountID) statusCode, body, err := fetch(ctx, client, ch.GetBaseURL(), accessToken, accountID)
if err != nil { if err != nil {
common.SysError("failed to fetch codex usage: " + err.Error()) common.SysError(logPrefix + ": " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取用量信息失败,请稍后重试"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": userMessage})
return return
} }
...@@ -98,10 +138,10 @@ func GetCodexChannelUsage(c *gin.Context) { ...@@ -98,10 +138,10 @@ func GetCodexChannelUsage(c *gin.Context) {
ctx2, cancel2 := context.WithTimeout(c.Request.Context(), 15*time.Second) ctx2, cancel2 := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel2() defer cancel2()
statusCode, body, err = service.FetchCodexWhamUsage(ctx2, client, ch.GetBaseURL(), oauthKey.AccessToken, accountID) statusCode, body, err = fetch(ctx2, client, ch.GetBaseURL(), oauthKey.AccessToken, accountID)
if err != nil { if err != nil {
common.SysError("failed to fetch codex usage after refresh: " + err.Error()) common.SysError(logPrefix + " after refresh: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取用量信息失败,请稍后重试"}) c.JSON(http.StatusOK, gin.H{"success": false, "message": userMessage})
return return
} }
} }
......
...@@ -251,6 +251,8 @@ func SetApiRouter(router *gin.Engine) { ...@@ -251,6 +251,8 @@ func SetApiRouter(router *gin.Engine) {
channelRoute.POST("/fetch_models", middleware.RootAuth(), controller.FetchModels) channelRoute.POST("/fetch_models", middleware.RootAuth(), controller.FetchModels)
channelRoute.POST("/:id/codex/refresh", controller.RefreshCodexChannelCredential) channelRoute.POST("/:id/codex/refresh", controller.RefreshCodexChannelCredential)
channelRoute.GET("/:id/codex/usage", controller.GetCodexChannelUsage) channelRoute.GET("/:id/codex/usage", controller.GetCodexChannelUsage)
channelRoute.GET("/:id/codex/usage/reset-credits", controller.GetCodexChannelRateLimitResetCredits)
channelRoute.POST("/:id/codex/usage/reset", controller.ResetCodexChannelUsage)
channelRoute.POST("/ollama/pull", controller.OllamaPullModel) channelRoute.POST("/ollama/pull", controller.OllamaPullModel)
channelRoute.POST("/ollama/pull/stream", controller.OllamaPullModelStream) channelRoute.POST("/ollama/pull/stream", controller.OllamaPullModelStream)
channelRoute.DELETE("/ollama/delete", controller.OllamaDeleteModel) channelRoute.DELETE("/ollama/delete", controller.OllamaDeleteModel)
......
package service package service
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"github.com/QuantumNous/new-api/common"
"github.com/google/uuid"
) )
func FetchCodexWhamUsage( func FetchCodexWhamUsage(
...@@ -35,13 +39,50 @@ func FetchCodexWhamUsage( ...@@ -35,13 +39,50 @@ func FetchCodexWhamUsage(
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
req.Header.Set("Authorization", "Bearer "+at) setCodexWhamRequestHeaders(req, at, aid)
req.Header.Set("chatgpt-account-id", aid)
req.Header.Set("Accept", "application/json") resp, err := client.Do(req)
if req.Header.Get("originator") == "" { if err != nil {
req.Header.Set("originator", "codex_cli_rs") return 0, nil, err
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return resp.StatusCode, nil, err
}
return resp.StatusCode, body, nil
}
func FetchCodexWhamRateLimitResetCredits(
ctx context.Context,
client *http.Client,
baseURL string,
accessToken string,
accountID string,
) (statusCode int, body []byte, err error) {
if client == nil {
return 0, nil, fmt.Errorf("nil http client")
}
bu := strings.TrimRight(strings.TrimSpace(baseURL), "/")
if bu == "" {
return 0, nil, fmt.Errorf("empty baseURL")
}
at := strings.TrimSpace(accessToken)
aid := strings.TrimSpace(accountID)
if at == "" {
return 0, nil, fmt.Errorf("empty accessToken")
}
if aid == "" {
return 0, nil, fmt.Errorf("empty accountID")
} }
req, err := http.NewRequestWithContext(ctx, http.MethodGet, bu+"/backend-api/wham/rate-limit-reset-credits", nil)
if err != nil {
return 0, nil, err
}
setCodexWhamRequestHeaders(req, at, aid)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
...@@ -54,3 +95,67 @@ func FetchCodexWhamUsage( ...@@ -54,3 +95,67 @@ func FetchCodexWhamUsage(
} }
return resp.StatusCode, body, nil return resp.StatusCode, body, nil
} }
func ConsumeCodexWhamRateLimitResetCredit(
ctx context.Context,
client *http.Client,
baseURL string,
accessToken string,
accountID string,
) (statusCode int, body []byte, err error) {
if client == nil {
return 0, nil, fmt.Errorf("nil http client")
}
bu := strings.TrimRight(strings.TrimSpace(baseURL), "/")
if bu == "" {
return 0, nil, fmt.Errorf("empty baseURL")
}
at := strings.TrimSpace(accessToken)
aid := strings.TrimSpace(accountID)
if at == "" {
return 0, nil, fmt.Errorf("empty accessToken")
}
if aid == "" {
return 0, nil, fmt.Errorf("empty accountID")
}
requestBody, err := common.Marshal(map[string]string{
"redeem_request_id": uuid.NewString(),
})
if err != nil {
return 0, nil, err
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
bu+"/backend-api/wham/rate-limit-reset-credits/consume",
bytes.NewReader(requestBody),
)
if err != nil {
return 0, nil, err
}
setCodexWhamRequestHeaders(req, at, aid)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return resp.StatusCode, nil, err
}
return resp.StatusCode, body, nil
}
func setCodexWhamRequestHeaders(req *http.Request, accessToken string, accountID string) {
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("chatgpt-account-id", accountID)
req.Header.Set("Accept", "application/json")
if req.Header.Get("originator") == "" {
req.Header.Set("originator", "codex_cli_rs")
}
}
...@@ -53,6 +53,10 @@ export type CodexUsageResponse = { ...@@ -53,6 +53,10 @@ export type CodexUsageResponse = {
data?: Record<string, unknown> data?: Record<string, unknown>
} }
export type CodexResetCreditsResponse = CodexUsageResponse
export type CodexUsageResetResponse = CodexUsageResponse
export type CodexCredentialRefreshResponse = { export type CodexCredentialRefreshResponse = {
success: boolean success: boolean
message?: string message?: string
...@@ -287,6 +291,27 @@ export async function getCodexUsage( ...@@ -287,6 +291,27 @@ export async function getCodexUsage(
return res.data return res.data
} }
export async function getCodexResetCredits(
channelId: number
): Promise<CodexResetCreditsResponse> {
const res = await api.get(
`/api/channel/${channelId}/codex/usage/reset-credits`,
channelActionConfig({ disableDuplicate: true })
)
return res.data
}
export async function resetCodexUsage(
channelId: number
): Promise<CodexUsageResetResponse> {
const res = await api.post(
`/api/channel/${channelId}/codex/usage/reset`,
{},
channelActionConfig({ disableDuplicate: true })
)
return res.data
}
// ============================================================================ // ============================================================================
// Multi-Key Management // Multi-Key Management
// ============================================================================ // ============================================================================
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment