diff --git a/components/chat/Chat.tsx b/components/chat/Chat.tsx index e187ad0..26828b5 100644 --- a/components/chat/Chat.tsx +++ b/components/chat/Chat.tsx @@ -6,7 +6,11 @@ import { Stream, Message } from "@/lib/OpenRouter" import { MessageView } from "./Messages" import { useDB } from "@/components/DatabaseProvider" import { toast } from "sonner" +import { Button } from "@/components/ui/button" +import { openUrl } from '@tauri-apps/plugin-opener' + import SourceDropdown from "./SourceDropdown" +import Settings from "./Settings" import OpenAI from "openai" import { @@ -14,7 +18,8 @@ import { SendHorizontalIcon, GlobeIcon, WrenchIcon, - CheckIcon + CheckIcon, + KeyRoundIcon, } from "lucide-react" import { @@ -26,6 +31,33 @@ import { type FormEvent = React.KeyboardEvent +// SetUpKeyCard is a card that asks the user to set up their OpenRouter +// API key whenever it's not available. +export const SetUpKeyCard = () => { + const [settingsOpen, setSettingsOpen] = useState(false) + const openrouter = () => { + openUrl("https://openrouter.ai/") + } + return ( +
+ +

+ Configure API Key +

+

+ Configure your OpenRouter API key to begin sending messages. +

+
+ + +
+ +
+ ) +} + export default function Chat() { const [messages, setMessages] = useState<(Message|ToolCall)[]>([]) const [source, setSource] = useState('google/gemini-2.5-pro-exp-03-25:free') @@ -34,6 +66,8 @@ export default function Chat() { const [isStreaming, setIsStreaming] = useState(false) const [webSearchEnabled, setWebSearchEnabled] = useState(false) const [toolCallingEnabled, setToolCallingEnabled] = useState(true) + const [hasKey, setHasKey] = useState(false) + const [isLoading, setIsLoading] = useState(true) const [client, setClient] = useState(new OpenAI({ baseURL: "https://openrouter.ai/api/v1", dangerouslyAllowBrowser: true, @@ -44,36 +78,51 @@ export default function Chat() { // Fetch messages and API key from database. useEffect(() => { - // TODO: HANDLE DATABASE ERRORS. const fetchMessages = async () => { - const msgs = await db.history.readAll() - setMessages(msgs.map(msg => { - if (msg.role === 'tool') { + try { + const msgs = await db.history.readAll() + setMessages(msgs.map(msg => { + if (msg.role === 'tool') { + return { + tool : msg.tool_name, + id : msg.id, + content : msg.content, + } as ToolCall + } return { - tool : msg.tool_name, - id : msg.id, - content : msg.content, - } as ToolCall - } - return { - role : msg.role, - content : msg.content - } as Message - })) - setIsTyping(false) + role : msg.role, + content : msg.content + } as Message + })) + } + catch { + toast('Error: Failed to Load Chat Message', { + description: 'The database failed to load chat message' + }) + } + finally {setIsTyping(false)} } const fetchKey = async () => { - const keys = await db.keys.readAll() - if (keys.length === 0) { - // TODO: SHOW DIALOG - return + try { + const keys = await db.keys.readAll() + if (keys.length === 0) { + setHasKey(false) + return + } + setClient(new OpenAI({ + baseURL: "https://openrouter.ai/api/v1", + dangerouslyAllowBrowser: true, + apiKey: keys[0].key_hash, + })) + setHasKey(true) + } + catch { + toast('Error: Failed to fetch API Key', { + description: 'Ensure your API Key is correct' + }) } - setClient(new OpenAI({ - baseURL: "https://openrouter.ai/api/v1", - dangerouslyAllowBrowser: true, - apiKey: keys[0].key_hash, - })) } + setIsLoading(false) fetchMessages() fetchKey() }, []) @@ -167,6 +216,15 @@ export default function Chat() { } } + if (isLoading) { + setIsLoading(false) + } + + // Show a "set up API key" card when OpenRouter key is not configured. + if (!hasKey) { + return + } + return (
diff --git a/components/chat/Settings.tsx b/components/chat/Settings.tsx index e6cf306..31560fd 100644 --- a/components/chat/Settings.tsx +++ b/components/chat/Settings.tsx @@ -1,24 +1,26 @@ 'use client' -import { useState, useEffect } from "react" +import { useState, useEffect, Dispatch, SetStateAction } from "react" import { Button } from "@/components/ui/button" -import { SettingsIcon } from "lucide-react" import { useDB } from "@/components/DatabaseProvider" import { toast } from "sonner" import { Keys } from "@/lib/controller/KeyController" import { Dialog, - DialogTrigger, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog" +type SettingsProps = { + onOpenChange: Dispatch> + open: boolean, +} + // Settings displays a modal asking the user to configure their // OpenRouter API key. -const Settings = () => { - const [open, setOpen] = useState(false) +const Settings = ({open, onOpenChange} : SettingsProps) => { const [apiKey, setApiKey] = useState("") const db = useDB() @@ -35,7 +37,7 @@ const Settings = () => { setApiKey("") return } - setApiKey(keys[0].id!.toString()) + setApiKey(keys[0].key_hash) } fetchKey() }, []) @@ -45,6 +47,10 @@ const Settings = () => { const handleSubmit = async () => { try { await db.keys.deleteAll() + if (apiKey === "") { + onOpenChange(false) + return + } await db.keys.create({ key_hash : apiKey, created_at : Math.floor(Date.now() / 1000) @@ -54,16 +60,11 @@ const Settings = () => { description: 'A database error prevented the key from being saved' }) } - setOpen(false) + onOpenChange(false) } return ( - - - - + Enter your OpenRouter API Key diff --git a/components/navigation/RightNavigation.tsx b/components/navigation/RightNavigation.tsx index 8f4178a..a5c3805 100644 --- a/components/navigation/RightNavigation.tsx +++ b/components/navigation/RightNavigation.tsx @@ -1,27 +1,35 @@ import { Button } from "@/components/ui/button" import { NavigationState } from "./NavigationState" -import { MessageSquareIcon, PlusIcon} from "lucide-react" +import { MessageSquareIcon, PlusIcon, SettingsIcon} from "lucide-react" +import { useState } from "react" + import Chat from "@/components/chat/Chat" import Settings from "../chat/Settings" // RightNavigation consists of the create note, LLM chat, and // settings buttons. -const RightNavigation = ({ state } : { state: NavigationState }) => ( -
- { /* Redirecting to the note page creates a new note */ } - - { /* Toggle the right sidebar */ } - - -
-) +const RightNavigation = ({ state } : { state: NavigationState }) => { + const [settingsOpen, setSettingsOpen] = useState(false) + return ( +
+ { /* Redirecting to the note page creates a new note */ } + + { /* Toggle the right sidebar */ } + + + +
+ ) +} // RightSidebar shows the LLM chat sidebar. const RightSidebar = () => (