# Project export: EasyPC

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 10.0
- Tagline: Your first stop when PC building, featuring a state-of-the-art PC-Parts AI refined and trained over valuable data.
- Devpost: https://devpost.com/software/easypc
- GitHub: https://github.com/TimTimich6/easypc
- Result: winner (Best Use of MindsDB)
- Team: 3 GitHub contributor(s) — TimTimich6 (13 commits), jason136 (3 commits), Thor Christoffersen Hochman (2 commits)

## Devpost submission (written by the team)

### Inspiration

Our inspiration all stems from one of our users terrible experiences building a PC. Building a PC can be an incredibly rewarding but also frustrating experience, especially for first-time builders. Our team member had a particularly disastrous PC build - incorrect parts that weren't compatible, unstable overclocking leading to crashes, and way over budget. It was a valuable learning experience but very costly in both time and money. We realized there had to be a better way than trial and error for PC novices to configure and buy the right components for their needs and budget. Our project aims to leverage AI and data to match users with PC parts tailored to their individual use case, whether that's high-end gaming, media editing, programming, etc.

### What it does

The website takes in a user's custom PC build needs and budget, then gets to work finding the optimal parts combination. Users simply describe what they plan to use the computer for, such as high-end 3D editing under $2,000. Within seconds, the website outputs a complete part list with prices, specs, and descriptions tailored to the user's intended workload.

### How we built it

Utilizing a Convex fullstack, we looked for cutting edge, pioneering softwares to help propel our project. We landed on utilizing together.ai to base our main AI system. Our fined-tuned llama-7b was trained on countless data-points, and works to create accurate and timely recommendations. Going further down, we used a secondary AI in MindsDB for bulk jobs to generate accurate descriptions of Pc Parts. We pulled from a scraped database in

### Challenges we ran into

Along the way, there were many challenges. One included running on only caffeine, but that was deemed worth the trouble knowing the amazing project we built. On a more technical level, as the technologies we planned on using were newer, there wasnt that large of a network of integrations. To combat this, we produced our own implementations. Specifically for a MindsDB integration for together.ai. To further the usefulness of together.ai, we also created an integration for Discord. Furthermore, obtaining data was a monumental obstacle. As a group of 4 without much capital, we had to create our own legal web scrapping tools. We ran into countless issues but eventually created a capable web scrapping tool to gather publicly available data to train our model on. Eventually, we intend to invest into purchasing data from large PC Parts databases to finalize and secure data.

### Accomplishments we're proud of

We are definitely proud of the project we built, keeping in mind we are not all trained and seasoned hackathon veterans. More specifically, the revolutionary integrations are definitely a high-point for our project. Coming from knowing nothing about integrations, LLM creation and ethical data-scraping, we now know how to implement these systems in the future. And even when we would get frustrated, we always encouraged and pushed eachother forward in new and creative ways.

### What we learned

We learned that even if we start from a basic understanding of how LLMs AIs and databases work, through passion and hard work we can become experts in this field.

### What's next

Scaling this project will be easy. With an already fully-functioning AI system, the possibilites would be endless. We can keep feeding it more data, and we plan on implementing a text-feature where you could ask a fined-tuned LLM on any pc related questions.

## README (from the GitHub repository)

1st Place CalHacks's Co-host MindsDB prize
 


## Detected evidence (automated analysis)

