'use client' import styles from "./settings.module.css"; import { Suspense, useEffect, useState } from "react"; import { useToast } from "@/components/ui/use-toast" import { useUserConfig, ModelOptions, UserConfig } from "../common/auth"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { ArrowRight, ChatCircleText, Key, Palette, SpeakerHigh, UserCircle, FileMagnifyingGlass, Trash, Copy, PlusCircle, CreditCard, CheckCircle, } from "@phosphor-icons/react"; import NavMenu from "../components/navMenu/navMenu"; import SidePanel from "../components/sidePanel/chatHistorySidePanel"; import Loading from "../components/loading/loading"; import { ExternalLinkIcon } from "lucide-react"; interface DropdownComponentProps { items: ModelOptions[]; selected: number; callbackFunc: (value: string) => Promise; } const DropdownComponent: React.FC = ({ items, selected, callbackFunc }) => { const [position, setPosition] = useState(selected?.toString() ?? "0"); return !!selected && (
{ setPosition(value); await callbackFunc(value); }} > {items.map((item) => ( {item.name} ))}
); } interface TokenObject { token: string; name: string; } export const useApiKeys = () => { const [apiKeys, setApiKeys] = useState([]); const { toast } = useToast(); const generateAPIKey = async () => { try { const response = await fetch(`/auth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); const tokenObj = await response.json(); setApiKeys(prevKeys => [...prevKeys, tokenObj]); } catch (error) { console.error('Error generating API key:', error); } }; const copyAPIKey = async (token: string) => { try { await navigator.clipboard.writeText(token); toast({ title: "🔑 API Key", description: "Copied to clipboard", }); } catch (error) { console.error('Error copying API key:', error); } }; const deleteAPIKey = async (token: string) => { try { const response = await fetch(`/auth/token?token=${token}`, { method: 'DELETE' }); if (response.ok) { setApiKeys(prevKeys => prevKeys.filter(key => key.token !== token)); } } catch (error) { console.error('Error deleting API key:', error); } }; const listApiKeys = async () => { try { const response = await fetch(`/auth/token`); const tokens = await response.json(); if (tokens?.length > 0) { setApiKeys(tokens); } } catch (error) { console.error('Error listing API keys:', error); } }; useEffect(() => { listApiKeys(); }, []); return { apiKeys, generateAPIKey, copyAPIKey, deleteAPIKey, }; }; export default function SettingsView() { const [title, setTitle] = useState("Settings"); const [isMobileWidth, setIsMobileWidth] = useState(false); const { apiKeys, generateAPIKey, copyAPIKey, deleteAPIKey } = useApiKeys(); const initialUserConfig = useUserConfig(true); const [userConfig, setUserConfig] = useState(null); const { toast } = useToast(); const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg"; useEffect(() => setUserConfig(initialUserConfig), [initialUserConfig]); useEffect(() => { setIsMobileWidth(window.innerWidth < 786); const handleResize = () => setIsMobileWidth(window.innerWidth < 786); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const setSubscription = async (state: string) => { try { const url = `/api/subscription?email=${userConfig?.username}&operation=${state}`; const response = await fetch(url, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) throw new Error('Failed to change subscription'); // Set updated user settings if (userConfig) { let newUserConfig = userConfig; newUserConfig.subscription_state = state === "cancel" ? "unsubscribed" : "subscribed"; setUserConfig(newUserConfig); } // Notify user of subscription change toast({ title: "💳 Billing", description: userConfig?.subscription_state === "unsubscribed" ? "Your subscription was cancelled" : "Your Futurist subscription has been renewed", }); } catch (error) { console.error('Error changing subscription:', error); toast({ title: "💳 Billing", description: state === "cancel" ? "Failed to cancel subscription. Try again or contact us at team@khoj.dev" : "Failed to renew subscription. Try again or contact us at team@khoj.dev", }); } }; const updateModel = (name: string) => async (id: string) => { try { const response = await fetch(`/api/model/${name}?id=` + id, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) throw new Error('Failed to update model'); toast({ description: `${name} model updated succesfully`, }); } catch (error) { console.error(`Failed to update ${name} model:`, error); toast({ description: `Failed to update ${name} model. Try again.`, variant: "destructive", }); } }; if (!userConfig) return ; return (
{title}
}>
Profile
Name
Content
Files Manage your synced files Github Set repositories to index Notion Sync your Notion pages
Features
Chat Search Paint Voice
Clients
API Keys

Access Khoj from your Desktop App, Obsidian plugin, and more.

Name Token Actions {apiKeys.map((key) => ( {key.name} {`${key.token.slice(0, 4)}...${key.token.slice(-4)}`} ))}
Billing
Subscription {(userConfig.subscription_state === "subscribed" || userConfig.subscription_state === "unsubscribed") && ( )} {userConfig.subscription_state === "trial" && (

You are on a 14 day trial of the Khoj Futurist plan

See pricing for details

) || userConfig.subscription_state === "subscribed" && (

You are subscribed to Khoj Futurist

Subscription will renew on { userConfig.subscription_renewal_date }

) || userConfig.subscription_state === "unsubscribed" && (

You are subscribed to Khoj Futurist

Subscription will expire on { userConfig.subscription_renewal_date }

) || userConfig.subscription_state === "expired" && (

Subscribe to the Khoj Futurist plan

{userConfig.subscription_renewal_date && (

Your subscription expired on { userConfig.subscription_renewal_date }

) || (

See pricing for details

)}
)}
{(userConfig.subscription_state == "subscribed") && ( ) || (userConfig.subscription_state == "unsubscribed") && ( ) || ( )}
); }