feat: add bring-your-own-API-key support (#186)

- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation

Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
This commit is contained in:
Dayuan Jiang
2025-12-09 17:50:07 +09:00
committed by GitHub
parent 77cb10393b
commit 97ab82e027
12 changed files with 434 additions and 43 deletions

View File

@@ -170,6 +170,27 @@ export default function AboutCN() {
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Bring Your Own Key */}
<div className="text-center mb-5">
<h4 className="text-base font-bold text-gray-900 mb-2">
使 API Key
</h4>
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
使 API Key
Provider API Key
</p>
<p className="text-xs text-gray-500 max-w-md mx-auto">
Key
</p>
</div>
{/* Divider */}
<div className="flex items-center gap-3 mb-5">
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Sponsorship CTA */}
<div className="text-center">
<h4 className="text-base font-bold text-gray-900 mb-2">

View File

@@ -179,6 +179,24 @@ export default function AboutJA() {
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Bring Your Own Key */}
<div className="text-center mb-5">
<h4 className="text-base font-bold text-gray-900 mb-2">
APIキーを使用
</h4>
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
APIキーを使用することでAPIキーを設定してください
</p>
<p className="text-xs text-gray-500 max-w-md mx-auto">
</p>
</div>
{/* Divider */}
<div className="flex items-center gap-3 mb-5">
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Sponsorship CTA */}
<div className="text-center">
<h4 className="text-base font-bold text-gray-900 mb-2">

View File

@@ -185,6 +185,28 @@ export default function About() {
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Bring Your Own Key */}
<div className="text-center mb-5">
<h4 className="text-base font-bold text-gray-900 mb-2">
Bring Your Own API Key
</h4>
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
You can use your own API key to bypass these
limits. Click the Settings icon in the chat
panel to configure your provider and API
key.
</p>
<p className="text-xs text-gray-500 max-w-md mx-auto">
Your key is stored locally in your browser
and is never stored on the server.
</p>
</div>
{/* Divider */}
<div className="flex items-center gap-3 mb-5">
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
</div>
{/* Sponsorship CTA */}
<div className="text-center">
<h4 className="text-base font-bold text-gray-900 mb-2">

View File

@@ -199,8 +199,17 @@ async function handleChatRequest(req: Request): Promise<Response> {
}
// === CACHE CHECK END ===
// Get AI model from environment configuration
const { model, providerOptions, headers, modelId } = getAIModel()
// Read client AI provider overrides from headers
const clientOverrides = {
provider: req.headers.get("x-ai-provider"),
baseUrl: req.headers.get("x-ai-base-url"),
apiKey: req.headers.get("x-ai-api-key"),
modelId: req.headers.get("x-ai-model"),
}
// Get AI model with optional client overrides
const { model, providerOptions, headers, modelId } =
getAIModel(clientOverrides)
// Check if model supports prompt caching
const shouldCache = supportsPromptCaching(modelId)

10
app/api/config/route.ts Normal file
View File

@@ -0,0 +1,10 @@
import { NextResponse } from "next/server"
export async function GET() {
return NextResponse.json({
accessCodeRequired: !!process.env.ACCESS_CODE_LIST,
dailyRequestLimit: Number(process.env.DAILY_REQUEST_LIMIT) || 0,
dailyTokenLimit: Number(process.env.DAILY_TOKEN_LIMIT) || 0,
tpmLimit: Number(process.env.TPM_LIMIT) || 0,
})
}