Skip to content

Commit f74f552

Browse files
authored
chore: relocate custom functions (#12)
2 parents f2f1c99 + 24a35e5 commit f74f552

18 files changed

+315
-211
lines changed

examples/nextjs-example/package-lock.json

Lines changed: 26 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/nextjs-example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"next": "15.5.4",
2020
"react": "19.1.0",
2121
"react-dom": "19.1.0",
22-
"tailwind-merge": "^3.3.1"
22+
"tailwind-merge": "^3.3.1",
23+
"usehooks-ts": "^3.1.1"
2324
},
2425
"devDependencies": {
2526
"@eslint/eslintrc": "^3",

examples/nextjs-example/src/app/chat/_dialogs/not-connected.tsx renamed to examples/nextjs-example/src/app/(app)/chat/_dialogs/not-connected.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,31 @@ import {
88
DialogHeader,
99
DialogTitle,
1010
} from "@/components/ui/dialog";
11-
import { getOAuthKeyUrl } from "@/lib/config";
11+
import { OAUTH_CALLBACK_URL, OPENROUTER_CODE_VERIFIER_KEY } from "@/lib/config";
12+
import { useOpenRouter } from "@/lib/hooks/use-openrouter-client";
1213
import { ExternalLink } from "lucide-react";
1314

1415
interface NotConnectedDialogProps {
1516
open: boolean;
1617
}
1718

1819
export function NotConnectedDialog({ open }: NotConnectedDialogProps) {
20+
const { client } = useOpenRouter();
21+
22+
const handleGotoOAuth = async () => {
23+
const { codeChallenge, codeVerifier } =
24+
await client.oAuth.createSHA256CodeChallenge();
25+
26+
const url = await client.oAuth.createAuthorizationUrl({
27+
codeChallenge,
28+
callbackUrl: OAUTH_CALLBACK_URL,
29+
codeChallengeMethod: "S256",
30+
});
31+
32+
localStorage.setItem(OPENROUTER_CODE_VERIFIER_KEY, codeVerifier);
33+
window.location.href = url;
34+
};
35+
1936
return (
2037
<Dialog open={open} onOpenChange={() => {}}>
2138
<DialogContent
@@ -39,19 +56,12 @@ export function NotConnectedDialog({ open }: NotConnectedDialogProps) {
3956
Claude, and more. Authentication is required to make API calls.
4057
</p>
4158

42-
<Button asChild className="w-full">
43-
<a
44-
href={getOAuthKeyUrl()}
45-
target="_blank"
46-
rel="noopener noreferrer"
47-
>
48-
<ExternalLink className="mr-2 h-4 w-4" />
49-
Connect to OpenRouter
50-
</a>
59+
<Button onClick={handleGotoOAuth} className="w-full">
60+
<ExternalLink className="mr-2 h-4 w-4" />
61+
Connect to OpenRouter
5162
</Button>
5263
</div>
5364
</DialogContent>
5465
</Dialog>
5566
);
5667
}
57-

examples/nextjs-example/src/app/chat/page.tsx renamed to examples/nextjs-example/src/app/(app)/chat/page.tsx

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import type React from "react";
44

5-
import { useState, useRef, useEffect } from "react";
65
import { Button } from "@/components/ui/button";
76
import { Card } from "@/components/ui/card";
87
import { Input } from "@/components/ui/input";
@@ -13,12 +12,11 @@ import {
1312
SelectTrigger,
1413
SelectValue,
1514
} from "@/components/ui/select";
16-
import { MessageSquare, Send, Settings, User, Bot } from "lucide-react";
17-
import { OpenRouterCore } from "@openrouter/sdk/core";
18-
import { chatSend, SendAcceptEnum } from "@openrouter/sdk/funcs/chatSend";
19-
import { OPENROUTER_KEY_LOCALSTORAGE_KEY } from "@/lib/config";
15+
import { useApiKey } from "@/lib/hooks/use-api-key";
16+
import { useOpenRouter } from "@/lib/hooks/use-openrouter-client";
2017
import { Message as OpenRouterMessageRequest } from "@openrouter/sdk/models";
21-
import useLocalStorage from "@/lib/hooks/use-local-storage";
18+
import { Bot, MessageSquare, Send, Settings, User } from "lucide-react";
19+
import { useEffect, useRef, useState } from "react";
2220
import { NotConnectedDialog } from "./_dialogs/not-connected";
2321

2422
type Message = OpenRouterMessageRequest & {
@@ -27,9 +25,8 @@ type Message = OpenRouterMessageRequest & {
2725
};
2826

2927
export default function Page() {
30-
const { value: apiKey, isPending: isApiKeyPending } = useLocalStorage<
31-
string | null
32-
>(OPENROUTER_KEY_LOCALSTORAGE_KEY, null);
28+
const { client: openRouter } = useOpenRouter();
29+
const [apiKey] = useApiKey();
3330

3431
const [messages, setMessages] = useState<Message[]>([
3532
{
@@ -39,11 +36,12 @@ export default function Page() {
3936
"Hello! I'm your AI assistant powered by OpenRouter. I can help you with a wide variety of tasks. What would you like to know or discuss today?",
4037
},
4138
]);
42-
const [input, setInput] = useState("");
4339
const [selectedModel, setSelectedModel] = useState("gpt-4");
4440
const [isLoading, setIsLoading] = useState(false);
4541
const messagesEndRef = useRef<HTMLDivElement>(null);
4642

43+
const inputRef = useRef<HTMLInputElement>(null);
44+
4745
const scrollToBottom = () => {
4846
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
4947
};
@@ -53,6 +51,9 @@ export default function Page() {
5351
}, [messages]);
5452

5553
const handleSend = async () => {
54+
const input = inputRef.current?.value;
55+
if (!input) return;
56+
5657
setIsLoading(true);
5758

5859
if (!input.trim() || isLoading) return;
@@ -72,7 +73,7 @@ export default function Page() {
7273
];
7374

7475
setMessages(updatedMessages);
75-
setInput("");
76+
inputRef.current!.value = "";
7677

7778
// Add an empty assistant message to stream into
7879
const assistantMessage: Message = {
@@ -88,27 +89,16 @@ export default function Page() {
8889
throw new Error("API key is required but not present.");
8990
}
9091

91-
const openRouter = new OpenRouterCore({ apiKey });
92-
93-
const result = await chatSend(
94-
openRouter,
95-
{
96-
model: "openai/gpt-4o",
97-
maxTokens: 1000,
98-
messages: updatedMessages,
99-
stream: true,
100-
},
101-
{ acceptHeaderOverride: SendAcceptEnum.textEventStream },
102-
);
103-
104-
if (!result.ok) {
105-
alert("Error: " + result.error.message);
106-
return setIsLoading(false);
107-
}
92+
const result = await openRouter.chat.send({
93+
model: "openai/gpt-4o",
94+
maxTokens: 1000,
95+
messages: updatedMessages,
96+
stream: true,
97+
});
10898

10999
// Stream chunks into the latest message
110100
const chunks: string[] = [];
111-
for await (const chunk of result.value) {
101+
for await (const chunk of result) {
112102
chunks.push(chunk.data.choices[0].delta.content || "");
113103
setMessages((prev) => {
114104
const newMessages = [...prev];
@@ -134,7 +124,7 @@ export default function Page() {
134124

135125
return (
136126
<>
137-
<NotConnectedDialog open={!isApiKeyPending && apiKey === null} />
127+
<NotConnectedDialog open={!apiKey} />
138128
<div className="flex flex-col h-screen bg-background">
139129
{/* Header */}
140130
<div className="border-b border-border bg-card">
@@ -221,18 +211,13 @@ export default function Page() {
221211
<div className="border-border bg-card p-4 max-w-4xl mx-auto w-full">
222212
<div className="flex gap-2">
223213
<Input
224-
value={input}
225-
onChange={(e) => setInput(e.target.value)}
214+
ref={inputRef}
226215
onKeyPress={handleKeyPress}
227216
placeholder="Type your message..."
228217
className="flex-1"
229218
disabled={isLoading}
230219
/>
231-
<Button
232-
onClick={handleSend}
233-
disabled={!input.trim() || isLoading}
234-
size="icon"
235-
>
220+
<Button onClick={handleSend} disabled={isLoading} size="icon">
236221
<Send className="h-4 w-4" />
237222
</Button>
238223
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use client";
2+
3+
import { OpenRouterClientProvider } from "@/lib/hooks/use-openrouter-client";
4+
5+
export default function Layout({
6+
children,
7+
}: Readonly<{
8+
children: React.ReactNode;
9+
}>) {
10+
return <OpenRouterClientProvider>{children}</OpenRouterClientProvider>;
11+
}

0 commit comments

Comments
 (0)