diff --git a/package.json b/package.json index 2232a86..00ee7e1 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,13 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-router": "^1.78.0", "@titaniumnetwork-dev/ultraviolet": "^3.2.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dotenv": "^16.4.5", + "fake-useragent": "^1.0.1", "fastify": "^5.0.0", "framer-motion": "^11.11.1", "lucide-react": "^0.447.0", @@ -41,6 +43,7 @@ "@eslint/js": "^9.11.1", "@tanstack/router-devtools": "^1.78.0", "@tanstack/router-plugin": "^1.78.0", + "@types/fake-useragent": "^1.0.0", "@types/node": "^22.7.4", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf5fb99..83a4409 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@radix-ui/react-separator': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.11)(react@18.3.1) '@tanstack/react-router': specifier: ^1.78.0 version: 1.78.0(@tanstack/router-generator@1.78.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -50,6 +53,9 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + fake-useragent: + specifier: ^1.0.1 + version: 1.0.1 fastify: specifier: ^5.0.0 version: 5.0.0 @@ -96,6 +102,9 @@ importers: '@tanstack/router-plugin': specifier: ^1.78.0 version: 1.78.0(vite@5.4.8(@types/node@22.7.4)) + '@types/fake-useragent': + specifier: ^1.0.0 + version: 1.0.0 '@types/node': specifier: ^22.7.4 version: 22.7.4 @@ -1220,6 +1229,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/fake-useragent@1.0.0': + resolution: {integrity: sha512-T+GsW6umr2E27FwubiKrPsjIyqx526AQH1dc1Sky31EnVW5YJ4aNV/KCWeCr9LBioSMGHi7zjNxAteHPQtzeDw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1628,6 +1640,9 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + fake-useragent@1.0.1: + resolution: {integrity: sha512-BOQh1TM//DhrVaeZ+b3w3s4E40rfYcDTn5aoSM2w1xVsZVGglNOzPR5H8KDO8NmF8sT4ppxyb4/MHGIHfZsVDA==} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -3579,6 +3594,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/fake-useragent@1.0.0': {} + '@types/json-schema@7.0.15': {} '@types/node@22.7.4': @@ -4041,6 +4058,8 @@ snapshots: events@3.3.0: {} + fake-useragent@1.0.1: {} + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} diff --git a/server.ts b/server.ts index 1b379fd..1785f9b 100644 --- a/server.ts +++ b/server.ts @@ -5,6 +5,7 @@ import { createServer } from "http"; import wisp from "wisp-server-node"; import path from "path"; import { fileURLToPath } from "url"; +import { Readable } from "node:stream"; import fs from "node:fs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -22,6 +23,29 @@ if (fs.existsSync(path.join(__dirname, "sponsers.json"))) { fs.readFileSync(path.join(__dirname, "sponsers.json"), "utf8") ); } + +type ChatPayload = { + messages: { content: string; role: "user" | "assistant" }[]; + model: "gpt-4o-mini"; +}; +const syntheticHeaders = { + accept: "application/json", + "accept-language": "en-US,en;q=0.9", + "cache-control": "no-cache", + "content-type": "application/json", + pragma: "no-cache", + priority: "u=1, i", + "sec-ch-ua": '"Not?A_Brand";v="99", "Chromium";v="130"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + cookie: "dcm=3", + Referer: "https://duckduckgo.com/", + "Referrer-Policy": "origin", +}; + const serverFactory = (handler, opts) => { return createServer() .on("request", (req, res) => { @@ -56,6 +80,52 @@ app.get("/api/sponser", async (req, res) => { } }); +app.post("/api/chat", async (req, res) => { + const { messages, model } = JSON.parse(req.body as string) as ChatPayload; + if (!messages || !model) { + return res.send({ + success: false, + error: "Missing messages or model", + }); + } + let vqdToken; + try { + if (req.headers["x-vqd-4"]) { + vqdToken = req.headers["x-vqd-4"]; + } else { + const res = await fetch(`https://duckduckgo.com/duckchat/v1/status`, { + headers: { + "x-vqd-accept": "1", + ...syntheticHeaders, + }, + }); + vqdToken = res.headers.get("x-vqd-4"); + } + + const response = await fetch(`https://duckduckgo.com/duckchat/v1/chat`, { + method: "POST", + body: JSON.stringify({ + messages, + model, + }), + headers: { + ...syntheticHeaders, + "X-Vqd-4": `${vqdToken}`, + }, + }); + if (response.body) { + res.header("X-Vqd-4", `${response.headers.get("X-Vqd-4")}`); + return res.send(Readable.from(response.body)); + } + } catch (error) { + res.status(500); + res.send({ + success: false, + error: error, + }); + } +}); + app.register(fastifyStatic, { root: path.join(__dirname, "dist"), prefix: "/", @@ -74,3 +144,52 @@ app.listen({ port: parseInt(process.env.PORT || "3000") }, (err, address) => { } console.log(`server listening on ${address}`); }); + +// async function* readStream(body: ReadableStream) { +// const reader = body.getReader(); +// const decoder = new TextDecoder("utf-8"); + +// let partial = ""; + +// let done = false; +// while (!done) { +// const { value, done: streamDone } = await reader.read(); +// done = streamDone; +// if (value) { +// let decodedData = decoder.decode(value, { stream: false }); + +// // Remove "data: " from each line in the decoded data +// decodedData = decodedData +// .split("\n") +// .map((line) => (line.startsWith("data: ") ? line.substring(6) : line)) +// .join("\n") +// .trim(); + +// if (decodedData !== "[DONE]") { +// const arr = decodedData.split("\n"); +// for (let i = 0; i < arr.length; i++) { +// const el = arr[i].replaceAll("\n", ""); +// if (el === "\n") continue; +// if (el === "[DONE]") { +// done = true; +// continue; +// } +// if (el.charAt(el.length - 1) === "}") { +// try { +// partial += el; +// yield `${partial}`; +// partial = ""; +// } catch (error) { +// console.log("Got error while parsing JSON", error); +// console.log("Partial: ", partial); +// } +// } else { +// partial += el; +// } +// } +// } +// } +// } + +// console.log("Stream complete."); +// } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..65d4fcd --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/main.tsx b/src/main.tsx index 73af24e..d5367f8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,27 +1,27 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import { RouterProvider, createRouter } from '@tanstack/react-router' -import './index.css' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RouterProvider, createRouter } from "@tanstack/react-router"; +import "./index.css"; // Import the generated route tree -import { routeTree } from './routeTree.gen' +import { routeTree } from "./routeTree.gen"; // Create a new router instance -const router = createRouter({ routeTree }) +const router = createRouter({ routeTree, defaultStaleTime: Infinity }); // Register the router instance for type safety -declare module '@tanstack/react-router' { +declare module "@tanstack/react-router" { interface Register { - router: typeof router + router: typeof router; } } -const rootElement = document.getElementById('root')! +const rootElement = document.getElementById("root")!; if (!rootElement.innerHTML) { - const root = createRoot(rootElement) + const root = createRoot(rootElement); root.render( - , - ) -} \ No newline at end of file + + ); +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 9015bfa..750e669 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -16,10 +16,17 @@ import { Route as rootRoute } from './routes/__root' // Create Virtual Routes +const ChatLazyImport = createFileRoute('/chat')() const IndexLazyImport = createFileRoute('/')() // Create/Update Routes +const ChatLazyRoute = ChatLazyImport.update({ + id: '/chat', + path: '/chat', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/chat.lazy').then((d) => d.Route)) + const IndexLazyRoute = IndexLazyImport.update({ id: '/', path: '/', @@ -37,6 +44,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexLazyImport parentRoute: typeof rootRoute } + '/chat': { + id: '/chat' + path: '/chat' + fullPath: '/chat' + preLoaderRoute: typeof ChatLazyImport + parentRoute: typeof rootRoute + } } } @@ -44,32 +58,37 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexLazyRoute + '/chat': typeof ChatLazyRoute } export interface FileRoutesByTo { '/': typeof IndexLazyRoute + '/chat': typeof ChatLazyRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexLazyRoute + '/chat': typeof ChatLazyRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' + fullPaths: '/' | '/chat' fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/' + to: '/' | '/chat' + id: '__root__' | '/' | '/chat' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexLazyRoute: typeof IndexLazyRoute + ChatLazyRoute: typeof ChatLazyRoute } const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, + ChatLazyRoute: ChatLazyRoute, } export const routeTree = rootRoute @@ -82,11 +101,15 @@ export const routeTree = rootRoute "__root__": { "filePath": "__root.tsx", "children": [ - "/" + "/", + "/chat" ] }, "/": { "filePath": "index.lazy.tsx" + }, + "/chat": { + "filePath": "chat.lazy.tsx" } } } diff --git a/src/routes/chat.lazy.tsx b/src/routes/chat.lazy.tsx new file mode 100644 index 0000000..38b9a37 --- /dev/null +++ b/src/routes/chat.lazy.tsx @@ -0,0 +1,206 @@ +import { createLazyFileRoute, Link } from "@tanstack/react-router"; +import GridPattern from "@/components/ui/grid-pattern"; +import { cn } from "@/lib/utils"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Bot, Send, User, Home } from "lucide-react"; +import { useEffect, useState } from "react"; +import { create } from "zustand"; +export const Route = createLazyFileRoute("/chat")({ + component: RouteComponent, +}); + +type ChatPayload = { + messages: { content: string; role: "user" | "assistant" }[]; + model: "gpt-4o-mini"; +}; + +type MessageStore = { + messages: ChatPayload["messages"]; + addMessage: (message: ChatPayload["messages"][number]) => void; + model: ChatPayload["model"]; + clearMessages: () => void; +}; + +const useMessageStore = create()((set) => ({ + messages: [], + addMessage: (message) => + set((state) => ({ messages: [...state.messages, message] })), + clearMessages: () => set({ messages: [] }), + model: "gpt-4o-mini", +})); + +function RouteComponent() { + const messageStore = useMessageStore(); + const [userInput, setUserInput] = useState(""); + const [VQDToken, setVQDToken] = useState(); + const [proccessing, setProcesssing] = useState(false); + + useEffect(() => { + messageStore.clearMessages(); + }, []); + + const sendChat = (updatedMessages: ChatPayload["messages"]) => { + if (!userInput || updatedMessages.length === 0) return; + setProcesssing(true); + (async () => { + console.log(updatedMessages); + const headers = new Headers(); + if (VQDToken) { + headers.append("X-Vqd-4", VQDToken); + } + const response = await fetch("/api/chat", { + method: "POST", + body: JSON.stringify({ + messages: updatedMessages, + model: messageStore.model, + }), + headers, + }); + + if (!response.body) { + console.log("No response body"); + setProcesssing(false); + return; + } + + setVQDToken(response.headers.get("X-Vqd-4") || undefined); + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + + let partial = ""; + let messsage = ""; + let done = false; + while (!done) { + const { value, done: streamDone } = await reader.read(); + done = streamDone; + if (value) { + let decodedData = decoder.decode(value, { stream: false }); + + decodedData = decodedData + .split("\n") + .map((line) => + line.startsWith("data: ") ? line.substring(6) : line + ) + .join("\n") + .trim(); + + if (decodedData !== "[DONE]") { + const arr = decodedData.split("\n"); + for (let i = 0; i < arr.length; i++) { + const el = arr[i].replaceAll("\n", ""); + if (el === "\n") continue; + if (el === "[DONE]") { + done = true; + continue; + } + if (el.charAt(el.length - 1) === "}") { + try { + partial += el; + const obj = JSON.parse(partial); + + if (obj.message) messsage += obj.message; + // console.log(obj); + partial = ""; + } catch (error) { + console.log("Got error while parsing JSON", error); + console.log("Partial: ", partial); + } + } else { + partial += el; + } + } + } + } + } + + messageStore.addMessage({ role: "assistant", content: messsage }); + setUserInput(""); + setProcesssing(false); + })(); + }; + + return ( + <> + +
+
+ {messageStore.messages.map((message, i) => { + return ; + })} + {proccessing && ( + + )} +
+ +
+ + + + +
+ setUserInput(e.target.value)} + value={userInput} + disabled={proccessing} + placeholder="Type your message here" + onKeyDown={(e) => { + if (e.key === "Enter") { + const newMessage = { + content: userInput, + role: "user", + } as ChatPayload["messages"][number]; + messageStore.addMessage(newMessage); + sendChat([...messageStore.messages, newMessage]); + } + }} + className="w-full border-none rounded-3xl h-12 text-lg focus-visible:ring-0" + /> + +
+
+
+ + ); +} + +function Chat({ message }: { message: ChatPayload["messages"][number] }) { + return ( +
+ {message.role === "user" ? ( + + ) : ( + + )} +
+

+ {message.content} +

+
+ ); +} diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx index 1d930c3..30dc491 100644 --- a/src/routes/index.lazy.tsx +++ b/src/routes/index.lazy.tsx @@ -1,36 +1,36 @@ -import { createLazyFileRoute } from '@tanstack/react-router' -import GridPattern from '@/components/ui/grid-pattern' -import { cn } from '../lib/utils' -import { Input } from '../components/ui/input' +import { createLazyFileRoute, Link } from "@tanstack/react-router"; +import GridPattern from "@/components/ui/grid-pattern"; +import { cn } from "../lib/utils"; +import { Input } from "../components/ui/input"; // import LetterPullup from "./components/ui/letter-pullup"; -import { useSettings } from '../store' -import PopularSites from '../sites.json' +import { useSettings } from "../store"; +import PopularSites from "../sites.json"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' +} from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' +} from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/components/ui/select' +} from "@/components/ui/select"; -import { Separator } from '../components/ui/separator' -import Draggable from 'react-draggable' -import { Dock, DockIcon } from '../components/ui/dock' -import { useEffect, useRef, useState } from 'react' -import { AnimatePresence, motion } from 'framer-motion' +import { Separator } from "../components/ui/separator"; +import Draggable from "react-draggable"; +import { Dock, DockIcon } from "../components/ui/dock"; +import { useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; import { House, Gamepad, @@ -39,64 +39,65 @@ import { AlignJustify, ArrowLeft, ArrowRight, -} from 'lucide-react' -import { Xor } from '../components/xor' -import ReactGA from 'react-ga4' -import useSw from '../components/hooks/useSw' + MessageCircle, +} from "lucide-react"; +import { Xor } from "../components/xor"; +import ReactGA from "react-ga4"; +import useSw from "../components/hooks/useSw"; -export const Route = createLazyFileRoute('/')({ +export const Route = createLazyFileRoute("/")({ component: Home, -}) +}); type Sponser = { - title: string - icon: string - url: string - discord: string -} + title: string; + icon: string; + url: string; + discord: string; +}; function Home() { - const settingStore = useSettings() - const [term, setTerm] = useState('') - const [suggestions, setSuggestions] = useState<{ phrase: string }[]>([]) - const [shouldOpen, setShouldOpen] = useState(false) - const [openSearch, setOpenSearch] = useState(false) - const [openSettings, setOpenSettings] = useState(false) - const [sponser, setSponser] = useState() - const frame = useRef(null) - const dock = useRef(null) - useSw('/sw.js') + const settingStore = useSettings(); + const [term, setTerm] = useState(""); + const [suggestions, setSuggestions] = useState<{ phrase: string }[]>([]); + const [shouldOpen, setShouldOpen] = useState(false); + const [openSearch, setOpenSearch] = useState(false); + const [openSettings, setOpenSettings] = useState(false); + const [sponser, setSponser] = useState(); + const frame = useRef(null); + const dock = useRef(null); + useSw("/sw.js"); - ReactGA.initialize('G-PBTEBTLRLZ') - ReactGA.event('page_view', { + ReactGA.initialize("G-PBTEBTLRLZ"); + ReactGA.event("page_view", { page_location: window.location.href, - page_title: 'Emerald', - user_agent: navigator.userAgent ?? 'no ua??', - }) + page_title: "Emerald", + user_agent: navigator.userAgent ?? "no ua??", + }); useEffect(() => { - setSuggestions([]) + setSuggestions([]); const delayDebounceFn = setTimeout(async () => { if (term.length > 0) { - const res = await fetch('/api/search?query=' + term) - const terms = await res.json() - setSuggestions(terms) + const res = await fetch("/api/search?query=" + term); + const terms = await res.json(); + setSuggestions(terms); } else { - setSuggestions([]) + setSuggestions([]); } - }, 500) + }, 500); return () => { - clearTimeout(delayDebounceFn) - } - }, [term]) + clearTimeout(delayDebounceFn); + }; + }, [term]); useEffect(() => { - ;(async () => { - const res = await fetch('/api/sponser') - const sponser = await res.json() - setSponser(sponser as unknown as Sponser) - })() - }, []) + (async () => { + const res = await fetch("/api/sponser"); + const sponser = await res.json(); + setSponser(sponser as unknown as Sponser); + })(); + }, []); const containerVariants = { hidden: { @@ -106,7 +107,7 @@ function Home() { opacity: 1, transition: {}, }, - } + }; const itemVariants = { hidden: { @@ -121,34 +122,34 @@ function Home() { opacity: 0, y: 100, }, - } + }; const canParse = (p: string) => { try { - new URL(p) - return true + new URL(p); + return true; } catch (e) { - return false + return false; } - } + }; const handleSearch = (p?: string) => { if (p && canParse(p)) { - setShouldOpen(true) + setShouldOpen(true); - frame.current!.src = `/~/${settingStore.proxy}/${Xor.encode(p)}` - return + frame.current!.src = `/~/${settingStore.proxy}/${Xor.encode(p)}`; + return; } if (p) { - setShouldOpen(true) + setShouldOpen(true); frame.current!.src = `/~/${settingStore.proxy}/${Xor.encode( - settingStore.searchEngine.url + p, - )}` + settingStore.searchEngine.url + p + )}`; } else { - setShouldOpen(true) + setShouldOpen(true); frame.current!.src = `/~/${settingStore.proxy}/${Xor.encode( - settingStore.searchEngine.url + term, - )}` + settingStore.searchEngine.url + term + )}`; } - } + }; return ( <> @@ -174,9 +175,9 @@ function Home() { value={term} onChange={(e) => setTerm(e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSearch() - setOpenSearch(false) + if (e.key === "Enter") { + handleSearch(); + setOpenSearch(false); } }} /> @@ -199,10 +200,10 @@ function Home() { delay: 0.05 * index, }} onClick={() => { - setSuggestions([]) + setSuggestions([]); - handleSearch(suggestion.phrase) - setOpenSearch(false) + handleSearch(suggestion.phrase); + setOpenSearch(false); }} > {suggestion.phrase} @@ -222,21 +223,21 @@ function Home() { height={30} x={-1} y={-1} - strokeDasharray={'1 2'} + strokeDasharray={"1 2"} className={cn( `[mask-image:radial-gradient(500px_circle_at_center,white,transparent)] z-[0] ${ - shouldOpen ? 'hidden' : '' - }`, + shouldOpen ? "hidden" : "" + }` )} />
@@ -250,8 +251,8 @@ function Home() { value={term} onChange={(e) => setTerm(e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSearch() + if (e.key === "Enter") { + handleSearch(); } }} /> @@ -267,11 +268,11 @@ function Home() { settingStore.setSearchEngine( - 'DuckDuckgo', - 'https://duckduckgo.com/?q=', + "DuckDuckgo", + "https://duckduckgo.com/?q=" ) } > @@ -284,11 +285,11 @@ function Home() { settingStore.setSearchEngine( - 'Google', - 'https://www.google.com/search?q=', + "Google", + "https://www.google.com/search?q=" ) } > @@ -317,9 +318,9 @@ function Home() { delay: 0.05 * index, }} onClick={() => { - setSuggestions([]) + setSuggestions([]); - handleSearch(suggestion.phrase) + handleSearch(suggestion.phrase); }} > {suggestion.phrase} @@ -372,10 +373,10 @@ function Home() { {sponser.title}

- Click{' '} + Click{" "} here - {' '} + {" "} to join their discord!

@@ -390,8 +391,8 @@ function Home() { disabled={!shouldOpen} handle=".handle" positionOffset={{ - x: '-50%', - y: '-1%', + x: "-50%", + y: "-1%", }} >
@@ -403,14 +404,14 @@ function Home() { {shouldOpen && (
{ - frame.current!.contentWindow?.history.back() + frame.current!.contentWindow?.history.back(); }} className="transform hover:-translate-y-1 transition-all hover:scale-105 cursor-pointer" /> @@ -427,12 +428,17 @@ function Home() { { - window.location.reload() + window.location.reload(); }} /> - + + + + + + Current proxy