Indexed codebase: 33 recognized source files, 61 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- PostgreSQL (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- HTML (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (69 of 69)

```
.eslintrc.json
.gitignore
.vscode/settings.json
app/api/dashboard/route.tsx
app/api/descriptions/route.tsx
app/api/search/route.tsx
app/components/Card.tsx
app/components/models/pc.jsx
app/components/Navbar.tsx
app/components/Part.tsx
app/components/Question.tsx
app/components/Search.tsx
app/components/sections/Hero.tsx
app/dashboard/page.tsx
app/faq/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/view/[_id]/page.tsx
convex/_generated/api.d.ts
convex/_generated/api.js
convex/_generated/dataModel.d.ts
convex/_generated/server.d.ts
convex/_generated/server.js
convex/myFunctions.ts
dataset/case-accessory.json
dataset/case-fan.json
dataset/case.json
dataset/cpu-cooler.json
dataset/cpu.json
dataset/external-hard-drive.json
dataset/fan-controller.json
dataset/headphones.json
dataset/internal-hard-drive.json
dataset/keyboard.json
dataset/memory.json
dataset/monitor.json
dataset/motherboard.json
dataset/mouse.json
dataset/optical-drive.json
dataset/os.json
dataset/power-supply.json
dataset/sound-card.json
dataset/speakers.json
dataset/thermal-paste.json
dataset/total.json
dataset/ups.json
dataset/video-card.json
dataset/webcam.json
dataset/wired-network-card.json
dataset/wireless-network-card.json
getBuildsScript.py
lib/db.ts
lib/mindsdb.ts
lib/parser.ts
lib/pinecone.ts
lib/scrape.ts
next.config.js
old/builds.txt
old/builds2.txt
old/output.jsonl
package.json
parser.ts
postcss.config.js
public/pc.glb
README.md
tailwind.config.ts
tsconfig.json
urls.txt
```

### Dependencies

- package.json: @pinecone-database/pinecone@^1.1.2, @react-three/drei@^9.88.7, @react-three/fiber@^8.15.8, @types/node@^20, @types/pg@^8.10.7, @types/react@^18, @types/react-dom@^18, @types/uuid@^9.0.6, autoprefixer@^10, axios@^1.6.0, convex@^1.5.1, dotenv@^16.3.1, eslint@^8, eslint-config-next@14.0.0, framer-motion@^10.16.4, net@^1.0.2, next@14.0.0, npm@^10.2.1, pg@^8.11.3, postcss@^8, postgres@^3.4.2, react@^18, react-dom@^18, tailwindcss@^3, three@^0.158.0, tls@^0.0.1, typescript@^5, uuid@^9.0.1

### Recent commits (newest first)

- Update README.md
- tim's mid-judging changes
- fix spacingt
- fix first
- final
- desc
- new postts
- loader
- lolcal
- ks
- parser
- network helpers
- pg2
- pg
- parser
- Merge branch 'main' of https://github.com/TimTimich6/easypc
- nav to builds
- animations
- fuck you
- dashboard

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "easypc",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@pinecone-database/pinecone": "^1.1.2",
    "@react-three/drei": "^9.88.7",
    "@react-three/fiber": "^8.15.8",
    "axios": "^1.6.0",
    "convex": "^1.5.1",
    "dotenv": "^16.3.1",
    "framer-motion": "^10.16.4",
    "net": "^1.0.2",
    "next": "14.0.0",
    "npm": "^10.2.1",
    "pg": "^8.11.3",
    "postgres": "^3.4.2",
    "react": "^18",
    "react-dom": "^18",
    "three": "^0.158.0",
    "tls": "^0.0.1",
    "uuid": "^9.0.1"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/pg": "^8.10.7",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@types/uuid": "^9.0.6",
    "autoprefixer": "^10",
    "eslint": "^8",
    "eslint-config-next": "14.0.0",
    "postcss": "^8",
    "tailwindcss": "^3",
    "typescript": "^5"
  }
}

```

### app/page.tsx

```typescript
"use client";
import Hero from "./components/sections/Hero";
export default function Home() {
  return (
    <main className="flex min-h-screen min-w-screen flex-col items-center justify-center  bg-primary">
      <Hero></Hero>
      <button></button>
    </main>
  );
}

```

### app/layout.tsx

```typescript
"use client";
import type { Metadata } from "next";
import { Poppins } from "next/font/google";
import "./globals.css";
import Navbar from "./components/Navbar";
import { ConvexProvider, ConvexReactClient } from "convex/react";

const poppins = Poppins({ weight: ["400", "500", "600", "700", "800", "900"], subsets: ["latin"] });

// export const metadata: Metadata = {
//   title: "EasyPC",
//   description: "Generate PC builds with AI",
// };

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL as string);
  return (
    <html lang="en">
      <ConvexProvider client={convex}>
        <body className={poppins.className + " " + "text-gray-800"}>
          {<Navbar />}
          {children}
        </body>
      </ConvexProvider>
    </html>
  );
}

