Skip to content

Commit 694fdec

Browse files
authored
feat: add more models (#80)
* feat: init open-provider * feat: extract settings types * feat: improve typing * feat: add anthropic * feat: update models dropdown free/pro * feat: add anthropic models * feat: init usage file * feat: track usage for pro model * feat: add checkLimitsAndNotify for pro models * feat: update model-selector * feat: add Advanced model usage * feat: move gpt-4.1 free model * feat: update HeaderAgent
1 parent f6c023f commit 694fdec

File tree

23 files changed

+917
-443
lines changed

23 files changed

+917
-443
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ CSRF_SECRET=your_csrf_secret
1717

1818
# Exa
1919
EXA_API_KEY=your_exa_api_key
20+
21+
# Gemini
22+
GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_api_key
23+
24+
# Anthropic
25+
ANTHROPIC_API_KEY=your_anthropic_api_key

app/api/agents/core/tools/usage.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
import {
2-
checkSpecialAgentUsage,
3-
checkUsage,
4-
incrementSpecialAgentUsage,
5-
incrementUsage,
6-
SpecialAgentLimitError,
7-
UsageLimitError,
8-
} from "@/lib/api"
1+
import { checkSpecialAgentUsage, incrementSpecialAgentUsage } from "@/lib/api"
92
import { sanitizeUserInput } from "@/lib/sanitize"
103
import { validateUserIdentity } from "@/lib/server/api"
4+
import { checkUsage, incrementUsage } from "@/lib/usage"
115
import { SupabaseClient } from "@supabase/supabase-js"
126

137
/**

app/api/chat/route.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
// /chat/api/chat.ts
2-
import { checkUsage, incrementUsage } from "@/lib/api"
3-
import { MODELS, SYSTEM_PROMPT_DEFAULT } from "@/lib/config"
1+
import { MODELS_OPTIONS, SYSTEM_PROMPT_DEFAULT } from "@/lib/config"
42
import { sanitizeUserInput } from "@/lib/sanitize"
53
import { validateUserIdentity } from "@/lib/server/api"
4+
import { checkUsageByModel, incrementUsageByModel } from "@/lib/usage"
65
import { Attachment } from "@ai-sdk/ui-utils"
76
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
8-
import { Message as MessageAISDK, streamText } from "ai"
7+
import { LanguageModelV1, Message as MessageAISDK, streamText } from "ai"
98

109
// Maximum allowed duration for streaming (in seconds)
1110
export const maxDuration = 30
@@ -41,7 +40,7 @@ export async function POST(req: Request) {
4140

4241
const supabase = await validateUserIdentity(userId, isAuthenticated)
4342

44-
await checkUsage(supabase, userId)
43+
await checkUsageByModel(supabase, userId, model, isAuthenticated)
4544

4645
const userMessage = messages[messages.length - 1]
4746
if (userMessage && userMessage.role === "user") {
@@ -57,7 +56,7 @@ export async function POST(req: Request) {
5756
console.error("Error saving user message:", msgError)
5857
} else {
5958
console.log("User message saved successfully.")
60-
await incrementUsage(supabase, userId)
59+
await incrementUsageByModel(supabase, userId, model, isAuthenticated)
6160
}
6261
}
6362

@@ -77,7 +76,7 @@ export async function POST(req: Request) {
7776
}
7877
}
7978

80-
const modelConfig = MODELS.find((m) => m.id === model)
79+
const modelConfig = MODELS_OPTIONS.find((m) => m.id === model)
8180

8281
if (!modelConfig) {
8382
throw new Error(`Model ${model} not found`)
@@ -87,13 +86,13 @@ export async function POST(req: Request) {
8786
const openRouter = createOpenRouter({
8887
apiKey: process.env.OPENROUTER_API_KEY,
8988
})
90-
modelInstance = openRouter.chat(modelConfig.api_sdk) // this is a special case for openrouter. Normal openrouter models are not supported.
89+
modelInstance = openRouter.chat(modelConfig.api_sdk as string) // this is a special case for openrouter. Normal openrouter models are not supported.
9190
} else {
9291
modelInstance = modelConfig.api_sdk
9392
}
9493

9594
const result = streamText({
96-
model: modelInstance,
95+
model: modelInstance as LanguageModelV1,
9796
system: effectiveSystemPrompt,
9897
messages,
9998
onError: (err) => {

app/api/create-chat-agent/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { checkUsage } from "@/lib/api"
21
import { validateUserIdentity } from "@/lib/server/api"
2+
import { checkUsageByModel } from "@/lib/usage"
33

44
export async function POST(request: Request) {
55
try {
@@ -15,7 +15,7 @@ export async function POST(request: Request) {
1515

1616
const supabase = await validateUserIdentity(userId, isAuthenticated)
1717

18-
await checkUsage(supabase, userId)
18+
await checkUsageByModel(supabase, userId, model, isAuthenticated)
1919

2020
const { data: agent, error: agentError } = await supabase
2121
.from("agents")

app/api/create-chat/route.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { checkUsage } from "@/lib/api"
21
import { SYSTEM_PROMPT_DEFAULT } from "@/lib/config"
32
import { validateUserIdentity } from "@/lib/server/api"
3+
import { checkUsageByModel } from "@/lib/usage"
44

55
export async function POST(request: Request) {
66
try {
@@ -14,8 +14,7 @@ export async function POST(request: Request) {
1414

1515
const supabase = await validateUserIdentity(userId, isAuthenticated)
1616

17-
// Only check usage but don't increment
18-
await checkUsage(supabase, userId)
17+
await checkUsageByModel(supabase, userId, model, isAuthenticated)
1918

2019
// Insert a new chat record in the chats table
2120
const { data: chatData, error: chatError } = await supabase

app/api/rate-limits/route.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,54 @@
11
import {
22
AUTH_DAILY_MESSAGE_LIMIT,
3+
DAILY_LIMIT_PRO_MODELS,
34
NON_AUTH_DAILY_MESSAGE_LIMIT,
45
} from "@/lib/config"
56
import { validateUserIdentity } from "../../../lib/server/api"
67

7-
88
export async function GET(req: Request) {
9-
10-
const { searchParams } = new URL(req.url)
11-
const userId = searchParams.get("userId")
12-
const isAuthenticated = searchParams.get("isAuthenticated") === "true"
13-
14-
if (!userId) {
15-
return new Response(JSON.stringify({error: "Missing userId"}), {
16-
status: 400,
17-
})
18-
}
19-
20-
const supabase = await validateUserIdentity(userId, isAuthenticated)
9+
const { searchParams } = new URL(req.url)
10+
const userId = searchParams.get("userId")
11+
const isAuthenticated = searchParams.get("isAuthenticated") === "true"
2112

22-
const { data, error } = await supabase
23-
.from("users")
24-
.select("daily_message_count")
25-
.eq("id", userId)
26-
.maybeSingle()
13+
if (!userId) {
14+
return new Response(JSON.stringify({ error: "Missing userId" }), {
15+
status: 400,
16+
})
17+
}
2718

28-
if (error || !data){
29-
return new Response(JSON.stringify({ error: error?.message }), {
30-
status: 500,
31-
})
32-
}
19+
const supabase = await validateUserIdentity(userId, isAuthenticated)
3320

34-
const dailyLimit = isAuthenticated
35-
? AUTH_DAILY_MESSAGE_LIMIT
36-
: NON_AUTH_DAILY_MESSAGE_LIMIT
37-
const dailyCount = data.daily_message_count || 0
38-
const remaining = dailyLimit - dailyCount
21+
const { data, error } = await supabase
22+
.from("users")
23+
.select("daily_message_count, daily_pro_message_count")
24+
.eq("id", userId)
25+
.maybeSingle()
3926

40-
return new Response(JSON.stringify({ dailyCount, dailyLimit, remaining }), {
41-
status: 200,
27+
if (error || !data) {
28+
return new Response(JSON.stringify({ error: error?.message }), {
29+
status: 500,
4230
})
43-
}
44-
45-
31+
}
32+
33+
const dailyLimit = isAuthenticated
34+
? AUTH_DAILY_MESSAGE_LIMIT
35+
: NON_AUTH_DAILY_MESSAGE_LIMIT
36+
const proDailyLimit = DAILY_LIMIT_PRO_MODELS
37+
const dailyCount = data.daily_message_count || 0
38+
const dailyProCount = data.daily_pro_message_count || 0
39+
const remaining = dailyLimit - dailyCount
40+
const remainingPro = proDailyLimit - dailyProCount
41+
42+
return new Response(
43+
JSON.stringify({
44+
dailyCount,
45+
dailyLimit,
46+
remaining,
47+
dailyProCount,
48+
remainingPro,
49+
}),
50+
{
51+
status: 200,
52+
}
53+
)
54+
}

app/components/chat-input/select-model.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
TooltipContent,
77
TooltipTrigger,
88
} from "@/components/ui/tooltip"
9+
import { MODELS_OPTIONS, PROVIDERS } from "@/lib/config"
910
import { CaretDown } from "@phosphor-icons/react"
10-
import { MODELS_OPTIONS, PROVIDERS_OPTIONS } from "../../../lib/config"
1111
import { PopoverContentAuth } from "./popover-content-auth"
1212

1313
export type SelectModelProps = {
@@ -21,9 +21,11 @@ export function SelectModel({
2121
onSelectModel,
2222
isUserAuthenticated,
2323
}: SelectModelProps) {
24-
const model = MODELS_OPTIONS.find((model) => model.id === selectedModel)
25-
const provider = PROVIDERS_OPTIONS.find(
26-
(provider) => provider.id === model?.provider
24+
const currentModel = MODELS_OPTIONS.find(
25+
(model) => model.id === selectedModel
26+
)
27+
const currentProvider = PROVIDERS.find(
28+
(provider) => provider.id === currentModel?.provider
2729
)
2830

2931
if (!isUserAuthenticated) {
@@ -38,8 +40,10 @@ export function SelectModel({
3840
className="border-border dark:bg-secondary text-accent-foreground h-9 w-auto rounded-full border bg-transparent"
3941
type="button"
4042
>
41-
{provider?.icon && <provider.icon className="size-5" />}
42-
{model?.name}
43+
{currentProvider?.icon && (
44+
<currentProvider.icon className="size-5" />
45+
)}
46+
{currentModel?.name}
4347
<CaretDown className="size-4" />
4448
</Button>
4549
</PopoverTrigger>

app/components/chat/chat.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ export function Chat() {
6767
)
6868
const [hydrated, setHydrated] = useState(false)
6969
const searchParams = useSearchParams()
70-
const router = useRouter()
71-
const hasSentInitialPromptRef = useRef(false)
7270
const hasSentFirstMessageRef = useRef(false)
7371
const { callAgent, isTooling, statusCall, agent } = useAgent()
7472

app/components/chat/use-chat-utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,16 @@ export function useChatUtils({
4545
if (rateData.remaining === REMAINING_QUERY_ALERT_THRESHOLD) {
4646
toast({
4747
title: `Only ${rateData.remaining} query${
48-
rateData.remaining === 1 ? "" : "ies"
48+
rateData.remaining === 1 ? "y" : "ies"
49+
} remaining today.`,
50+
status: "info",
51+
})
52+
}
53+
54+
if (rateData.remainingPro === REMAINING_QUERY_ALERT_THRESHOLD) {
55+
toast({
56+
title: `Only ${rateData.remainingPro} pro quer${
57+
rateData.remainingPro === 1 ? "y" : "ies"
4958
} remaining today.`,
5059
status: "info",
5160
})

app/components/layout/header-agent.tsx

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,64 @@ import {
66
TooltipProvider,
77
TooltipTrigger,
88
} from "@/components/ui/tooltip"
9-
import { cn } from "@/lib/utils"
109
import { Info } from "@phosphor-icons/react"
10+
import { AnimatePresence, motion } from "motion/react"
11+
import { AgentHeader } from "./header"
1112

1213
type HeaderAgentProps = {
13-
avatarUrl: string
14-
name: string
15-
info: string
16-
className?: string
14+
agent?: AgentHeader | null
1715
}
1816

19-
export function HeaderAgent({
20-
avatarUrl,
21-
name,
22-
info,
23-
className,
24-
}: HeaderAgentProps) {
17+
export function HeaderAgent({ agent }: HeaderAgentProps) {
2518
const isMobile = useBreakpoint(768)
26-
const initials = name
19+
const initials = agent?.name
2720
.split(" ")
2821
.map((n) => n[0])
2922
.join("")
3023

3124
return (
32-
<div
33-
className={cn(
34-
"bg-background/40 flex items-center justify-center gap-2 rounded-t-none rounded-b-md px-0 py-0 backdrop-blur-2xl md:px-3 md:py-3",
35-
className
36-
)}
37-
>
38-
<Avatar className="size-10">
39-
<AvatarImage src={avatarUrl} alt={name} className="object-cover" />
40-
<AvatarFallback>{initials}</AvatarFallback>
41-
</Avatar>
25+
<AnimatePresence mode="wait">
26+
{agent && (
27+
<motion.div
28+
key={agent.slug}
29+
initial={{ opacity: 0, scale: 0.95, filter: "blur(2px)" }}
30+
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
31+
exit={{ opacity: 0, scale: 0.95, filter: "blur(2px)" }}
32+
transition={{
33+
duration: 0.15,
34+
ease: "easeOut",
35+
}}
36+
className="bg-background/40 flex items-center justify-center gap-2 rounded-t-none rounded-b-md px-0 py-0 backdrop-blur-2xl md:px-3 md:py-3"
37+
>
38+
<Avatar className="size-10">
39+
<AvatarImage
40+
src={agent?.avatar_url || ""}
41+
alt={agent.name}
42+
className="object-cover"
43+
/>
44+
<AvatarFallback>{initials}</AvatarFallback>
45+
</Avatar>
4246

43-
<div className="flex items-center">
44-
<h2 className="text-sm font-medium">{name}</h2>
45-
{!isMobile && info && (
46-
<TooltipProvider>
47-
<Tooltip>
48-
<TooltipTrigger asChild>
49-
<button className="text-muted-foreground hover:text-foreground ml-1.5 transition-colors">
50-
<Info className="size-4" />
51-
<span className="sr-only">User information</span>
52-
</button>
53-
</TooltipTrigger>
54-
<TooltipContent>
55-
<p className="max-w-xs text-sm">{info}</p>
56-
</TooltipContent>
57-
</Tooltip>
58-
</TooltipProvider>
59-
)}
60-
</div>
61-
</div>
47+
<div className="flex items-center">
48+
<h2 className="text-sm font-medium">{agent.name}</h2>
49+
{!isMobile && agent.description && (
50+
<TooltipProvider>
51+
<Tooltip>
52+
<TooltipTrigger asChild>
53+
<button className="text-muted-foreground hover:text-foreground ml-1.5 transition-colors">
54+
<Info className="size-4" />
55+
<span className="sr-only">User information</span>
56+
</button>
57+
</TooltipTrigger>
58+
<TooltipContent>
59+
<p className="max-w-xs text-sm">{agent.description}</p>
60+
</TooltipContent>
61+
</Tooltip>
62+
</TooltipProvider>
63+
)}
64+
</div>
65+
</motion.div>
66+
)}
67+
</AnimatePresence>
6268
)
6369
}

0 commit comments

Comments
 (0)