mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-03 23:02:31 +08:00
Compare commits
1 Commits
d3fb2314ee
...
refactor/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dbda5ba8e |
@@ -504,7 +504,7 @@ export function ModelConfigDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Provider Details (Right Panel) */}
|
{/* Provider Details (Right Panel) */}
|
||||||
<div className="flex-1 min-w-0 flex flex-col overflow-auto [&::-webkit-scrollbar]:hidden ">
|
<div className="flex-1 min-w-0 flex flex-col overflow-hidden">
|
||||||
{selectedProvider ? (
|
{selectedProvider ? (
|
||||||
<>
|
<>
|
||||||
<ScrollArea className="flex-1" ref={scrollRef}>
|
<ScrollArea className="flex-1" ref={scrollRef}>
|
||||||
|
|||||||
@@ -14,14 +14,22 @@ export function register() {
|
|||||||
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
||||||
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
||||||
baseUrl: process.env.LANGFUSE_BASEURL,
|
baseUrl: process.env.LANGFUSE_BASEURL,
|
||||||
// Whitelist approach: only export AI-related spans
|
// Filter out Next.js HTTP request spans so AI SDK spans become root traces
|
||||||
shouldExportSpan: ({ otelSpan }) => {
|
shouldExportSpan: ({ otelSpan }) => {
|
||||||
const spanName = otelSpan.name
|
const spanName = otelSpan.name
|
||||||
// Only export AI SDK spans (ai.*) and our explicit "chat" wrapper
|
// Skip Next.js HTTP infrastructure spans
|
||||||
if (spanName === "chat" || spanName.startsWith("ai.")) {
|
if (
|
||||||
return true
|
spanName.startsWith("POST") ||
|
||||||
|
spanName.startsWith("GET") ||
|
||||||
|
spanName.startsWith("RSC") ||
|
||||||
|
spanName.includes("BaseServer") ||
|
||||||
|
spanName.includes("handleRequest") ||
|
||||||
|
spanName.includes("resolve page") ||
|
||||||
|
spanName.includes("start response")
|
||||||
|
) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return true
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,30 +9,6 @@ import {
|
|||||||
// OSS users who don't need quota tracking can simply not set this env var
|
// OSS users who don't need quota tracking can simply not set this env var
|
||||||
const TABLE = process.env.DYNAMODB_QUOTA_TABLE
|
const TABLE = process.env.DYNAMODB_QUOTA_TABLE
|
||||||
const DYNAMODB_REGION = process.env.DYNAMODB_REGION || "ap-northeast-1"
|
const DYNAMODB_REGION = process.env.DYNAMODB_REGION || "ap-northeast-1"
|
||||||
// Timezone for daily quota reset (e.g., "Asia/Tokyo" for JST midnight reset)
|
|
||||||
// Defaults to UTC if not set
|
|
||||||
let QUOTA_TIMEZONE = process.env.QUOTA_TIMEZONE || "UTC"
|
|
||||||
|
|
||||||
// Validate timezone at module load
|
|
||||||
try {
|
|
||||||
new Intl.DateTimeFormat("en-CA", { timeZone: QUOTA_TIMEZONE }).format(
|
|
||||||
new Date(),
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
console.warn(
|
|
||||||
`[quota] Invalid QUOTA_TIMEZONE "${QUOTA_TIMEZONE}", using UTC`,
|
|
||||||
)
|
|
||||||
QUOTA_TIMEZONE = "UTC"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get today's date string in the configured timezone (YYYY-MM-DD format)
|
|
||||||
*/
|
|
||||||
function getTodayInTimezone(): string {
|
|
||||||
return new Intl.DateTimeFormat("en-CA", {
|
|
||||||
timeZone: QUOTA_TIMEZONE,
|
|
||||||
}).format(new Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only create client if quota is enabled
|
// Only create client if quota is enabled
|
||||||
const client = TABLE ? new DynamoDBClient({ region: DYNAMODB_REGION }) : null
|
const client = TABLE ? new DynamoDBClient({ region: DYNAMODB_REGION }) : null
|
||||||
@@ -73,67 +49,32 @@ export async function checkAndIncrementRequest(
|
|||||||
return { allowed: true }
|
return { allowed: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
const today = getTodayInTimezone()
|
const today = new Date().toISOString().split("T")[0]
|
||||||
const currentMinute = Math.floor(Date.now() / 60000).toString()
|
const currentMinute = Math.floor(Date.now() / 60000).toString()
|
||||||
const ttl = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
|
const ttl = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, try to reset counts if it's a new day (atomic day reset)
|
// Atomic check-and-increment with ConditionExpression
|
||||||
// This will succeed only if lastResetDate < today or doesn't exist
|
// This prevents race conditions by failing if limits are exceeded
|
||||||
try {
|
|
||||||
await client.send(
|
|
||||||
new UpdateItemCommand({
|
|
||||||
TableName: TABLE,
|
|
||||||
Key: { PK: { S: `IP#${ip}` } },
|
|
||||||
// Reset all counts to 1/0 for the new day
|
|
||||||
UpdateExpression: `
|
|
||||||
SET lastResetDate = :today,
|
|
||||||
dailyReqCount = :one,
|
|
||||||
dailyTokenCount = :zero,
|
|
||||||
lastMinute = :minute,
|
|
||||||
tpmCount = :zero,
|
|
||||||
#ttl = :ttl
|
|
||||||
`,
|
|
||||||
// Only succeed if it's a new day (or new item)
|
|
||||||
ConditionExpression: `
|
|
||||||
attribute_not_exists(lastResetDate) OR lastResetDate < :today
|
|
||||||
`,
|
|
||||||
ExpressionAttributeNames: { "#ttl": "ttl" },
|
|
||||||
ExpressionAttributeValues: {
|
|
||||||
":today": { S: today },
|
|
||||||
":zero": { N: "0" },
|
|
||||||
":one": { N: "1" },
|
|
||||||
":minute": { S: currentMinute },
|
|
||||||
":ttl": { N: String(ttl) },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
// New day reset successful
|
|
||||||
return { allowed: true }
|
|
||||||
} catch (resetError: any) {
|
|
||||||
// If condition failed, it's the same day - continue to increment logic
|
|
||||||
if (!(resetError instanceof ConditionalCheckFailedException)) {
|
|
||||||
throw resetError // Re-throw unexpected errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same day - increment request count with limit checks
|
|
||||||
await client.send(
|
await client.send(
|
||||||
new UpdateItemCommand({
|
new UpdateItemCommand({
|
||||||
TableName: TABLE,
|
TableName: TABLE,
|
||||||
Key: { PK: { S: `IP#${ip}` } },
|
Key: { PK: { S: `IP#${ip}` } },
|
||||||
// Increment request count, handle minute boundary for TPM
|
// Reset counts if new day/minute, then increment request count
|
||||||
UpdateExpression: `
|
UpdateExpression: `
|
||||||
SET lastMinute = :minute,
|
SET lastResetDate = :today,
|
||||||
|
dailyReqCount = if_not_exists(dailyReqCount, :zero) + :one,
|
||||||
|
dailyTokenCount = if_not_exists(dailyTokenCount, :zero),
|
||||||
|
lastMinute = :minute,
|
||||||
tpmCount = if_not_exists(tpmCount, :zero),
|
tpmCount = if_not_exists(tpmCount, :zero),
|
||||||
#ttl = :ttl
|
#ttl = :ttl
|
||||||
ADD dailyReqCount :one
|
|
||||||
`,
|
`,
|
||||||
// Check all limits before allowing increment
|
// Atomic condition: only succeed if ALL limits pass
|
||||||
|
// Uses attribute_not_exists for new items, then checks limits for existing items
|
||||||
ConditionExpression: `
|
ConditionExpression: `
|
||||||
lastResetDate = :today AND
|
(attribute_not_exists(lastResetDate) OR lastResetDate < :today OR
|
||||||
(attribute_not_exists(dailyReqCount) OR dailyReqCount < :reqLimit) AND
|
((attribute_not_exists(dailyReqCount) OR dailyReqCount < :reqLimit) AND
|
||||||
(attribute_not_exists(dailyTokenCount) OR dailyTokenCount < :tokenLimit) AND
|
(attribute_not_exists(dailyTokenCount) OR dailyTokenCount < :tokenLimit))) AND
|
||||||
(attribute_not_exists(lastMinute) OR lastMinute <> :minute OR
|
(attribute_not_exists(lastMinute) OR lastMinute <> :minute OR
|
||||||
attribute_not_exists(tpmCount) OR tpmCount < :tpmLimit)
|
attribute_not_exists(tpmCount) OR tpmCount < :tpmLimit)
|
||||||
`,
|
`,
|
||||||
|
|||||||
Reference in New Issue
Block a user