```

### app/dashboard/page.tsx

```typescript
"use client";
import { useQuery } from "convex/react";
import React, { useState } from "react";
import { api } from "@/../convex/_generated/api";
import Card from "@/components/Card";
import { motion } from "framer-motion";
import { materialOpacity } from "three/examples/jsm/nodes/Nodes.js";

interface PartProps {
  src: string;
  model: string;
  type: string;
  price: number;
  desc?: string;
}

export default function Build() {
  const listBuilds = useQuery(api.myFunctions.listBuilds);

  return (
    <main className="flex min-h-screen h-full  p-24 py-40 bg-primary w-full flex-col relative gap-4 items-center">
      <h1 className="text-3xl font-bold w-auto self-start">Saved Builds</h1>
      <div className="flex flex-row  flex-wrap gap- w-full justify-start gap-6">
        {listBuilds?.map((build) => (
          <Card key={build._id} build={build}></Card>
        ))}
      </div>
    </main>
  );
}

```

### app/faq/page.tsx

```typescript
"use client";
import { useQuery } from "convex/react";
import React, { useState } from "react";
import { api } from "@/../convex/_generated/api";
import Question from "@/components/Question";
import { motion } from "framer-motion";
import { materialOpacity } from "three/examples/jsm/nodes/Nodes.js";


interface PartProps {
  src: string;
  model: string;
  type: string;
  price: number;
  desc?: string;
}

export default function faq() {
  const listBuilds = useQuery(api.myFunctions.listBuilds); 

  return (
    <main className="flex min-h-screen h-full  p-12 py-40 bg-primary w-full items-center flex-col gap-6 relative">
      <div 
      className="flex flex-row flex-wrap gap-5 justify-evenly w-1/2 font-bold">
      <h1 className="text-2xl w-full"> Frequently Asked Questions</h1>
        {listBuilds?.map((build) => (
          <Question key={build._id} build={build}
          
      >

          </Question>
        ))}
      </div>

     
    </main>
  );    
}

```

### convex/_generated/server.js

```javascript
/* eslint-disable */
/**
 * Generated utilities for implementing server-side Convex query and mutation functions.
 *
 * THIS CODE IS AUTOMATICALLY GENERATED.
 *
 * Generated by convex@1.5.1.
 * To regenerate, run `npx convex dev`.
 * @module
 */

import {
  actionGeneric,
  httpActionGeneric,
  queryGeneric,
  mutationGeneric,
  internalActionGeneric,
  internalMutationGeneric,
  internalQueryGeneric,
} from "convex/server";

/**
 * Define a query in this Convex app's public API.
 *
 * This function will be allowed to read your Convex database and will be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const query = queryGeneric;

/**
 * Define a query that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to read from your Convex database. It will not be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const internalQuery = internalQueryGeneric;

/**
 * Define a mutation in this Convex app's public API.
 *
 * This function will be allowed to modify your Convex database and will be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const mutation = mutationGeneric;

/**
 * Define a mutation that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to modify your Convex database. It will not be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const internalMutation = internalMutationGeneric;

/**
 * Define an action in this Convex app's public API.
 *
 * An action is a function which can execute any JavaScript code, including non-deterministic
 * code and code with side-effects, like calling third-party services.
 * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
 * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
 *
 * @param func - The action. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
 */
export const action = actionGeneric;

/**
 * Define an action that is only accessible from other Convex functions (but not from the client).
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
 */
export const internalAction = internalActionGeneric;

/**
 * Define a Convex HTTP action.
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
 * as its second.
 * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
 */
export const httpAction = httpActionGeneric;

```

### app/api/descriptions/route.tsx

```typescript
import { NextRequest, NextResponse } from "next/server";
import insert_build from "../../../lib/db";
import get_part_descriptions from "../../../lib/mindsdb";

