From 1ef55020830fbfb863337808b4bf46b5b05dce8c Mon Sep 17 00:00:00 2001 From: Jeff Gu Date: Thu, 9 Apr 2026 10:03:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E4=B8=BA=20One=20API=20on=20Workers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +- frontend/README.md | 4 +- frontend/index.html | 2 +- frontend/src/components/layout/AppLayout.tsx | 2 +- frontend/src/components/layout/Sidebar.tsx | 202 +++++++++---------- frontend/src/pages/Dashboard.tsx | 2 +- package.json | 2 +- src/index.ts | 2 +- tests/run-e2e.sh | 8 +- tests/setup-test-data.sh | 18 +- type.d.ts | 2 +- 11 files changed, 122 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 0ffdb7e..a3c69ac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWSL One API +# One API on Workers 一个基于 Cloudflare Workers 的 OpenAI API 代理服务,支持多渠道管理、Token 管理和使用量统计。 @@ -22,7 +22,7 @@ flowchart LR end subgraph CF["Cloudflare Workers"] - B[AWSL One API] + B[One API on Workers] D[(Cloudflare D1
Database)] end @@ -41,7 +41,7 @@ flowchart LR

📁 项目结构

```text -awsl-one-api/ +one-api-workers/ ├── src/ # 源代码目录 │ ├── admin/ # 管理接口 │ │ ├── channel_api.ts # 渠道管理 API @@ -93,7 +93,7 @@ bun install ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", - "name": "awsl-one-api", + "name": "one-api-workers", "main": "src/index.ts", "compatibility_date": "2025-04-28", "workers_dev": false, @@ -120,7 +120,7 @@ bun install ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", - "name": "awsl-one-api", + "name": "one-api-workers", "main": "src/index.ts", "compatibility_date": "2025-04-28", "workers_dev": false, @@ -134,7 +134,7 @@ bun install 3. 创建 Cloudflare D1 数据库: ```bash -wrangler d1 create awsl-one-api +wrangler d1 create one-api-workers ``` 4. 在 Cloudflare Dashboard 中手动绑定生产域名: diff --git a/frontend/README.md b/frontend/README.md index 0cc665a..3115f14 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,6 +1,6 @@ -# AWSL One API Frontend +# One API on Workers Frontend -Modern React + Vite + TypeScript frontend for AWSL One API management platform. +Modern React + Vite + TypeScript frontend for One API on Workers management platform. ## Features diff --git a/frontend/index.html b/frontend/index.html index 1ff0aaa..c63e583 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Awsl One API - 管理控制台 + One API on Workers - 管理控制台
diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index 0c026f7..e555b12 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -77,7 +77,7 @@ export function AppLayout({ children }: AppLayoutProps) {
- Awsl One API + One API on Workers
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 0bffee7..ce55b8c 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -1,5 +1,5 @@ -import { Link, useLocation, useNavigate } from 'react-router-dom' -import { cn } from '@/lib/utils' +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { cn } from "@/lib/utils"; import { Link as LinkIcon, Key, @@ -13,30 +13,30 @@ import { PanelLeftClose, PanelLeftOpen, Zap, -} from 'lucide-react' -import { useAuthStore } from '@/store/auth' -import { useState } from 'react' +} from "lucide-react"; +import { useAuthStore } from "@/store/auth"; +import { useState } from "react"; interface NavItem { - title: string - href: string - icon: React.ComponentType<{ className?: string }> + title: string; + href: string; + icon: React.ComponentType<{ className?: string }>; } const navItems: NavItem[] = [ - { title: '渠道管理', href: '/channels', icon: LinkIcon }, - { title: '令牌管理', href: '/tokens', icon: Key }, - { title: '定价管理', href: '/pricing', icon: DollarSign }, - { title: 'API 测试', href: '/api-test', icon: TestTube2 }, -] + { title: "渠道管理", href: "/channels", icon: LinkIcon }, + { title: "令牌管理", href: "/tokens", icon: Key }, + { title: "定价管理", href: "/pricing", icon: DollarSign }, + { title: "API 测试", href: "/api-test", icon: TestTube2 }, +]; interface SidebarProps { - className?: string - onNavigate?: () => void - onClose?: () => void - collapsed?: boolean - onToggleCollapse?: () => void - showCollapseToggle?: boolean + className?: string; + onNavigate?: () => void; + onClose?: () => void; + collapsed?: boolean; + onToggleCollapse?: () => void; + showCollapseToggle?: boolean; } export function Sidebar({ @@ -47,89 +47,84 @@ export function Sidebar({ onToggleCollapse, showCollapseToggle = false, }: SidebarProps) { - const location = useLocation() - const navigate = useNavigate() - const { logout } = useAuthStore() - const [theme, setTheme] = useState<'light' | 'dark'>(() => { - if (typeof window !== 'undefined') { - const savedTheme = localStorage.getItem('theme') - const isDark = savedTheme === 'dark' || (!savedTheme && document.documentElement.classList.contains('dark')) + const location = useLocation(); + const navigate = useNavigate(); + const { logout } = useAuthStore(); + const [theme, setTheme] = useState<"light" | "dark">(() => { + if (typeof window !== "undefined") { + const savedTheme = localStorage.getItem("theme"); + const isDark = savedTheme === "dark" || (!savedTheme && document.documentElement.classList.contains("dark")); if (isDark) { - document.documentElement.classList.add('dark') + document.documentElement.classList.add("dark"); } - return isDark ? 'dark' : 'light' + return isDark ? "dark" : "light"; } - return 'light' - }) + return "light"; + }); const toggleTheme = () => { - const newTheme = theme === 'light' ? 'dark' : 'light' - setTheme(newTheme) - localStorage.setItem('theme', newTheme) - if (newTheme === 'dark') { - document.documentElement.classList.add('dark') + const newTheme = theme === "light" ? "dark" : "light"; + setTheme(newTheme); + localStorage.setItem("theme", newTheme); + if (newTheme === "dark") { + document.documentElement.classList.add("dark"); } else { - document.documentElement.classList.remove('dark') + document.documentElement.classList.remove("dark"); } - } + }; const handleLogout = () => { - logout() - navigate('/', { replace: true }) - onNavigate?.() - } + logout(); + navigate("/", { replace: true }); + onNavigate?.(); + }; const NavLink = ({ item }: { item: NavItem }) => { - const isActive = location.pathname === item.href + const isActive = location.pathname === item.href; return ( onNavigate?.()} title={collapsed ? item.title : undefined} className={cn( - 'group relative flex items-center gap-3 px-3 py-2 rounded-lg text-[13px] font-medium transition-all duration-150', - collapsed && 'justify-center px-0 w-10 h-10 mx-auto', + "group relative flex items-center gap-3 px-3 py-2 rounded-lg text-[13px] font-medium transition-all duration-150", + collapsed && "justify-center px-0 w-10 h-10 mx-auto", isActive - ? 'bg-primary text-primary-foreground shadow-sm shadow-primary/20' - : 'text-muted-foreground hover:text-foreground hover:bg-muted/80' + ? "bg-primary text-primary-foreground shadow-sm shadow-primary/20" + : "text-muted-foreground hover:text-foreground hover:bg-muted/80", )} > - + {!collapsed && {item.title}} - ) - } + ); + }; return ( - ) + ); } diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 2a67bf9..707c214 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -62,7 +62,7 @@ export function Dashboard() {

- Awsl One API + One API on Workers

diff --git a/package.json b/package.json index ed46e34..21f8d32 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "awsl-ai-proxy", + "name": "one-api-workers", "workspaces": [ "frontend" ], diff --git a/src/index.ts b/src/index.ts index f1c26c2..ec4e039 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,7 +81,7 @@ const app = new Hono() const openapi = fromHono(app, { schema: { info: { - title: 'Awsl One API', + title: 'One API on Workers', version: '1.0.0', } }, diff --git a/tests/run-e2e.sh b/tests/run-e2e.sh index 4193404..fec784b 100755 --- a/tests/run-e2e.sh +++ b/tests/run-e2e.sh @@ -174,10 +174,10 @@ assert_http "azure responses stream → 200 + SSE" "200" "event:" "$R" echo "" echo "--- Quota check ---" -npx wrangler d1 execute awsl-one-api --local --command "UPDATE api_token SET usage = 99999999 WHERE key = 'sk-mock-all'" > /dev/null 2>&1 +npx wrangler d1 execute one-api-workers --local --command "UPDATE api_token SET usage = 99999999 WHERE key = 'sk-mock-all'" > /dev/null 2>&1 R=$(do_post -H "Authorization: Bearer sk-mock-all" "$API/v1/chat/completions" -H "Content-Type: application/json" -d '{"model":"gpt-4o","messages":[]}') assert_http "quota exceeded → 402" "402" "Quota exceeded" "$R" -npx wrangler d1 execute awsl-one-api --local --command "UPDATE api_token SET usage = 0 WHERE key = 'sk-mock-all'" > /dev/null 2>&1 +npx wrangler d1 execute one-api-workers --local --command "UPDATE api_token SET usage = 0 WHERE key = 'sk-mock-all'" > /dev/null 2>&1 # ---- Error recovery ---- echo "" @@ -197,10 +197,10 @@ echo "=========================================" echo "" echo "Cleaning up test data..." for key in test-openai test-azure test-claude test-c2o test-responses test-azure-resp; do - npx wrangler d1 execute awsl-one-api --local --command "DELETE FROM channel_config WHERE key = '$key'" > /dev/null 2>&1 + npx wrangler d1 execute one-api-workers --local --command "DELETE FROM channel_config WHERE key = '$key'" > /dev/null 2>&1 done for key in sk-mock-all sk-mock-openai sk-mock-quota; do - npx wrangler d1 execute awsl-one-api --local --command "DELETE FROM api_token WHERE key = '$key'" > /dev/null 2>&1 + npx wrangler d1 execute one-api-workers --local --command "DELETE FROM api_token WHERE key = '$key'" > /dev/null 2>&1 done echo "Cleanup done." diff --git a/tests/setup-test-data.sh b/tests/setup-test-data.sh index 3914b25..e99cf31 100755 --- a/tests/setup-test-data.sh +++ b/tests/setup-test-data.sh @@ -7,30 +7,30 @@ MOCK="http://localhost:9999" echo "Setting up test channels and tokens..." # Channel: openai type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-openai', '{\"name\":\"test-openai\",\"type\":\"openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"gpt-4o\":\"gpt-4o\",\"gpt-4\":\"gpt-4\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-openai', '{\"name\":\"test-openai\",\"type\":\"openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"gpt-4o\":\"gpt-4o\",\"gpt-4\":\"gpt-4\"}}')" # Channel: azure-openai type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-azure', '{\"name\":\"test-azure\",\"type\":\"azure-openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"api_version\":\"2024-02-01\",\"deployment_mapper\":{\"gpt-4o-azure\":\"gpt-4o-deployment\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-azure', '{\"name\":\"test-azure\",\"type\":\"azure-openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"api_version\":\"2024-02-01\",\"deployment_mapper\":{\"gpt-4o-azure\":\"gpt-4o-deployment\"}}')" # Channel: claude type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-claude', '{\"name\":\"test-claude\",\"type\":\"claude\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"claude-3-opus\":\"claude-3-opus-20240229\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-claude', '{\"name\":\"test-claude\",\"type\":\"claude\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"claude-3-opus\":\"claude-3-opus-20240229\"}}')" # Channel: claude-to-openai type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-c2o', '{\"name\":\"test-c2o\",\"type\":\"claude-to-openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"claude-via-openai\":\"claude-3-opus\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-c2o', '{\"name\":\"test-c2o\",\"type\":\"claude-to-openai\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"claude-via-openai\":\"claude-3-opus\"}}')" # Channel: openai-responses type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-responses', '{\"name\":\"test-responses\",\"type\":\"openai-responses\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"gpt-4o-resp\":\"gpt-4o\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-responses', '{\"name\":\"test-responses\",\"type\":\"openai-responses\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"deployment_mapper\":{\"gpt-4o-resp\":\"gpt-4o\"}}')" # Channel: azure-openai-responses type -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-azure-resp', '{\"name\":\"test-azure-resp\",\"type\":\"azure-openai-responses\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"api_version\":\"2025-01-01\",\"deployment_mapper\":{\"gpt-4o-azure-resp\":\"gpt-4o-deployment\"}}')" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO channel_config (key, value) VALUES ('test-azure-resp', '{\"name\":\"test-azure-resp\",\"type\":\"azure-openai-responses\",\"endpoint\":\"$MOCK\",\"api_key\":\"mock-key\",\"api_version\":\"2025-01-01\",\"deployment_mapper\":{\"gpt-4o-azure-resp\":\"gpt-4o-deployment\"}}')" # Token: access to all test channels only -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-all', '{\"name\":\"mock-all\",\"channel_keys\":[\"test-openai\",\"test-azure\",\"test-claude\",\"test-c2o\",\"test-responses\",\"test-azure-resp\"],\"total_quota\":99999999}', 0)" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-all', '{\"name\":\"mock-all\",\"channel_keys\":[\"test-openai\",\"test-azure\",\"test-claude\",\"test-c2o\",\"test-responses\",\"test-azure-resp\"],\"total_quota\":99999999}', 0)" # Token: limited to openai only -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-openai', '{\"name\":\"mock-openai\",\"channel_keys\":[\"test-openai\"],\"total_quota\":99999999}', 0)" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-openai', '{\"name\":\"mock-openai\",\"channel_keys\":[\"test-openai\"],\"total_quota\":99999999}', 0)" # Token: quota almost full -npx wrangler d1 execute awsl-one-api --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-quota', '{\"name\":\"mock-quota\",\"channel_keys\":[\"test-openai\"],\"total_quota\":100}', 99)" +npx wrangler d1 execute one-api-workers --local --command "INSERT OR REPLACE INTO api_token (key, value, usage) VALUES ('sk-mock-quota', '{\"name\":\"mock-quota\",\"channel_keys\":[\"test-openai\"],\"total_quota\":100}', 99)" echo "Test data setup complete!" diff --git a/type.d.ts b/type.d.ts index 7d1d427..9f1944a 100644 --- a/type.d.ts +++ b/type.d.ts @@ -71,7 +71,7 @@ type Usage = { cached_tokens?: number, } -type AwslKeyPayload = { +type oawKeyPayload = { channel_key: string; multipler: number | undefined | null; }