From 27f26d8b26c7dfe69b102e55fb540900f9fae356 Mon Sep 17 00:00:00 2001
From: Dayuan Jiang <34411969+DayuanJiang@users.noreply.github.com>
Date: Mon, 29 Dec 2025 12:12:22 +0900
Subject: [PATCH] feat: improve quota toast with ByteDance Doubao sponsorship
info and model config button (#447)
- Add 'Use Your API Key' button to open model config dialog
- Add ByteDance Doubao sponsorship message with registration link
- Update quota limit messages to be warmer and friendlier
- Add dev panel button to test quota toast
- Update i18n translations for EN, ZH, JA
---
components/chat-panel.tsx | 4 ++++
components/dev-xml-simulator.tsx | 11 +++++++++++
components/quota-limit-toast.tsx | 27 ++++++++++++++++++++++++---
lib/i18n/dictionaries/en.json | 8 +++++---
lib/i18n/dictionaries/ja.json | 8 +++++---
lib/i18n/dictionaries/zh.json | 8 +++++---
lib/use-quota-manager.tsx | 10 +++++++---
7 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx
index bf13588..114310b 100644
--- a/components/chat-panel.tsx
+++ b/components/chat-panel.tsx
@@ -185,6 +185,7 @@ export default function ChatPanel({
dailyRequestLimit,
dailyTokenLimit,
tpmLimit,
+ onConfigModel: () => setShowModelConfigDialog(true),
})
// Generate a unique session ID for Langfuse tracing (restore from localStorage if available)
@@ -1036,6 +1037,9 @@ export default function ChatPanel({
+ quotaManager.showQuotaLimitToast(50, 50)
+ }
/>
)}
diff --git a/components/dev-xml-simulator.tsx b/components/dev-xml-simulator.tsx
index e08185a..14a8b49 100644
--- a/components/dev-xml-simulator.tsx
+++ b/components/dev-xml-simulator.tsx
@@ -134,11 +134,13 @@ const DEV_XML_PRESETS: Record = {
interface DevXmlSimulatorProps {
setMessages: React.Dispatch>
onDisplayChart: (xml: string) => void
+ onShowQuotaToast?: () => void
}
export function DevXmlSimulator({
setMessages,
onDisplayChart,
+ onShowQuotaToast,
}: DevXmlSimulatorProps) {
const [devXml, setDevXml] = useState("")
const [isSimulating, setIsSimulating] = useState(false)
@@ -342,6 +344,15 @@ export function DevXmlSimulator({
Stop
)}
+ {onShowQuotaToast && (
+
+ )}
diff --git a/components/quota-limit-toast.tsx b/components/quota-limit-toast.tsx
index 566a666..644f94a 100644
--- a/components/quota-limit-toast.tsx
+++ b/components/quota-limit-toast.tsx
@@ -1,7 +1,6 @@
"use client"
-import { Coffee, X } from "lucide-react"
-import Link from "next/link"
+import { Coffee, Settings, X } from "lucide-react"
import type React from "react"
import { FaGithub } from "react-icons/fa"
import { useDictionary } from "@/hooks/use-dictionary"
@@ -12,6 +11,7 @@ interface QuotaLimitToastProps {
used: number
limit: number
onDismiss: () => void
+ onConfigModel?: () => void
}
export function QuotaLimitToast({
@@ -19,6 +19,7 @@ export function QuotaLimitToast({
used,
limit,
onDismiss,
+ onConfigModel,
}: QuotaLimitToastProps) {
const dict = useDictionary()
const isTokenLimit = type === "token"
@@ -75,16 +76,36 @@ export function QuotaLimitToast({
? dict.quota.messageToken
: dict.quota.messageApi}
+
{dict.quota.reset}
{" "}
{/* Action buttons */}
+ {onConfigModel && (
+
+ )}
{dict.quota.selfHost}
diff --git a/lib/i18n/dictionaries/en.json b/lib/i18n/dictionaries/en.json
index b19f1b6..6c118fe 100644
--- a/lib/i18n/dictionaries/en.json
+++ b/lib/i18n/dictionaries/en.json
@@ -157,10 +157,12 @@
"tpmLimit": "Rate Limit",
"tpmMessage": "Too many requests. Please wait a moment.",
"tpmMessageDetailed": "Rate limit reached ({limit} tokens/min). Please wait {seconds} seconds before sending another request.",
- "messageApi": "Oops — you've reached the daily API limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
- "messageToken": "Oops — you've reached the daily token limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
+ "messageApi": "Looks like you've reached today's demo limit. We're thrilled you're enjoying it, and while ByteDance Doubao generously sponsors this demo, we've had to set a few boundaries to keep things fair for everyone.",
+ "messageToken": "Looks like you've reached today's token limit. We're thrilled you're enjoying it, and while ByteDance Doubao generously sponsors this demo, we've had to set a few boundaries to keep things fair for everyone.",
"tip": "Tip: You can use your own API key (click the Settings icon) or self-host the project to bypass these limits.",
- "reset": "Your limit resets tomorrow. Thanks for understanding!",
+ "reset": "Your limit resets tomorrow. Thanks for understanding.",
+ "doubaoSponsorship": "Register here to get 500K free tokens per model (including Doubao, DeepSeek and Kimi), then configure your API key in model settings.",
+ "configModel": "Use Your API Key",
"selfHost": "Self-host",
"sponsor": "Sponsor",
"learnMore": "Learn more →",
diff --git a/lib/i18n/dictionaries/ja.json b/lib/i18n/dictionaries/ja.json
index b5d82af..e6d5058 100644
--- a/lib/i18n/dictionaries/ja.json
+++ b/lib/i18n/dictionaries/ja.json
@@ -157,10 +157,12 @@
"tpmLimit": "レート制限",
"tpmMessage": "リクエストが多すぎます。しばらくお待ちください。",
"tpmMessageDetailed": "レート制限に達しました({limit}トークン/分)。{seconds}秒待ってからもう一度リクエストしてください。",
- "messageApi": "おっと — このデモの1日の API 制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
- "messageToken": "おっと — このデモの1日のトークン制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
+ "messageApi": "今日のデモ利用上限に達してしまったようです。楽しんでいただけて本当に嬉しいです。このデモはByteDance Doubaoのご厚意により提供されていますが、皆様に公平にご利用いただくため、少し制限を設けさせていただいております。",
+ "messageToken": "今日のトークン利用上限に達してしまったようです。楽しんでいただけて本当に嬉しいです。このデモはByteDance Doubaoのご厚意により提供されていますが、皆様に公平にご利用いただくため、少し制限を設けさせていただいております。",
"tip": "
ヒント:独自の API キーを使用する(設定アイコンをクリック)か、プロジェクトをセルフホストしてこれらの制限を回避できます。",
- "reset": "制限は明日リセットされます。ご理解ありがとうございます!",
+ "reset": "制限は明日リセットされます。ご理解ありがとうございます。",
+ "doubaoSponsorship": "
こちらから登録すると、各モデル(Doubao、DeepSeek、Kimi含む)で50万トークンを無料で取得できます。モデル設定でAPIキーを設定してください。",
+ "configModel": "APIキーを使用",
"selfHost": "セルフホスト",
"sponsor": "スポンサー",
"learnMore": "詳細 →",
diff --git a/lib/i18n/dictionaries/zh.json b/lib/i18n/dictionaries/zh.json
index 51b6b64..5c923ff 100644
--- a/lib/i18n/dictionaries/zh.json
+++ b/lib/i18n/dictionaries/zh.json
@@ -157,10 +157,12 @@
"tpmLimit": "速率限制",
"tpmMessage": "请求过多。请稍等片刻。",
"tpmMessageDetailed": "达到速率限制({limit} 令牌/分钟)。请等待 {seconds} 秒后再发送请求。",
- "messageApi": "糟糕 — 您已达到此演示的每日 API 限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
- "messageToken": "糟糕 — 您已达到此演示的每日令牌限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
+ "messageApi": "看来您今天的体验次数已达上限。非常高兴您玩得开心,虽然本项目由字节跳动豆包慷慨赞助,但为了确保大家都能公平使用,我们不得不对使用量做一点小小的限制。",
+ "messageToken": "看来您今天的 Token 用量已达上限。非常高兴您玩得开心,虽然本项目由字节跳动豆包慷慨赞助,但为了确保大家都能公平使用,我们不得不对使用量做一点小小的限制。",
"tip": "
提示:您可以使用自己的 API 密钥(点击设置图标)或自托管项目来绕过这些限制。",
- "reset": "您的限制将在明天重置。感谢您的理解!",
+ "reset": "您的限制将在明天重置。感谢您的理解。",
+ "doubaoSponsorship": "
点击此处注册可获得每个模型 50 万免费 Token(包括豆包、DeepSeek 和 Kimi),然后在模型设置中配置您的 API Key。",
+ "configModel": "使用您的密钥",
"selfHost": "自托管",
"sponsor": "赞助",
"learnMore": "了解更多 →",
diff --git a/lib/use-quota-manager.tsx b/lib/use-quota-manager.tsx
index 33a7527..fd705c6 100644
--- a/lib/use-quota-manager.tsx
+++ b/lib/use-quota-manager.tsx
@@ -10,6 +10,7 @@ export interface QuotaConfig {
dailyRequestLimit: number
dailyTokenLimit: number
tpmLimit: number
+ onConfigModel?: () => void
}
/**
@@ -22,7 +23,8 @@ export function useQuotaManager(config: QuotaConfig): {
showTokenLimitToast: (used?: number, limit?: number) => void
showTPMLimitToast: (limit?: number) => void
} {
- const { dailyRequestLimit, dailyTokenLimit, tpmLimit } = config
+ const { dailyRequestLimit, dailyTokenLimit, tpmLimit, onConfigModel } =
+ config
const dict = useDictionary()
// Show quota limit toast (request-based)
@@ -34,12 +36,13 @@ export function useQuotaManager(config: QuotaConfig): {
used={used ?? dailyRequestLimit}
limit={limit ?? dailyRequestLimit}
onDismiss={() => toast.dismiss(t)}
+ onConfigModel={onConfigModel}
/>
),
{ duration: 15000 },
)
},
- [dailyRequestLimit],
+ [dailyRequestLimit, onConfigModel],
)
// Show token limit toast
@@ -52,12 +55,13 @@ export function useQuotaManager(config: QuotaConfig): {
used={used ?? dailyTokenLimit}
limit={limit ?? dailyTokenLimit}
onDismiss={() => toast.dismiss(t)}
+ onConfigModel={onConfigModel}
/>
),
{ duration: 15000 },
)
},
- [dailyTokenLimit],
+ [dailyTokenLimit, onConfigModel],
)
// Show TPM limit toast