export async function POST(req: NextRequest) {
  const body = await req.json();
  console.log(body)
  const resp2 = await insert_build(body.build, body.prompt.trim());
  console.log(resp2);
  const parts: any = await get_part_descriptions(resp2);
  console.log(parts);

  const cpu_description = parts.data.find((x: any) => x[1] == "cpu")[4];
  const gpu_description = parts.data.find((x: any) => x[1] == "gpu")[4];
  const ram_description = parts.data.find((x: any) => x[1] == "ram")[4];
  const mobo_description = parts.data.find((x: any) => x[1] == "mobo")[4];
  const psu_description = parts.data.find((x: any) => x[1] == "psu")[4];
  const storage_description = parts.data.find((x: any) => x[1] == "storage")[4];
  const case_description = parts.data.find((x: any) => x[1] == "case")[4];

  const part_descriptions = {
    CPU: cpu_description,
    GPU: gpu_description,
    RAM: ram_description,
    MOBO: mobo_description,
    PSU: psu_description,
    STORAGE: storage_description,
    CASE: case_description,
  };

  return NextResponse.json(part_descriptions);
}

```

### app/api/search/route.tsx

```typescript
import axios from "axios";
import { NextRequest, NextResponse } from "next/server";
import parser from "@/../parser";
import insert_build from "@/../lib/db";
import get_part_descriptions from "../../../lib/mindsdb";

export async function POST(req: NextRequest) {
  const data: any = await req.json();
  const resp = await axios.post(
    "https://api.together.xyz/inference",
    {
      model: "thorchri@usc.edu/Llama-2-7B-32K-Instruct-2023-10-29-09-45-46",
      max_tokens: 1400,
      prompt:
        "You are an AI focused on generating lists of PC components based on given user requirements. Your task is to provide a list of recommended parts in a specific order. Your responses should be clear, concise, and informative with the full model name. Do not engage in regular conversation with the user. Always ensure that your recommendations are based on the information provided and are well-reasoned and with enough information. In general, try not to recommend the 3090 or 4090 unless the build budget is over $3000. Make sure the CPU includes the model e.g. Ryzen 5 or Intel i5. The format should be first the prompt, then cpu, gpu, ram, motherboard, powersupply, storage, and case, all separated by semicolon ; Like this: \nPrompt: Prompt ;<CPU> CPU; <GPU> GPU; <RAM> RAM; <MOBO> MOBO; <PSU> PSU; <STORAGE> STORAGE; <CASE> CASE \nUser Prompt: " +
        data.prompt +
        ": \n<CPU> ",
      request_type: "language-model-inference",
      temperature: 0.7,
      top_p: 0.7,
      top_k: 50,
      repetition_penalty: 1,
      stop: ["[INST]", "\n\n"],
      negative_prompt: "",
      sessionKey: "8819e382626ead35e0bde588f78a5fb8176952ec",
      update_at: "2023-10-29T12:42:47.612Z",
    },
    {
      headers: {
        Authorization: "Bearer 2d03281983b9a1e6704e17dd97fb77420fa3ce56bce2de2f089845639e27c96f",
      },
    }
  );
  if (resp.data) {
    console.log(resp.data);
    console.log(resp.data.output.choices[0].text);
    const option = resp.data.output.choices[0].text.trim();
    const out2 =
      "12900KF; <GPU> 4090; <RAM> 32GB; <MOBO> Z690 GAMING MAG; <PSU> Corsair RM850x (2020); <STORAGE> 2x 1TB M.2-2280 PCIE 4.0 X4 NVME SSD; <CASE> Corsair 4000D Airflow";
    const parsed = parser("<CPU> " + option);
    return NextResponse.json(parsed);
  }
}

