diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index ecf1216..fde02bc 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -161,6 +161,9 @@ export const apiClient = { deleteToken: (key: string) => request(`/api/admin/token/${encodeURIComponent(key)}`, { method: 'DELETE' }), + resetTokenUsage: (key: string) => + request(`/api/admin/token/${encodeURIComponent(key)}/reset`, { method: 'POST' }), + // Pricing APIs getPricing: () => request('/api/admin/pricing', { method: 'GET' }), diff --git a/frontend/src/pages/Tokens.tsx b/frontend/src/pages/Tokens.tsx index 80d5a63..748cd7a 100644 --- a/frontend/src/pages/Tokens.tsx +++ b/frontend/src/pages/Tokens.tsx @@ -25,6 +25,7 @@ import { ChevronRight, AlertCircle, Search, + RotateCcw, } from 'lucide-react' import { Checkbox } from '@/components/ui/checkbox' import { PageContainer } from '@/components/ui/page-container' @@ -106,6 +107,19 @@ export function Tokens() { }, }) + const resetUsageMutation = useMutation({ + mutationFn: async (key: string) => { + return apiClient.resetTokenUsage(key) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['tokens'] }) + addToast('已用额度已重置', 'success') + }, + onError: (error: any) => { + addToast('重置失败:' + error.message, 'error') + }, + }) + const resetForm = () => { setFormData({ name: '', channel_keys: [], total_quota: 0 }) setTokenKey('') @@ -137,6 +151,12 @@ export function Tokens() { } } + const handleResetUsage = (key: string) => { + if (confirm(`确定要重置此令牌的已用额度吗?`)) { + resetUsageMutation.mutate(key) + } + } + const handleCopy = async (text: string) => { try { await copyToClipboard(text) @@ -318,6 +338,13 @@ export function Tokens() { 编辑 + - + diff --git a/src/admin/index.ts b/src/admin/index.ts index ad93b5b..d489de8 100644 --- a/src/admin/index.ts +++ b/src/admin/index.ts @@ -5,7 +5,7 @@ import { ChannelGetEndpoint, ChannelUpsertEndpoint, ChannelDeleteEndpoint } from "./channel_api" import { - TokenListEndpoint, TokenUpsertEndpoint, TokenDeleteEndpoint + TokenListEndpoint, TokenUpsertEndpoint, TokenDeleteEndpoint, TokenResetUsageEndpoint } from "./token_api" import { PricingGetEndpoint, PricingUpdateEndpoint @@ -34,6 +34,7 @@ api.delete("/api/admin/channel/:key", ChannelDeleteEndpoint) // Token management routes api.get("/api/admin/token", TokenListEndpoint) api.post("/api/admin/token/:key", TokenUpsertEndpoint) +api.post("/api/admin/token/:key/reset", TokenResetUsageEndpoint) api.delete("/api/admin/token/:key", TokenDeleteEndpoint) // Pricing management routes diff --git a/src/admin/token_api.ts b/src/admin/token_api.ts index f3c5150..54257c6 100644 --- a/src/admin/token_api.ts +++ b/src/admin/token_api.ts @@ -98,6 +98,36 @@ export class TokenUpsertEndpoint extends OpenAPIRoute { } } +// Token 重置额度 API +export class TokenResetUsageEndpoint extends OpenAPIRoute { + schema = { + tags: ['Admin API'], + summary: 'Reset token usage to zero', + request: { + params: z.object({ + key: z.string().describe('Token key'), + }), + }, + responses: { + ...CommonSuccessfulResponse(z.boolean()), + ...CommonErrorResponse, + }, + }; + + async handle(c: Context) { + const { key } = c.req.param(); + + const result = await c.env.DB.prepare( + `UPDATE api_token SET usage = 0, updated_at = datetime('now') WHERE key = ?` + ).bind(key).run(); + + return { + success: true, + data: result.success + } as CommonResponse; + } +} + // Token 删除 API export class TokenDeleteEndpoint extends OpenAPIRoute { schema = {