'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 { isValidPhoneNumber } from 'libphonenumber-js'; 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, NotionLogo, GithubLogo, Files, WhatsappLogo, ExclamationMark } 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"; import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"; import { Input } from "@/components/ui/input"; 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, }; }; enum PhoneNumberValidationState { Setup = "setup", SendOTP = "otp", VerifyOTP = "verify", Verified = "verified", } 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 [number, setNumber] = useState(undefined); const [otp, setOTP] = useState(""); const [numberValidationState, setNumberValidationState] = useState(PhoneNumberValidationState.Verified); const { toast } = useToast(); const cardClassName = "w-full lg:w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg"; useEffect(() => { setUserConfig(initialUserConfig); setNumber(initialUserConfig?.phone_number); setNumberValidationState( initialUserConfig?.is_phone_number_verified ? PhoneNumberValidationState.Verified : initialUserConfig?.phone_number ? PhoneNumberValidationState.SendOTP : PhoneNumberValidationState.Setup ); }, [initialUserConfig]); useEffect(() => { setIsMobileWidth(window.innerWidth < 786); const handleResize = () => setIsMobileWidth(window.innerWidth < 786); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const sendOTP = async () => { try { const response = await fetch(`/api/phone?phone_number=${number}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) throw new Error('Failed to send OTP'); setNumberValidationState(PhoneNumberValidationState.VerifyOTP); } catch (error) { console.error('Error sending OTP:', error); toast({ title: "📱 Phone", description: "Failed to send OTP. Try again or contact us at team@khoj.dev", }); } }; const verifyOTP = async () => { try { const response = await fetch(`/api/phone/verify?code=${otp}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) throw new Error('Failed to verify OTP'); setNumberValidationState(PhoneNumberValidationState.Verified); toast({ title: "📱 Phone", description: "Phone number verified", }); } catch (error) { console.error('Error verifying OTP:', error); toast({ title: "📱 Phone", description: "Failed to verify OTP. Try again or contact us at team@khoj.dev", }); } }; 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)}`} ))}
Chat on Whatsapp {numberValidationState === PhoneNumberValidationState.Verified && ( ) || numberValidationState !== PhoneNumberValidationState.Setup && ( )}

Connect your number to chat with Khoj on WhatsApp. Learn more about the integration here.

{numberValidationState === PhoneNumberValidationState.VerifyOTP && (

{`Enter the OTP sent to your WhatsApp number: ${number}`}

setNumberValidationState(PhoneNumberValidationState.Verified)} >
) || (
setNumber(e.target.value)} value={number} placeholder="Enter your WhatsApp number" className="w-full border border-gray-300 rounded-lg p-4" />
)}
{numberValidationState === PhoneNumberValidationState.VerifyOTP && ( ) || ( )}
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") && ( ) || ( )}
); }