```

### app/view/[_id]/page.tsx

```typescript
"use client";
import Part from "@/components/Part";
import Model from "@/components/models/pc";
import { Bounds, OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { useQuery } from "convex/react";
import React, { useState } from "react";
import { api } from "@/../convex/_generated/api";
import { Id } from "@/../convex/_generated/dataModel";
import { PartProps } from "@/build/page";

export default function Build({
  params,
}: {
  params: {
    _id: string;
  };
}) {
  console.log("idd",params._id);
  const build =  useQuery(api.myFunctions.getSingle, { _id: params._id as Id<"builds"> });
  console.log("build",build);
  return (
    <main className="flex min-h-screen h-full justify-center p-24 bg-primary w-full  relative">
      <div className="flex-1 flex-col flex relative w-[40rem] h-[40rem] items-center justify-center">
        <Canvas>
          <Bounds fit margin={1.4}>
            <ambientLight position={[-1, 1, -2]} intensity={2} />
            <Model></Model>
            <OrbitControls
              enablePan={false}
              enableZoom={false}
              enableRotate={true}
              autoRotate={false}
              maxPolarAngle={Math.PI / 2}
              minPolarAngle={-Math.PI / 4}
            />
          </Bounds>
        </Canvas>
        {/* <form action="" className="w-full flex flex-col items-center" onSubmit={save_to_convex}>
          <input
            type="text"
            placeholder="Name this build"
            className="rounded-xl w-1/2 px-5 py-2 my-6 h-16 text-secondary flex  justify-between items-center text-2xl outline ring-4 bg-white focus:outline-gray-400 outline-gray-200 shadow-lg shadow-cyan-700"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <button className=" shadow-md shadow-gray-500 px-10 py-4 font-extrabold rounded-xl text-2xl bg-slate-800 text-primary" type="submit">
            Save Build
          </button>
        </form> */}
      </div>

      <div className="w-full flex flex-1 flex-col items-center justify-between gap-4 h-full  p-4">
        {build&& build.parts.map((part: any) => {
          return <Part key={part.src} src={part.preview} model={part.title} type={part.type} price={part.price}></Part>;
        })}
      </div>
    </main>
  );
}

```

### app/api/dashboard/route.tsx

```typescript
import axios from "axios";
import { NextRequest, NextResponse } from "next/server";
import { writeFileSync } from "fs";

const headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
  accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9",
};
export async function POST(req: NextRequest) {
  const data: any = await req.json();
  console.log(data);

  const resp = await axios.get(data.url, {
    headers: {
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
      accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
      "accept-encoding": "gzip, deflate, br",
      "accept-language": "en-US,en;q=0.9",
    },
  });

  if (resp.status == 200) {
    const pics = /<source type="image\/webp" srcSet="(https:\/\/photos.zillowstatic.com\/fp\/\S+.webp)/.exec(resp.data)?.[1];
    console.log("pic:", pics);
    const region = /\\"streetAddress\\":\\"([^\\]+)\\"/.exec(resp.data)?.[1];
    const price = /\\"price\\":\\"([^\\]+)\\"/.exec(resp.data)?.[1];
    const yearBuilt = /\\"yearBuilt\\":(\d+),\\/.exec(resp.data)?.[1];
    const bathrooms = /\\"bathrooms\\":(\d+),\\/.exec(resp.data)?.[1];
    const bedrooms = /\\"bedrooms\\":(\d+),\\/.exec(resp.data)?.[1];
    const sqft = /\\"sqft\\":\\"([^\\]+)\\"/.exec(resp.data)?.[1];

    // console.log(yearBuilt);
    const parsed = { region, price, yearBuilt, bathrooms, bedrooms, sqft };
    writeFileSync("input.json", JSON.stringify(parsed));
    const pyth = await axios.get("http://127.0.0.1:8000/similarity");
    console.log(pyth.data);
    const parsedjson = JSON.parse(pyth.data);
    parsedjson.pic = pics;
    // const zillowfind = await axios.get("https://www.zillow.com/homedetails/" + parsedjson.output.address.replace(" ", "-"), {
    //   headers: {
    //     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
    //     accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    //     "accept-encoding": "gzip, deflate, br",
    //     "accept-language": "en-US,en;q=0.9",
    //   },
    // });
    // console.log(zillowfind.data);
    return NextResponse.json(parsedjson);
  } else {
    console.log(resp.data);
  }
  return NextResponse.json("yay");
}

export async function PUT(req: NextRequest) {
  const parsed = await req.json();
  console.log("req", parsed);

  writeFileSync("input.json", JSON.stringify(parsed));
  const pyth = await axios.get("http://127.0.0.1:8000/similarity");
  console.log(pyth.data);
  const parsedjson = JSON.parse(pyth.data);
  // parsedjson.pic = pics;
  return NextResponse.json(parsedjson);

  // const zillowfind = await axios.get("https://www.zillow.com/homes/" + parsedjson.output.address.replaceAll(" ", "-"), {
  //   headers: {
  //     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
  //     accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
  //     "accept-encoding": "gzip, deflate, br",
  //     "accept-language": "en-US,en;q=0.9",
  //   },
  // });
  // console.log(zillowfind.data);
}

```

[23 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]