From 63dcd6702eee52a84ede78fc2e5d1bbec40acf27 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 21 Feb 2025 09:00:23 -0800 Subject: [PATCH] Add login key system --- app/components/chat/Chat.client.tsx | 14 ++++-- .../settings/providers/APIKeysTab.tsx | 26 ++++++---- app/components/sidebar/Menu.client.tsx | 4 +- app/lib/.server/llm/chat-anthropic.ts | 2 + app/lib/replay/Problems.ts | 48 ++++++++++++++----- app/routes/api.chat.ts | 4 +- app/routes/problem.$id.tsx | 13 +++-- app/routes/problems.tsx | 7 ++- 8 files changed, 83 insertions(+), 35 deletions(-) diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 94b682f7..0b4952f0 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -29,6 +29,7 @@ import { getCurrentMouseData } from '../workbench/PointSelector'; import { anthropicNumFreeUsesCookieName, anthropicApiKeyCookieName, MaxFreeUses } from '~/utils/freeUses'; import type { FileMap } from '~/lib/stores/files'; import { shouldIncludeFile } from '~/utils/fileUtils'; +import { getNutLoginKey } from '~/lib/replay/Problems'; const toastAnimation = cssTransition({ enter: 'animated fadeInRight', @@ -334,11 +335,18 @@ export const ChatImpl = memo( return; } + const loginKey = getNutLoginKey(); + if (!loginKey) { + toast.error('Please set a login key in the "User Info" settings.'); + return; + } + const anthropicApiKey = Cookies.get(anthropicApiKeyCookieName); - if (!anthropicApiKey) { + + if (!loginKey && !anthropicApiKey) { const numFreeUses = +(Cookies.get(anthropicNumFreeUsesCookieName) || 0); if (numFreeUses >= MaxFreeUses) { - toast.error('All free uses consumed. Please set an Anthropic API key in the settings.'); + toast.error('All free uses consumed. Please set a login key or Anthropic API key in the "User Info" settings.'); return; } @@ -412,7 +420,7 @@ export const ChatImpl = memo( image: imageData, })), ] as any, // Type assertion to bypass compiler check - }, { body: { simulationEnhancedPrompt, anthropicApiKey } }); + }, { body: { simulationEnhancedPrompt, anthropicApiKey, loginKey } }); if (fileModifications !== undefined) { /** diff --git a/app/components/settings/providers/APIKeysTab.tsx b/app/components/settings/providers/APIKeysTab.tsx index a7bbe115..b8981f5f 100644 --- a/app/components/settings/providers/APIKeysTab.tsx +++ b/app/components/settings/providers/APIKeysTab.tsx @@ -2,16 +2,16 @@ import { useState } from 'react'; import { toast } from 'react-toastify'; import Cookies from 'js-cookie'; import { anthropicNumFreeUsesCookieName, anthropicApiKeyCookieName, MaxFreeUses } from '~/utils/freeUses'; -import { setNutAdminKey, setProblemsUsername, getNutAdminKey, getProblemsUsername } from '~/lib/replay/Problems'; +import { saveNutLoginKey, saveProblemsUsername, getNutLoginKey, getProblemsUsername } from '~/lib/replay/Problems'; export default function ConnectionsTab() { const [apiKey, setApiKey] = useState(Cookies.get(anthropicApiKeyCookieName) || ''); const [username, setUsername] = useState(getProblemsUsername() || ''); - const [adminKey, setAdminKey] = useState(getNutAdminKey() || ''); + const [loginKey, setLoginKey] = useState(getNutLoginKey() || ''); const numFreeUses = +(Cookies.get(anthropicNumFreeUsesCookieName) || 0); const handleSaveAPIKey = async (key: string) => { - if (!key || !key.startsWith('sk-ant-')) { + if (key && !key.startsWith('sk-ant-')) { toast.error('Please provide a valid Anthropic API key'); return; } @@ -21,13 +21,19 @@ export default function ConnectionsTab() { }; const handleSaveUsername = async (username: string) => { - setProblemsUsername(username); + saveProblemsUsername(username); setUsername(username); }; - const handleSaveAdminKey = async (key: string) => { - setNutAdminKey(key); - setAdminKey(key); + const handleSaveLoginKey = async (key: string) => { + setLoginKey(key); + + try { + await saveNutLoginKey(key); + toast.success('Login key saved'); + } catch (error) { + toast.error('Failed to save login key'); + } }; return ( @@ -61,13 +67,13 @@ export default function ConnectionsTab() { /> -

Nut Admin Key

+

Nut Login Key

handleSaveAdminKey(e.target.value)} + value={loginKey} + onChange={(e) => handleSaveLoginKey(e.target.value)} className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50" />
diff --git a/app/components/sidebar/Menu.client.tsx b/app/components/sidebar/Menu.client.tsx index 5549e236..745c5de8 100644 --- a/app/components/sidebar/Menu.client.tsx +++ b/app/components/sidebar/Menu.client.tsx @@ -13,7 +13,7 @@ import { binDates } from './date-binning'; import { useSearchFilter } from '~/lib/hooks/useSearchFilter'; import { SaveProblem } from './SaveProblem'; import { SaveSolution } from './SaveSolution'; -import { hasNutAdminKey } from '~/lib/replay/Problems'; +import { getNutIsAdmin } from '~/lib/replay/Problems'; const menuVariants = { closed: { @@ -140,7 +140,7 @@ export const Menu = () => { Problems - {hasNutAdminKey() && } + {getNutIsAdmin() && } (); let finished: (v?: any) => void; @@ -49,6 +50,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) { const anthropicApiKey: AnthropicApiKey = { key: apiKey, isUser: !!clientAnthropicApiKey, + userLoginKey: loginKey, }; const resultStream = new ReadableStream({ diff --git a/app/routes/problem.$id.tsx b/app/routes/problem.$id.tsx index 5af7210f..126f9c57 100644 --- a/app/routes/problem.$id.tsx +++ b/app/routes/problem.$id.tsx @@ -5,11 +5,10 @@ import BackgroundRays from '~/components/ui/BackgroundRays'; import { TooltipProvider } from '@radix-ui/react-tooltip'; import { ToastContainerWrapper, Status, Keywords } from './problems'; import { toast } from 'react-toastify'; -import { useCallback, useEffect } from 'react'; -import { useState } from 'react'; +import { Suspense, useCallback, useEffect, useState } from 'react'; import { useParams } from '@remix-run/react'; -import { getProblem, updateProblem as backendUpdateProblem, getProblemsUsername, BoltProblemStatus, hasNutAdminKey } from '~/lib/replay/Problems'; -import type { BoltProblem, BoltProblemComment, BoltProblemInput } from '~/lib/replay/Problems'; +import { getProblem, updateProblem as backendUpdateProblem, getProblemsUsername, BoltProblemStatus, getNutIsAdmin } from '~/lib/replay/Problems'; +import type { BoltProblem, BoltProblemComment } from '~/lib/replay/Problems'; function Comments({ comments }: { comments: BoltProblemComment[] }) { return ( @@ -152,6 +151,8 @@ function UpdateProblemForms({ updateProblem }: { updateProblem: UpdateProblemCal ) } +const Nothing = () => null; + function ViewProblemPage() { const params = useParams(); const problemId = params.id; @@ -177,6 +178,7 @@ function ViewProblemPage() { }, [problemId]); return ( + }>
@@ -190,12 +192,13 @@ function ViewProblemPage() {
) : }
- {hasNutAdminKey() && problemData && ( + {getNutIsAdmin() && problemData && ( )} + ); } diff --git a/app/routes/problems.tsx b/app/routes/problems.tsx index 78259f7c..bde969ac 100644 --- a/app/routes/problems.tsx +++ b/app/routes/problems.tsx @@ -4,8 +4,7 @@ import { Menu } from '~/components/sidebar/Menu.client'; import BackgroundRays from '~/components/ui/BackgroundRays'; import { TooltipProvider } from '@radix-ui/react-tooltip'; import { cssTransition, ToastContainer } from 'react-toastify'; -import { useEffect } from 'react'; -import { useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; import { BoltProblemStatus, listAllProblems } from '~/lib/replay/Problems'; import type { BoltProblemDescription } from '~/lib/replay/Problems'; @@ -91,6 +90,8 @@ function getProblemStatus(problem: BoltProblemDescription): BoltProblemStatus { return problem.status ?? BoltProblemStatus.Pending; } +const Nothing = () => null; + function ProblemsPage() { const [problems, setProblems] = useState(null); const [statusFilter, setStatusFilter] = useState(BoltProblemStatus.Solved); @@ -104,6 +105,7 @@ function ProblemsPage() { }); return ( + }>
@@ -164,6 +166,7 @@ function ProblemsPage() {
+
); }