Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #20

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example

This file was deleted.

33 changes: 9 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
# [TwitterBio.com](https://www.twitterbio.com/)
# aitools
Generate your Twitter bio with OpenAI and Vercel Edge Functions.

This project generates Twitter bios for you using AI.
## cd to server, run npm i
## add a .env file to server and add DATABASE_URL=[postgresURL]

[![Twitter Bio Generator](./public/screenshot.png)](https://www.twitterbio.com)
## cd to client, add a .env file
OPENAI_API_KEY=[openai_key]
run npm i

## How it works

This project uses the [OpenAI GPT-3 API](https://openai.com/api/) (specifically, text-davinci-003) and [Vercel Edge functions](https://vercel.com/features/edge-functions) with streaming. It constructs a prompt based on the form and user input, sends it to the GPT-3 API via a Vercel Edge function, then streams the response back to the application.

Video and blog post coming soon on how to build apps with OpenAI and Vercel Edge functions!

## Running Locally

After cloning the repo, go to [OpenAI](https://beta.openai.com/account/api-keys) to make an account and put your API key in a file called `.env`.

Then, run the application in the command line and it will be available at `http://localhost:3000`.

```bash
npm run dev
```

## One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Nutlope/twitterbio&env=OPENAI_API_KEY&project-name=twitter-bio-generator&repo-name=twitterbio)
run client with npm run dev
run server with nodemon server.js
File renamed without changes.
27 changes: 27 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# [TwitterBio.com](https://www.twitterbio.com/)

This project generates Twitter bios for you using AI.

[![Twitter Bio Generator](./public/screenshot.png)](https://www.twitterbio.com)

## How it works

This project uses the [OpenAI GPT-3 API](https://openai.com/api/) (specifically, text-davinci-003) and [Vercel Edge functions](https://vercel.com/features/edge-functions) with streaming. It constructs a prompt based on the form and user input, sends it to the GPT-3 API via a Vercel Edge function, then streams the response back to the application.

Video and blog post coming soon on how to build apps with OpenAI and Vercel Edge functions!

## Running Locally

After cloning the repo, go to [OpenAI](https://beta.openai.com/account/api-keys) to make an account and put your API key in a file called `.env`.

Then, run the application in the command line and it will be available at `http://localhost:3000`.

```bash
npm run dev
```

## One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Nutlope/twitterbio&env=OPENAI_API_KEY&project-name=twitter-bio-generator&repo-name=twitterbio)
File renamed without changes.
113 changes: 113 additions & 0 deletions client/components/DropDownNew.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// interface DropDownProps {
// value: string | undefined;
// options: { value: string; label: string }[] | undefined;
// onChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
// }

// export default function DropDownNew({ value, options, onChange }: DropDownProps) {
// return (

// <select value={value} onChange={onChange}>
// {options && options.map((option) => (
// <option key={option.value} value={option.value}>
// {option.label}
// </option>
// ))}
// </select>
// );
// };


import { Menu, Transition } from "@headlessui/react";
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@heroicons/react/20/solid";
import { Fragment } from "react";

function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" ");
}

// type vibeType = "Professional" | "Casual" | "Funny";

// interface DropDownProps {
// vibe: "Professional" | "Casual" | "Funny";
// setVibe: (vibe: vibeType) => void;
// }

// let vibes: vibeType[] = ["Professional", "Casual", "Funny"];

interface FormData {
[key: string]: string | undefined;
}


interface DropDownProps {
value: string | undefined;
name: string;
options: { value: string; label: string }[] | undefined;
formData: FormData
setFormData: (newFormData: FormData) => void;
}

export default function DropDown({ value, name, options, formData, setFormData }: DropDownProps) {
return (
<Menu as="div" className="relative block text-left w-full">
<div>
<Menu.Button className="inline-flex w-full justify-between items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-black">
{value}
<ChevronUpIcon
className="-mr-1 ml-2 h-5 w-5 ui-open:hidden"
aria-hidden="true"
/>
<ChevronDownIcon
className="-mr-1 ml-2 h-5 w-5 hidden ui-open:block"
aria-hidden="true"
/>
</Menu.Button>
</div>

<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute left-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
key={value}
>
<div className="">
{options && options.map((option) => (
<Menu.Item>
{({ active }) => (
<button
onClick={() => { setFormData({ ...formData, [name]: option.value });
}}
className={classNames(
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
value === option.value ? "bg-gray-200" : "",
"px-4 py-2 text-sm w-full text-left flex items-center space-x-2 justify-between"
)}
>
<span>{option.value}</span>
{value === option.value ? (
<CheckIcon className="w-4 h-4 text-bold" />
) : null}
</button>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
);
}


File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
54 changes: 54 additions & 0 deletions client/components/home/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Link from "next/link";
import { ReactNode } from "react";
import ReactMarkdown from "react-markdown";
import Balancer from "react-wrap-balancer";

export default function Card({
displayName,
slug,
large,
}: {
displayName: string;
slug: string;
large?: boolean;
}) {
return (
<Link href={`/tool/${slug}`} passHref>
<div
className='w-full px-5 min-h-[200px] card flex items-center justify-center bg-white hover:gradient border border-gray-200 rounded-md shadow-lg py-7'
>
<div>
<h2 className="text-xl font-bold text-center text-transparent capitalize font-display bg-gradient-to-br from-black to-stone-500 bg-clip-text md:text-xl md:font-normal">
<Balancer>{displayName}</Balancer>
</h2>
{/* <div className="-mt-2 leading-normal prose-sm text-gray-500 md:prose">
<Balancer>
<ReactMarkdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
{...props}
className="font-medium text-gray-800 underline transition-colors"
/>
),
code: ({ node, ...props }) => (
<code
{...props}
// @ts-ignore (to fix "Received `true` for a non-boolean attribute `inline`." warning)
inline="true"
className="rounded-sm bg-gray-100 px-1 py-0.5 font-mono font-medium text-gray-800"
/>
),
}}
>
{description}
</ReactMarkdown>
</Balancer>
</div> */}
</div>
</div>
</Link>
);
}
55 changes: 55 additions & 0 deletions client/components/home/component-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from "react";
import { useDemoModal } from "../home/demo-modal";
import Popover from "../shared/popover";
import Tooltip from "../shared/tooltip";
import { ChevronDown } from "lucide-react";

export default function ComponentGrid() {
const { DemoModal, setShowDemoModal } = useDemoModal();
const [openPopover, setOpenPopover] = useState(false);
return (
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
<DemoModal />
<button
onClick={() => setShowDemoModal(true)}
className="flex items-center justify-center w-40 px-3 py-2 transition-all duration-75 border border-gray-300 rounded-md hover:border-gray-800 focus:outline-none active:bg-gray-100"
>
<p className="text-gray-600">Modal</p>
</button>
<Popover
content={
<div className="w-full p-2 bg-white rounded-md sm:w-40">
<button className="flex items-center justify-start w-full p-2 space-x-2 text-sm text-left transition-all duration-75 rounded-md hover:bg-gray-100 active:bg-gray-200">
Item 1
</button>
<button className="flex items-center justify-start w-full p-2 space-x-2 text-sm text-left transition-all duration-75 rounded-md hover:bg-gray-100 active:bg-gray-200">
Item 2
</button>
<button className="flex items-center justify-start w-full p-2 space-x-2 text-sm text-left transition-all duration-75 rounded-md hover:bg-gray-100 active:bg-gray-200">
Item 3
</button>
</div>
}
openPopover={openPopover}
setOpenPopover={setOpenPopover}
>
<button
onClick={() => setOpenPopover(!openPopover)}
className="flex items-center justify-between w-40 px-4 py-2 transition-all duration-75 border border-gray-300 rounded-md hover:border-gray-800 focus:outline-none active:bg-gray-100"
>
<p className="text-gray-600">Popover</p>
<ChevronDown
className={`h-4 w-4 text-gray-600 transition-all ${
openPopover ? "rotate-180" : ""
}`}
/>
</button>
</Popover>
<Tooltip content="Precedent is an opinionated collection of components, hooks, and utilities for your Next.js project.">
<div className="flex items-center justify-center w-40 px-3 py-2 transition-all duration-75 border border-gray-300 rounded-md cursor-default hover:border-gray-800 focus:outline-none active:bg-gray-100">
<p className="text-gray-600">Tooltip</p>
</div>
</Tooltip>
</div>
);
}
58 changes: 58 additions & 0 deletions client/components/home/demo-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Modal from "../shared/modal";
import {
useState,
Dispatch,
SetStateAction,
useCallback,
useMemo,
} from "react";
import Image from "next/image";

const DemoModal = ({
showDemoModal,
setShowDemoModal,
}: {
showDemoModal: boolean;
setShowDemoModal: Dispatch<SetStateAction<boolean>>;
}) => {
return (
<Modal showModal={showDemoModal} setShowModal={setShowDemoModal}>
<div className="w-full overflow-hidden md:max-w-md md:rounded-2xl md:border md:border-gray-100 md:shadow-xl">
<div className="flex flex-col items-center justify-center px-4 py-6 pt-8 space-y-3 text-center bg-white md:px-16">
<a href="https://precedent.dev">
<Image
src="/logo.png"
alt="Precedent Logo"
className="w-10 h-10 rounded-full"
width={20}
height={20}
/>
</a>
<h3 className="text-2xl font-bold font-display">Precedent</h3>
<p className="text-sm text-gray-500">
Precedent is an opinionated collection of components, hooks, and
utilities for your Next.js project.
</p>
</div>
</div>
</Modal>
);
};

export function useDemoModal() {
const [showDemoModal, setShowDemoModal] = useState(false);

const DemoModalCallback = useCallback(() => {
return (
<DemoModal
showDemoModal={showDemoModal}
setShowDemoModal={setShowDemoModal}
/>
);
}, [showDemoModal, setShowDemoModal]);

return useMemo(
() => ({ setShowDemoModal, DemoModal: DemoModalCallback }),
[setShowDemoModal, DemoModalCallback],
);
}
37 changes: 37 additions & 0 deletions client/components/home/web-vitals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { motion } from "framer-motion";
import CountingNumbers from "../shared/counting-numbers";

export default function WebVitals() {
return (
<div className="relative w-full h-full">
<motion.svg
className="absolute inset-0 m-auto"
viewBox="0 0 100 100"
width={140}
height={140}
>
<motion.circle
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
whileInView={{ pathLength: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.5, duration: 2, ease: "easeOut" }}
strokeWidth={7}
strokeDasharray="0 1"
strokeLinecap="round"
transform="rotate(-90 50 50)"
cx="50"
cy="50"
r="45"
fill="#DCFCE7"
stroke="#22C55E"
/>
</motion.svg>
<CountingNumbers
value={100}
duration={2500}
className="absolute inset-0 flex items-center justify-center mx-auto text-5xl text-green-500 font-display"
/>
</div>
);
}
Loading