Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 73 additions & 53 deletions app/UserAPIKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,54 @@
import { useEffect, useState, useRef } from "react";
import { toast } from "sonner";

const isValidKeyFormat = (key: string) => /^[a-zA-Z0-9]{64,}$/.test(key);

export function UserAPIKey() {
const [userAPIKey, setUserAPIKey] = useState<string>("");
const [isValidating, setIsValidating] = useState(false);
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const [userAPIKey, setUserAPIKey] = useState(() => {
if (typeof window !== "undefined") {
return localStorage.getItem("togetherApiKey") || "";
}
return "";
});

// Initialize from sessionStorage
useEffect(() => {
const storedKey = sessionStorage.getItem("togetherApiKey");
if (storedKey) {
setUserAPIKey(storedKey);
const [isValid, setIsValid] = useState(true);
const [isChecking, setIsChecking] = useState(false);

const validateKey = async (key: string) => {
if (!key) {
setIsValid(true);
return;
}
}, []);

const validateAndSaveApiKey = async (apiKey: string) => {
if (!apiKey) {
sessionStorage.removeItem("togetherApiKey");
return false;
if (!isValidKeyFormat(key)) {
setIsValid(false);
return;
}

setIsValidating(true);
setIsChecking(true);

try {
const response = await fetch("/api/validate-key", {
method: "POST",
const res = await fetch("https://api.together.xyz/v1/models", {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${key}`,
},
body: JSON.stringify({ apiKey }),
});

const result = await response.json();

if (result.success) {
sessionStorage.setItem("togetherApiKey", apiKey);
toast.success("API key validated and saved!");
return true;
} else {
toast.error(result.message || "Invalid API key");
return false;
}
} catch (error) {
console.error("Error validating API key:", error);
toast.error("Failed to validate API key. Please try again.");
return false;
setIsValid(res.ok);
} catch {
setIsValid(false);
} finally {
setIsValidating(false);
setIsChecking(false);
}
};

useEffect(() => {
if (userAPIKey) {
localStorage.setItem("togetherApiKey", userAPIKey);
validateKey(userAPIKey);
} else {
localStorage.removeItem("togetherApiKey");
setIsValid(true);
}
};

Expand All @@ -70,33 +73,50 @@ export function UserAPIKey() {
};

return (
<div className="flex gap-3">
<div className="text-left text-xs max-md:hidden">
<p className="text-gray-600">[Optional] Add your</p>
<a
href="https://api.together.xyz/settings/api-keys"
target="_blank"
className="text-gray-300 underline"
rel="noopener noreferrer"
>
Together API Key:
</a>
</div>
<div className="relative flex-1">
<div className="flex flex-col gap-2">
<div className="flex gap-3 items-center">
<div className="text-left text-xs max-md:hidden">
<p className="text-gray-600">[Optional] Add your</p>
<a
href="https://api.together.xyz/settings/api-keys"
target="_blank"
className="text-gray-300 underline"
>
Together API Key:
</a>
</div>
<input
type="password"
value={userAPIKey}
autoComplete="off"
onChange={handleApiKeyChange}
onChange={(e) => setUserAPIKey(e.target.value)}
placeholder="API key"
className="h-8 w-full rounded border-[0.5px] border-gray-700 bg-gray-900 px-2 text-sm focus-visible:outline focus-visible:outline-gray-200"
className={`h-8 rounded border-[0.5px] px-2 text-sm focus-visible:outline
${
isValid
? "border-gray-700 bg-gray-900 focus-visible:outline-gray-200"
: "border-red-500 bg-red-900 focus-visible:outline-red-300"
}`}
/>
{isValidating && (
<div className="absolute top-1/2 right-2 -translate-y-1/2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-blue-600" />
</div>
)}
</div>

{!isChecking && !isValid && (
<p className="text-xs text-red-400 pl-[104px]">
Invalid Together AI key. Please double-check or create one at{" "}
<a
href="https://api.together.xyz/settings/api-keys"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Together API settings
</a>.
</p>
)}

{isChecking && (
<p className="text-xs text-gray-400 pl-[104px]">Validating key...</p>
)}
</div>
);
}