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