Skip to content

Commit

Permalink
Feat: AI page
Browse files Browse the repository at this point in the history
  • Loading branch information
illusionTBA committed Nov 2, 2024
1 parent c8c8a5a commit 68e283f
Show file tree
Hide file tree
Showing 10 changed files with 556 additions and 123 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 119 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) => {
Expand Down Expand Up @@ -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: "/",
Expand All @@ -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<Uint8Array>) {
// 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.");
// }
57 changes: 57 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
26 changes: 13 additions & 13 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -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(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}
</StrictMode>
);
}
Loading

0 comments on commit 68e283f

Please sign in to comment.