mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 14:22:28 +08:00
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
This commit is contained in:
@@ -185,6 +185,7 @@ export default function ChatPanel({
|
|||||||
dailyRequestLimit,
|
dailyRequestLimit,
|
||||||
dailyTokenLimit,
|
dailyTokenLimit,
|
||||||
tpmLimit,
|
tpmLimit,
|
||||||
|
onConfigModel: () => setShowModelConfigDialog(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Generate a unique session ID for Langfuse tracing (restore from localStorage if available)
|
// Generate a unique session ID for Langfuse tracing (restore from localStorage if available)
|
||||||
@@ -1036,6 +1037,9 @@ export default function ChatPanel({
|
|||||||
<DevXmlSimulator
|
<DevXmlSimulator
|
||||||
setMessages={setMessages}
|
setMessages={setMessages}
|
||||||
onDisplayChart={onDisplayChart}
|
onDisplayChart={onDisplayChart}
|
||||||
|
onShowQuotaToast={() =>
|
||||||
|
quotaManager.showQuotaLimitToast(50, 50)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -134,11 +134,13 @@ const DEV_XML_PRESETS: Record<string, string> = {
|
|||||||
interface DevXmlSimulatorProps {
|
interface DevXmlSimulatorProps {
|
||||||
setMessages: React.Dispatch<React.SetStateAction<any[]>>
|
setMessages: React.Dispatch<React.SetStateAction<any[]>>
|
||||||
onDisplayChart: (xml: string) => void
|
onDisplayChart: (xml: string) => void
|
||||||
|
onShowQuotaToast?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DevXmlSimulator({
|
export function DevXmlSimulator({
|
||||||
setMessages,
|
setMessages,
|
||||||
onDisplayChart,
|
onDisplayChart,
|
||||||
|
onShowQuotaToast,
|
||||||
}: DevXmlSimulatorProps) {
|
}: DevXmlSimulatorProps) {
|
||||||
const [devXml, setDevXml] = useState("")
|
const [devXml, setDevXml] = useState("")
|
||||||
const [isSimulating, setIsSimulating] = useState(false)
|
const [isSimulating, setIsSimulating] = useState(false)
|
||||||
@@ -342,6 +344,15 @@ export function DevXmlSimulator({
|
|||||||
Stop
|
Stop
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{onShowQuotaToast && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onShowQuotaToast}
|
||||||
|
className="px-3 py-1 text-xs bg-purple-500 text-white rounded hover:bg-purple-600"
|
||||||
|
>
|
||||||
|
Test Quota Toast
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Coffee, X } from "lucide-react"
|
import { Coffee, Settings, X } from "lucide-react"
|
||||||
import Link from "next/link"
|
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import { FaGithub } from "react-icons/fa"
|
import { FaGithub } from "react-icons/fa"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
@@ -12,6 +11,7 @@ interface QuotaLimitToastProps {
|
|||||||
used: number
|
used: number
|
||||||
limit: number
|
limit: number
|
||||||
onDismiss: () => void
|
onDismiss: () => void
|
||||||
|
onConfigModel?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QuotaLimitToast({
|
export function QuotaLimitToast({
|
||||||
@@ -19,6 +19,7 @@ export function QuotaLimitToast({
|
|||||||
used,
|
used,
|
||||||
limit,
|
limit,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
|
onConfigModel,
|
||||||
}: QuotaLimitToastProps) {
|
}: QuotaLimitToastProps) {
|
||||||
const dict = useDictionary()
|
const dict = useDictionary()
|
||||||
const isTokenLimit = type === "token"
|
const isTokenLimit = type === "token"
|
||||||
@@ -75,16 +76,36 @@ export function QuotaLimitToast({
|
|||||||
? dict.quota.messageToken
|
? dict.quota.messageToken
|
||||||
: dict.quota.messageApi}
|
: dict.quota.messageApi}
|
||||||
</p>
|
</p>
|
||||||
|
<p
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: formatMessage(dict.quota.doubaoSponsorship, {
|
||||||
|
link: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<p dangerouslySetInnerHTML={{ __html: dict.quota.tip }} />
|
<p dangerouslySetInnerHTML={{ __html: dict.quota.tip }} />
|
||||||
<p>{dict.quota.reset}</p>
|
<p>{dict.quota.reset}</p>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
{onConfigModel && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
onConfigModel()
|
||||||
|
onDismiss()
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||||
|
>
|
||||||
|
<Settings className="w-3.5 h-3.5" />
|
||||||
|
{dict.quota.configModel}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border text-foreground hover:bg-muted transition-colors"
|
||||||
>
|
>
|
||||||
<FaGithub className="w-3.5 h-3.5" />
|
<FaGithub className="w-3.5 h-3.5" />
|
||||||
{dict.quota.selfHost}
|
{dict.quota.selfHost}
|
||||||
|
|||||||
@@ -157,10 +157,12 @@
|
|||||||
"tpmLimit": "Rate Limit",
|
"tpmLimit": "Rate Limit",
|
||||||
"tpmMessage": "Too many requests. Please wait a moment.",
|
"tpmMessage": "Too many requests. Please wait a moment.",
|
||||||
"tpmMessageDetailed": "Rate limit reached ({limit} tokens/min). Please wait {seconds} seconds before sending another request.",
|
"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.",
|
"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": "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.",
|
"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": "<strong>Tip:</strong> You can use your own API key (click the Settings icon) or self-host the project to bypass these limits.",
|
"tip": "<strong>Tip:</strong> 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": "<a href=\"{link}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"underline hover:text-foreground\">Register here</a> 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",
|
"selfHost": "Self-host",
|
||||||
"sponsor": "Sponsor",
|
"sponsor": "Sponsor",
|
||||||
"learnMore": "Learn more →",
|
"learnMore": "Learn more →",
|
||||||
|
|||||||
@@ -157,10 +157,12 @@
|
|||||||
"tpmLimit": "レート制限",
|
"tpmLimit": "レート制限",
|
||||||
"tpmMessage": "リクエストが多すぎます。しばらくお待ちください。",
|
"tpmMessage": "リクエストが多すぎます。しばらくお待ちください。",
|
||||||
"tpmMessageDetailed": "レート制限に達しました({limit}トークン/分)。{seconds}秒待ってからもう一度リクエストしてください。",
|
"tpmMessageDetailed": "レート制限に達しました({limit}トークン/分)。{seconds}秒待ってからもう一度リクエストしてください。",
|
||||||
"messageApi": "おっと — このデモの1日の API 制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
"messageApi": "今日のデモ利用上限に達してしまったようです。楽しんでいただけて本当に嬉しいです。このデモはByteDance Doubaoのご厚意により提供されていますが、皆様に公平にご利用いただくため、少し制限を設けさせていただいております。",
|
||||||
"messageToken": "おっと — このデモの1日のトークン制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
"messageToken": "今日のトークン利用上限に達してしまったようです。楽しんでいただけて本当に嬉しいです。このデモはByteDance Doubaoのご厚意により提供されていますが、皆様に公平にご利用いただくため、少し制限を設けさせていただいております。",
|
||||||
"tip": "<strong>ヒント:</strong>独自の API キーを使用する(設定アイコンをクリック)か、プロジェクトをセルフホストしてこれらの制限を回避できます。",
|
"tip": "<strong>ヒント:</strong>独自の API キーを使用する(設定アイコンをクリック)か、プロジェクトをセルフホストしてこれらの制限を回避できます。",
|
||||||
"reset": "制限は明日リセットされます。ご理解ありがとうございます!",
|
"reset": "制限は明日リセットされます。ご理解ありがとうございます。",
|
||||||
|
"doubaoSponsorship": "<a href=\"{link}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"underline hover:text-foreground\">こちらから登録</a>すると、各モデル(Doubao、DeepSeek、Kimi含む)で50万トークンを無料で取得できます。モデル設定でAPIキーを設定してください。",
|
||||||
|
"configModel": "APIキーを使用",
|
||||||
"selfHost": "セルフホスト",
|
"selfHost": "セルフホスト",
|
||||||
"sponsor": "スポンサー",
|
"sponsor": "スポンサー",
|
||||||
"learnMore": "詳細 →",
|
"learnMore": "詳細 →",
|
||||||
|
|||||||
@@ -157,10 +157,12 @@
|
|||||||
"tpmLimit": "速率限制",
|
"tpmLimit": "速率限制",
|
||||||
"tpmMessage": "请求过多。请稍等片刻。",
|
"tpmMessage": "请求过多。请稍等片刻。",
|
||||||
"tpmMessageDetailed": "达到速率限制({limit} 令牌/分钟)。请等待 {seconds} 秒后再发送请求。",
|
"tpmMessageDetailed": "达到速率限制({limit} 令牌/分钟)。请等待 {seconds} 秒后再发送请求。",
|
||||||
"messageApi": "糟糕 — 您已达到此演示的每日 API 限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
"messageApi": "看来您今天的体验次数已达上限。非常高兴您玩得开心,虽然本项目由字节跳动豆包慷慨赞助,但为了确保大家都能公平使用,我们不得不对使用量做一点小小的限制。",
|
||||||
"messageToken": "糟糕 — 您已达到此演示的每日令牌限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
"messageToken": "看来您今天的 Token 用量已达上限。非常高兴您玩得开心,虽然本项目由字节跳动豆包慷慨赞助,但为了确保大家都能公平使用,我们不得不对使用量做一点小小的限制。",
|
||||||
"tip": "<strong>提示:</strong>您可以使用自己的 API 密钥(点击设置图标)或自托管项目来绕过这些限制。",
|
"tip": "<strong>提示:</strong>您可以使用自己的 API 密钥(点击设置图标)或自托管项目来绕过这些限制。",
|
||||||
"reset": "您的限制将在明天重置。感谢您的理解!",
|
"reset": "您的限制将在明天重置。感谢您的理解。",
|
||||||
|
"doubaoSponsorship": "<a href=\"{link}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"underline hover:text-foreground\">点击此处注册</a>可获得每个模型 50 万免费 Token(包括豆包、DeepSeek 和 Kimi),然后在模型设置中配置您的 API Key。",
|
||||||
|
"configModel": "使用您的密钥",
|
||||||
"selfHost": "自托管",
|
"selfHost": "自托管",
|
||||||
"sponsor": "赞助",
|
"sponsor": "赞助",
|
||||||
"learnMore": "了解更多 →",
|
"learnMore": "了解更多 →",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface QuotaConfig {
|
|||||||
dailyRequestLimit: number
|
dailyRequestLimit: number
|
||||||
dailyTokenLimit: number
|
dailyTokenLimit: number
|
||||||
tpmLimit: number
|
tpmLimit: number
|
||||||
|
onConfigModel?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +23,8 @@ export function useQuotaManager(config: QuotaConfig): {
|
|||||||
showTokenLimitToast: (used?: number, limit?: number) => void
|
showTokenLimitToast: (used?: number, limit?: number) => void
|
||||||
showTPMLimitToast: (limit?: number) => void
|
showTPMLimitToast: (limit?: number) => void
|
||||||
} {
|
} {
|
||||||
const { dailyRequestLimit, dailyTokenLimit, tpmLimit } = config
|
const { dailyRequestLimit, dailyTokenLimit, tpmLimit, onConfigModel } =
|
||||||
|
config
|
||||||
const dict = useDictionary()
|
const dict = useDictionary()
|
||||||
|
|
||||||
// Show quota limit toast (request-based)
|
// Show quota limit toast (request-based)
|
||||||
@@ -34,12 +36,13 @@ export function useQuotaManager(config: QuotaConfig): {
|
|||||||
used={used ?? dailyRequestLimit}
|
used={used ?? dailyRequestLimit}
|
||||||
limit={limit ?? dailyRequestLimit}
|
limit={limit ?? dailyRequestLimit}
|
||||||
onDismiss={() => toast.dismiss(t)}
|
onDismiss={() => toast.dismiss(t)}
|
||||||
|
onConfigModel={onConfigModel}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
{ duration: 15000 },
|
{ duration: 15000 },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[dailyRequestLimit],
|
[dailyRequestLimit, onConfigModel],
|
||||||
)
|
)
|
||||||
|
|
||||||
// Show token limit toast
|
// Show token limit toast
|
||||||
@@ -52,12 +55,13 @@ export function useQuotaManager(config: QuotaConfig): {
|
|||||||
used={used ?? dailyTokenLimit}
|
used={used ?? dailyTokenLimit}
|
||||||
limit={limit ?? dailyTokenLimit}
|
limit={limit ?? dailyTokenLimit}
|
||||||
onDismiss={() => toast.dismiss(t)}
|
onDismiss={() => toast.dismiss(t)}
|
||||||
|
onConfigModel={onConfigModel}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
{ duration: 15000 },
|
{ duration: 15000 },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[dailyTokenLimit],
|
[dailyTokenLimit, onConfigModel],
|
||||||
)
|
)
|
||||||
|
|
||||||
// Show TPM limit toast
|
// Show TPM limit toast
|
||||||
|
|||||||
Reference in New Issue
Block a user