feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- Sidebar: dynamic brand-accent colors, brainstorm section restyled - AI chat general: popup panel with expand/collapse, hides when contextual AI open - AI chat contextual: tabs reordered (Actions first), X close button, height fix - Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.) - Global color cleanup: emerald/orange hardcoded → brand-accent dynamic - Brainstorm page: orange → brand-accent throughout - PageEntry animation component added to key pages - Floating AI button: bg-brand-accent instead of hardcoded black - i18n: all 15 locales updated with new AI/billing keys - Billing: freemium quota tracking, BYOK, stripe subscription scaffolding - Admin: integrated into new design - AGENTS.md + CLAUDE.md project rules added
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { streamText, UIMessage, stepCountIs } from 'ai'
|
||||
import { getChatProvider } from '@/lib/ai/factory'
|
||||
import { resolveAiRouteWithTiming, formatAiRouteDebug } from '@/lib/ai/router'
|
||||
import { runLaneWithBillingUser, willUseByokForLane } from '@/lib/ai/provider-for-user'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { semanticSearchService } from '@/lib/ai/services/semantic-search.service'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { loadTranslations, getTranslationValue, SupportedLanguage } from '@/lib/i18n'
|
||||
import { toolRegistry } from '@/lib/ai/tools'
|
||||
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
|
||||
import { trackFeatureUsage } from '@/lib/usage-tracker'
|
||||
import { readFile } from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
@@ -46,6 +49,20 @@ export async function POST(req: Request) {
|
||||
}
|
||||
const userId = session.user.id
|
||||
|
||||
// 1.5 Quota check (per-provider BYOK bypass — only when BYOK will be used for resolved provider)
|
||||
try {
|
||||
const sysConfigEarly = await getSystemConfig()
|
||||
const { usedByok: willUseByok } = await willUseByokForLane('chat', sysConfigEarly, userId)
|
||||
if (!willUseByok) {
|
||||
await checkEntitlementOrThrow(userId, 'chat')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof QuotaExceededError) {
|
||||
return Response.json(err.toJSON(), { status: 402 })
|
||||
}
|
||||
console.error('[chat] Quota check error (fail-open):', err)
|
||||
}
|
||||
|
||||
// 2. Parse request body
|
||||
const body = await req.json()
|
||||
const { messages: rawMessages, conversationId, notebookId, language, webSearch, noteContext, format, noteId } = body as {
|
||||
@@ -323,27 +340,43 @@ Focus ONLY on this note unless asked otherwise.`
|
||||
|
||||
// 6. Execute stream
|
||||
const sysConfig = await getSystemConfig()
|
||||
|
||||
const routeDebug =
|
||||
process.env.NODE_ENV !== 'production' || process.env.MEMENTO_AI_ROUTE_DEBUG === '1'
|
||||
if (routeDebug) {
|
||||
console.debug('[ai-route]', formatAiRouteDebug(resolveAiRouteWithTiming('chat', sysConfig)))
|
||||
}
|
||||
|
||||
const chatTools = noteContext
|
||||
? toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined })
|
||||
: toolRegistry.buildToolsForChat({ userId, config: sysConfig, webSearch, notebookId: notebookId || undefined })
|
||||
|
||||
const provider = getChatProvider(sysConfig)
|
||||
const result = await streamText({
|
||||
model: provider.getModel(),
|
||||
system: systemPrompt,
|
||||
messages: incomingMessages,
|
||||
tools: chatTools,
|
||||
stopWhen: stepCountIs(5),
|
||||
onFinish: async (final) => {
|
||||
const userContent = incomingMessages[incomingMessages.length - 1].content
|
||||
await prisma.chatMessage.create({
|
||||
data: { conversationId: conversation.id, role: 'user', content: userContent }
|
||||
})
|
||||
await prisma.chatMessage.create({
|
||||
data: { conversationId: conversation.id, role: 'assistant', content: final.text }
|
||||
})
|
||||
}
|
||||
})
|
||||
const { result, usedByok } = await runLaneWithBillingUser(
|
||||
'chat',
|
||||
sysConfig,
|
||||
userId,
|
||||
async (provider) =>
|
||||
streamText({
|
||||
model: provider.getModel(),
|
||||
system: systemPrompt,
|
||||
messages: incomingMessages,
|
||||
tools: chatTools,
|
||||
stopWhen: stepCountIs(5),
|
||||
onFinish: async (final) => {
|
||||
const userContent = incomingMessages[incomingMessages.length - 1].content
|
||||
await prisma.chatMessage.create({
|
||||
data: { conversationId: conversation.id, role: 'user', content: userContent },
|
||||
})
|
||||
await prisma.chatMessage.create({
|
||||
data: { conversationId: conversation.id, role: 'assistant', content: final.text },
|
||||
})
|
||||
if (!usedByok) {
|
||||
trackFeatureUsage(userId, 'chat', final.usage?.totalTokens ?? 0)
|
||||
incrementUsageAsync(userId, 'chat')
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
return result.toUIMessageStreamResponse()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user