├── .env.example
├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── api
│ └── chat
│ │ └── route.ts
├── assets
│ ├── swiftiegpt.png
│ ├── taylors_version.png
│ └── ts-chatbot-bg.jpg
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx
├── components
├── Bubble.tsx
├── Footer.tsx
├── LoadingBubble.tsx
└── PromptSuggestions
│ ├── PromptSuggestionButton.tsx
│ └── PromptSuggestionsRow.tsx
├── gh-hero.png
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── scripts
└── loadDb.ts
├── tailwind.config.js
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | ASTRA_DB_APPLICATION_TOKEN=REPLACE_ME
2 | ASTRA_DB_API_ENDPOINT=REPLACE_ME
3 | ASTRA_DB_COLLECTION=swiftipedia
4 | OPENAI_API_KEY=REPLACE_ME
5 |
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "prettier"]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | wheels/
22 | share/python-wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .nox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *.cover
48 | *.py,cover
49 | .hypothesis/
50 | .pytest_cache/
51 | cover/
52 |
53 | # Translations
54 | *.mo
55 | *.pot
56 |
57 | # Django stuff:
58 | *.log
59 | local_settings.py
60 | db.sqlite3
61 | db.sqlite3-journal
62 |
63 | # Flask stuff:
64 | instance/
65 | .webassets-cache
66 |
67 | # Scrapy stuff:
68 | .scrapy
69 |
70 | # Sphinx documentation
71 | docs/_build/
72 |
73 | # PyBuilder
74 | .pybuilder/
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | # For a library or package, you might want to ignore these files since the code is
86 | # intended to run in multiple environments; otherwise, check them in:
87 | # .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # poetry
97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
98 | # This is especially recommended for binary packages to ensure reproducibility, and is more
99 | # commonly ignored for libraries.
100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
101 | #poetry.lock
102 |
103 | # pdm
104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
105 | #pdm.lock
106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
107 | # in version control.
108 | # https://pdm.fming.dev/#use-with-ide
109 | .pdm.toml
110 |
111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
112 | __pypackages__/
113 |
114 | # Celery stuff
115 | celerybeat-schedule
116 | celerybeat.pid
117 |
118 | # SageMath parsed files
119 | *.sage.py
120 |
121 | # Environments
122 | .env
123 | .venv
124 | env/
125 | venv/
126 | ENV/
127 | env.bak/
128 | venv.bak/
129 |
130 | # Spyder project settings
131 | .spyderproject
132 | .spyproject
133 |
134 | # Rope project settings
135 | .ropeproject
136 |
137 | # mkdocs documentation
138 | /site
139 |
140 | # mypy
141 | .mypy_cache/
142 | .dmypy.json
143 | dmypy.json
144 |
145 | # Pyre type checker
146 | .pyre/
147 |
148 | # pytype static type analyzer
149 | .pytype/
150 |
151 | # Cython debug symbols
152 | cython_debug/
153 |
154 | # PyCharm
155 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
156 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
157 | # and can be added to the global gitignore or merged into this file. For a more nuclear
158 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
159 | .idea/
160 |
161 | node_modules
162 | .next
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftieGPT
2 |
3 | 
4 |
5 | SwiftieGPT knows Taylor all too well. Ask questions in a clear and concise manner and get back responses based on details from publicly available data on Taylor. From tour dates to song lyrics, learn everything there is to know about the songstress here.
6 |
7 | ## Prerequisites
8 |
9 | - An Astra DB account. You can [create one here](https://astra.datastax.com/register)
10 | - An OpenAI account and api key [create one here](https://platform.openai.com/)
11 |
12 | ## Setup
13 |
14 | Clone this repository to your local machine.
15 |
16 | Install Puppeteer: `npm i puppeteer`. If you're on a newer Macbook, you'll need to run `PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM=1 npm i puppeteer`.
17 |
18 | Install the rest of the dependencies by running `npm install`.
19 |
20 | [Create a Vector Database](https://docs.datastax.com/en/astra/astra-db-vector/get-started/quickstart.html#create-a-serverless-vector-database) in Astra and generate and Application Token.
21 |
22 | Copy to supplied `.env.example` to `.env` and enter your credentials for OpenAI and AstraDB:
23 |
24 | - `OPENAI_API_KEY`: API key for OPENAI
25 | - `ASTRA_DB_API_ENDPOINT`: Your Astra DB vector database endpoint
26 | - `ASTRA_DB_APPLICATION_TOKEN`: The generated app token for your Astra database
27 |
28 |
29 | ## Load the data
30 |
31 | The first thing you need to to run this chatbot is to load the data. This may take awhile, so grab some coffee! ☕️
32 |
33 | `npx ts-node scripts/loadDb.ts`
34 |
35 | ## Running the Project
36 |
37 | Once the data is loaded, run `npm run dev` in your terminal. Open [http://localhost:3000](http://localhost:3000) to view the chatbot in your browser.
38 |
39 | ## More Resources
40 |
41 | - Astra DB [docs](https://docs.datastax.com/en/astra/astra-db-vector/index.html) and [quickstart](https://docs.datastax.com/en/astra/astra-db-vector/get-started/quickstart.html).
42 | - Astra DB TypeScript client [on Github](https://github.com/datastax/astra-db-ts)
43 | - SwiftieGPT how-to [blog post](https://www.datastax.com/blog/using-astradb-vector-to-build-taylor-swift-chatbot)
44 |
45 |
--------------------------------------------------------------------------------
/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import OpenAI from 'openai';
2 | import { OpenAIStream, StreamingTextResponse } from "ai";
3 | import { AstraDB } from "@datastax/astra-db-ts";
4 |
5 | const {
6 | ASTRA_DB_API_ENDPOINT,
7 | ASTRA_DB_APPLICATION_TOKEN,
8 | ASTRA_DB_COLLECTION,
9 | OPENAI_API_KEY,
10 | } = process.env;
11 |
12 | const openai = new OpenAI({
13 | apiKey: OPENAI_API_KEY,
14 | });
15 |
16 | const astraDb = new AstraDB(ASTRA_DB_APPLICATION_TOKEN, ASTRA_DB_API_ENDPOINT);
17 |
18 | export async function POST(req: Request) {
19 | try {
20 | const {messages} = await req.json();
21 | const latestMessage = messages[messages?.length - 1]?.content;
22 |
23 | let docContext = '';
24 |
25 | const embedding = await openai.embeddings.create({
26 | model: "text-embedding-3-small",
27 | input: latestMessage,
28 | encoding_format: "float",
29 | });
30 |
31 | try {
32 | const collection = await astraDb.collection(ASTRA_DB_COLLECTION);
33 | const cursor = collection.find(null, {
34 | sort: {
35 | $vector: embedding.data[0].embedding,
36 | },
37 | limit: 10,
38 | });
39 |
40 | const documents = await cursor.toArray();
41 |
42 | const docsMap = documents?.map(doc => doc.text);
43 |
44 | docContext = JSON.stringify(docsMap);
45 | } catch (e) {
46 | console.log("Error querying db...");
47 | docContext = "";
48 | }
49 |
50 | const Template = {
51 | role: 'system',
52 | content: `You are an AI assistant who is a Taylor Swift super fan. Use the below context to augement what you know about Taylor Swift and her music.
53 | The context will provide you with the most recent page data from her wikipedia, tour website and others.
54 | If the context doesn't include the information you need answer based on your existing knowledge and don't mention the source of your information or what the context does or doesn't include.
55 | Format responses using markdown where applicable and don't return images.
56 | ----------------
57 | START CONTEXT
58 | ${docContext}
59 | END CONTEXT
60 | ----------------
61 | QUESTION: ${latestMessage}
62 | ----------------
63 | `
64 | };
65 |
66 | const response = await openai.chat.completions.create(
67 | {
68 | model: 'gpt-4',
69 | stream: true,
70 | messages: [Template, ...messages],
71 | }
72 | );
73 | const stream = OpenAIStream(response);
74 |
75 | return new StreamingTextResponse(stream);
76 | } catch (e) {
77 | throw e;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/assets/swiftiegpt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastax/SwiftieGPT/7ca8f26fad1509213a27ddf050ec7d795d01b28c/app/assets/swiftiegpt.png
--------------------------------------------------------------------------------
/app/assets/taylors_version.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastax/SwiftieGPT/7ca8f26fad1509213a27ddf050ec7d795d01b28c/app/assets/taylors_version.png
--------------------------------------------------------------------------------
/app/assets/ts-chatbot-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastax/SwiftieGPT/7ca8f26fad1509213a27ddf050ec7d795d01b28c/app/assets/ts-chatbot-bg.jpg
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastax/SwiftieGPT/7ca8f26fad1509213a27ddf050ec7d795d01b28c/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --text-primary: #FFFFFF;
7 | --text-primary-inverse: #090909;
8 | --text-primary-main: #7724AA;
9 | --text-secondary: #A6AAAE;
10 | --text-secondary-inverse: #494A4D;
11 | --text-bubble-primary: #EB4039;
12 | --background-bubble-primary: #FFDEE0;
13 | --background-bubble-secondary: #F0F0F0;
14 | --border-primary: #CED0D2;
15 | --background-soft: #F3E5F5;
16 | --background-start-rgb: 214, 219, 220;
17 | --background-end-rgb: 255, 255, 255;
18 | --text-tertiary: #820DEB;
19 | --shadow-primary: 0px 6px 16px 0px #0A0A0A1F, 0px 4px 14px 0px #0A0A0A14, 0px 8px 10px 0px #0A0A0A14;
20 | --processing-dot-size: 4px;
21 | --header-footer-background: #DA92FC;
22 | --button-background: #FDFFAA;
23 | }
24 |
25 | body {
26 | background-image: url('assets/ts-chatbot-bg.jpg');
27 | background-size: cover;
28 | background-position: center;
29 | background-repeat: no-repeat;
30 | }
31 |
32 | .chatbot-section {
33 | background-color: var(--text-primary);
34 | box-shadow: var(--shadow-primary);
35 | /* border: 1px solid var(--border-primary); */
36 | }
37 |
38 | .chatbot-header {
39 | border-bottom: 1px solid var(--border-primary);;
40 | }
41 |
42 | .chatbot-header svg {
43 | fill: var(--text-primary-inverse);
44 | }
45 |
46 | .chatbot-text-primary {
47 | color: var(--text-primary-inverse);
48 | }
49 |
50 | .chatbot-text-secondary {
51 | color: var(--text-secondary);
52 | }
53 |
54 | .chatbot-text-secondary-inverse {
55 | color: var(--text-secondary-inverse);
56 | }
57 |
58 | .chatbot-text-tertiary {
59 | color: var(--text-tertiary);
60 | }
61 |
62 | .chatbot-input {
63 | color: var(--text-primary-inverse);
64 | background-color: var(--text-primary);
65 | /* border: 1px solid var(--border-primary); */
66 | }
67 |
68 | .chatbot-input::placeholder {
69 | color: var(--text-secondary);
70 | }
71 |
72 | .chatbot-send-button {
73 | background-color: var(--button-background);
74 | color: var(--text-primary-inverse);
75 | }
76 |
77 | .chatbot-send-button svg {
78 | fill: var(--text-primary);
79 | }
80 |
81 | .chatbot-button-primary {
82 | background-color: var(--text-primary-inverse);
83 | color: var(--text-primary);
84 | }
85 |
86 | .chatbot-button-secondary {
87 | border: 1px solid var(--border-primary);
88 | color: var(--text-primary-inverse);
89 | }
90 |
91 | .chatbot-faq-link {
92 | border: 1px solid var(--border-primary);
93 | border-radius: 24px;
94 | color: var(--background-bubble-primary);
95 | }
96 |
97 | .chatbot-faq-link svg {
98 | fill: var(--text-tertiary);
99 | }
100 |
101 | .talk-bubble {
102 | text-align: left;
103 | display: inline-block;
104 | position: relative;
105 | color: var(--text-secondary-inverse);
106 | background-color: var(--background-bubble-secondary);
107 | border-radius: 10px;
108 | border-bottom-left-radius: 0px;
109 | }
110 |
111 | .talk-bubble.user {
112 | text-align: right;
113 | color: var(--text-bubble-primary);
114 | background-color: var(--background-bubble-primary);
115 | border-radius: 10px;
116 | border-bottom-right-radius: 0px;
117 | }
118 |
119 | .talk-bubble svg {
120 | position: absolute;
121 | left: -1px;
122 | bottom: 2px;
123 | transform: translateY(100%) rotateY(180deg);
124 | fill: var(--background-bubble-secondary);
125 | }
126 |
127 | .talk-bubble.user svg {
128 | right: -1px;
129 | left: auto;
130 | bottom: 2px;
131 | transform: translateY(100%);
132 | fill: var(--background-bubble-primary);
133 | }
134 |
135 | .dot-flashing {
136 | position: relative;
137 | /* padding-left: 8px; */
138 | /* padding-right: 8px; */
139 | width: var(--processing-dot-size);
140 | height: var(--processing-dot-size);
141 | border-radius: 100%;
142 | background-color: var(--text-primary-inverse);
143 | color: var(--text-primary-inverse);
144 | animation: dot-flashing 1s infinite linear alternate;
145 | animation-delay: 0.5s;
146 | }
147 |
148 | .dot-flashing::before,
149 | .dot-flashing::after {
150 | content: "";
151 | display: inline-block;
152 | position: absolute;
153 | top: 0;
154 | }
155 |
156 | .dot-flashing::before {
157 | left: -6px;
158 | width: var(--processing-dot-size);
159 | height: var(--processing-dot-size);
160 | border-radius: 100%;
161 | background-color: var(--text-primary-inverse);
162 | color: var(--text-primary-inverse);
163 | animation: dot-flashing 1s infinite alternate;
164 | animation-delay: 0s;
165 | }
166 |
167 | .dot-flashing::after {
168 | left: 6px;
169 | width: var(--processing-dot-size);
170 | height: var(--processing-dot-size);
171 | border-radius: 100%;
172 | background-color: var(--text-primary-inverse);
173 | color: var(--text-primary-inverse);
174 | animation: dot-flashing 1s infinite alternate;
175 | animation-delay: 1s;
176 | }
177 |
178 | @keyframes dot-flashing {
179 | 0% {
180 | background-color: var(--text-primary-inverse);
181 | }
182 |
183 | 50%,
184 | 100% {
185 | background-color: rgba(152, 128, 255, 0.2);
186 | }
187 | }
188 |
189 | .prompt-button {
190 | background-color: var(--background-bubble-primary);
191 | color: var(--text-bubble-primary);
192 | }
193 |
194 | /* Toggle Styles */
195 | .toggle-background {
196 | background-color: var(--background-bubble-primary);
197 | }
198 |
199 | .toggle-boarder {
200 | border: 1px solid var(--background-bubble-primary);
201 | }
202 |
203 | .vercel-link {
204 | color: var(--text-primary-inverse);
205 | background-color: var(--text-primary);
206 | border-color: var(--border-primary);
207 | }
208 |
209 | .vercel-link hr {
210 | border-color: var(--border-primary);
211 | }
212 |
213 | .source-link {
214 | position: absolute;
215 | bottom: -2rem;
216 | right: 0;
217 | display: flex;
218 | align-items: center;
219 | }
220 |
221 | .source-link svg {
222 | position: static;
223 | transform: none;
224 | margin-right: 8px;
225 | }
226 |
227 | .source-link a {
228 | display: flex;
229 | align-items: center;
230 | color: var(--text-primary-main);
231 | border: 1px solid var(--border-primary);
232 | border-radius: 24px;
233 | padding: 0 8px;
234 | margin-left: 8px;
235 | }
236 |
237 | .link {
238 | color: var(--text-primary-main);
239 | }
240 |
241 | .active-chat-header {
242 | font-family: 'Brush Script MT', cursive;
243 | font-size: 24px;
244 | font-weight: 400;
245 | line-height: 36px;
246 | letter-spacing: 0em;
247 | text-align: right;
248 | }
249 |
250 | .header {
251 | overflow: hidden;
252 | margin: 0 -0.5rem 0 -0.5rem;
253 | }
254 |
255 | @media (min-width: 768px) {
256 | .header {
257 | overflow: hidden;
258 | margin: -1rem -1.5rem 0 -1.5rem;
259 | }
260 | }
261 |
262 | .clip-path-header {
263 | position: absolute;
264 | top: 0;
265 | right: 0;
266 | bottom: 0;
267 | left: 0;
268 | background: var(--header-footer-background);
269 | clip-path: polygon(0 0, 100% 0, 100% 100%, 0 70%);
270 | z-index: 0;
271 | }
272 |
273 | .footer {
274 | overflow: hidden;
275 | margin: 0 -0.5rem 0 -0.5rem;
276 | }
277 |
278 | @media (min-width: 768px) {
279 | .footer {
280 | overflow: hidden;
281 | margin: 0 -1.5rem -1rem -1.5rem;
282 | }
283 | }
284 |
285 | .clip-path-footer {
286 | position: absolute;
287 | top: 0;
288 | right: 0;
289 | bottom: 0;
290 | left: 0;
291 | background: var(--header-footer-background);
292 | clip-path: polygon(0 0, 100% 30%, 100% 100%, 0 100%);
293 | z-index: 0;
294 | }
295 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { GeistSans } from "geist/font/sans";
2 | import Script from "next/script";
3 | import "./globals.css";
4 |
5 | export const metadata = {
6 | title: `SwiftieGPT`,
7 | description: `Welcome to SwiftieGPT, your go-to chatbot for all things Taylor Swift! Whether you're curious about her latest songs, looking for fun facts, or just want to chat about Tay's amazing journey, I'm here to keep you in the Swift loop. Let's talk Taylor!`,
8 | };
9 |
10 | export default function RootLayout({ children }) {
11 | return (
12 |
13 |
14 |
23 |
{children}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {useEffect, useRef} from 'react';
3 | import Image from 'next/image';
4 | import tswiftImg from './assets/swiftiegpt.png';
5 | import Bubble from '../components/Bubble'
6 | import { useChat } from 'ai/react';
7 | import Footer from '../components/Footer';
8 | import PromptSuggestionRow from '../components/PromptSuggestions/PromptSuggestionsRow';
9 | import { Message } from 'ai';
10 | import LoadingBubble from '../components/LoadingBubble';
11 |
12 | export default function Home() {
13 | const { append, isLoading, messages, input, handleInputChange, handleSubmit } = useChat();
14 |
15 | const messagesEndRef = useRef(null);
16 |
17 | const scrollToBottom = () => {
18 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
19 | };
20 |
21 | useEffect(() => {
22 | scrollToBottom();
23 | }, [messages]);
24 |
25 |
26 | const handlePrompt = (promptText) => {
27 | const msg: Message = { id: crypto.randomUUID(), content: promptText, role: 'user' };
28 | append(msg);
29 | };
30 |
31 | return (
32 |
33 |
34 | {!messages || messages.length === 0 ? (
35 |
36 |
37 |
38 |
39 | SwiftieGPT knows Taylor all too well. Ask questions in a clear and concise manner and get back responses based on details from publicly available data on Taylor. From tour dates to song lyrics, learn everything there is to know about the songstress here.
40 |
41 | For Swifties, by Swifties.
42 |
43 |
44 |
45 |
46 | ): (
47 | <>
48 |
49 |
50 |
51 |
55 |
56 |
57 | {messages.map((message, index) => )}
58 | {isLoading && messages?.length % 2 !== 0 && }
59 |
60 |
61 | >
62 | )}
63 |
64 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
--------------------------------------------------------------------------------
/components/Bubble.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Link from "next/link";
3 | import {forwardRef, JSXElementConstructor, RefObject} from "react";
4 | import Markdown from "react-markdown";
5 | import remarkGfm from "remark-gfm";
6 |
7 | const Bubble:JSXElementConstructor = forwardRef(function Bubble({ content }, ref) {
8 | const { role } = content;
9 | const isUser = role === "user"
10 | const [hasSource, setHasSource] = useState(false);
11 |
12 | return (
13 | } className={`block mt-4 md:mt-6 pb-[7px] clear-both ${isUser ? 'float-right' : 'float-left'}`}>
14 |
15 |
16 |
23 | Source:
24 |
29 |
30 |
31 |
32 | {children}
33 |
34 |
35 | )
36 | },
37 | code({ node, children, ...props }) {
38 | return (
39 |
40 | {children}
41 |
42 | )
43 | }
44 | }}
45 | >
46 | {content?.content}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | )
55 | })
56 |
57 | export default Bubble;
--------------------------------------------------------------------------------
/components/Footer.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Footer = () => {
3 | const fillColor = "#820DEB";
4 |
5 | return (
6 |
7 |
8 |
Powered by
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default Footer;
43 |
--------------------------------------------------------------------------------
/components/LoadingBubble.tsx:
--------------------------------------------------------------------------------
1 | import {forwardRef, JSXElementConstructor, RefObject} from "react";
2 |
3 | const LoadingBubble:JSXElementConstructor = forwardRef(function LoadingBubble({}, ref) {
4 |
5 | return (
6 | } className={'block mt-4 md:mt-6 pb-[7px] clear-both float-left'}>
7 |
14 |
15 | )
16 | });
17 |
18 | export default LoadingBubble;
19 |
--------------------------------------------------------------------------------
/components/PromptSuggestions/PromptSuggestionButton.tsx:
--------------------------------------------------------------------------------
1 |
2 | const PromptSuggestionButton = ({ text, onClick }) => {
3 | return (
4 |
8 | {text}
9 |
10 | );
11 | };
12 |
13 | export default PromptSuggestionButton;
14 |
--------------------------------------------------------------------------------
/components/PromptSuggestions/PromptSuggestionsRow.tsx:
--------------------------------------------------------------------------------
1 | import PromptSuggestionButton from "./PromptSuggestionButton";
2 |
3 | const PromptSuggestionRow = ({ onPromptClick }) => {
4 | const prompts = [
5 | "How did Taylor become Time's Person of the Year? 🎤",
6 | "What is Taylor's net worth? 💰",
7 | "What were the surprise songs in Chicago? 🤯",
8 | "How many Grammy Awards has Taylor won? 🏆",
9 | ];
10 |
11 | return (
12 |
13 | {prompts.map((prompt, index) => (
14 |
onPromptClick(prompt)} />
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default PromptSuggestionRow;
21 |
--------------------------------------------------------------------------------
/gh-hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datastax/SwiftieGPT/7ca8f26fad1509213a27ddf050ec7d795d01b28c/gh-hero.png
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "swiftiegpt",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "seed": "ts-node ./scripts/loadDb.ts",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@datastax/astra-db-ts": "^0.0.27",
14 | "@types/node": "^20.8.10",
15 | "ai": "^2.2.25",
16 | "geist": "^1.1.0",
17 | "next": "14.0.1",
18 | "openai": "^4.20.0",
19 | "react": "^18",
20 | "react-dom": "^18",
21 | "react-markdown": "^9.0.0",
22 | "remark-gfm": "^4.0.0"
23 | },
24 | "devDependencies": {
25 | "@types/react": "^18.2.37",
26 | "autoprefixer": "^10",
27 | "dotenv": "^16.3.1",
28 | "eslint": "^8",
29 | "eslint-config-next": "13.5.5",
30 | "eslint-config-prettier": "^9.0.0",
31 | "langchain": "^0.1.34",
32 | "postcss": "^8",
33 | "puppeteer": "^22.6.5",
34 | "tailwindcss": "^3",
35 | "ts-node": "^10.9.1",
36 | "typescript": "5.2.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/loadDb.ts:
--------------------------------------------------------------------------------
1 | import { AstraDB } from "@datastax/astra-db-ts";
2 | import { PuppeteerWebBaseLoader } from "langchain/document_loaders/web/puppeteer";
3 | import OpenAI from "openai";
4 |
5 |
6 | import {RecursiveCharacterTextSplitter} from "langchain/text_splitter";
7 | import 'dotenv/config'
8 |
9 | type SimilarityMetric = "dot_product" | "cosine" | "euclidean";
10 |
11 | const {ASTRA_DB_COLLECTION, ASTRA_DB_APPLICATION_TOKEN, ASTRA_DB_API_ENDPOINT, OPENAI_API_KEY} = process.env;
12 |
13 | const openai = new OpenAI();
14 |
15 | const taylorData = [
16 | 'https://time.com/6342806/person-of-the-year-2023-taylor-swift/',
17 | 'https://en.wikipedia.org/wiki/Taylor_Swift',
18 | 'https://en.wikipedia.org/wiki/Taylor_Swift_albums_discography',
19 | 'https://www.taylorswift.com/tour/',
20 | 'https://taylorswift.tumblr.com/',
21 | 'https://www.forbes.com/profile/taylor-swift/?sh=242c42f818e2',
22 | 'https://taylorswiftstyle.com/',
23 | 'https://www.tstheerastourfilm.com/participating-territories/',
24 | 'https://www.cosmopolitan.com/entertainment/celebs/a29684699/taylor-swift-dating-boyfriend-history/E',
25 | 'https://www.tstheerastourfilm.com/participating-territories/',
26 | 'https://en.wikipedia.org/wiki/The_Eras_Tour',
27 | 'https://taylorswift.fandom.com/wiki/The_Eras_Tour',
28 | ];
29 |
30 | const astraDb = new AstraDB(ASTRA_DB_APPLICATION_TOKEN, ASTRA_DB_API_ENDPOINT);
31 |
32 | const splitter = new RecursiveCharacterTextSplitter({
33 | chunkSize: 512,
34 | chunkOverlap: 100,
35 | });
36 |
37 | const createCollection = async (similarityMetric: SimilarityMetric = 'dot_product') => {
38 | const res = await astraDb.createCollection(ASTRA_DB_COLLECTION, {
39 | vector: {
40 | dimension: 1536,
41 | metric: similarityMetric,
42 | }
43 | });
44 | console.log(res);
45 | };
46 |
47 | const loadSampleData = async (similarityMetric: SimilarityMetric = 'dot_product') => {
48 | const collection = await astraDb.collection(ASTRA_DB_COLLECTION);
49 | for await (const url of taylorData) {
50 | console.log(`Processing url ${url}`);
51 | const content = await scrapePage(url);
52 | const chunks = await splitter.splitText(content);
53 | for await (const chunk of chunks) {
54 | const embedding = await openai.embeddings.create({
55 | model: "text-embedding-3-small",
56 | input: chunk,
57 | encoding_format: "float",
58 | });
59 |
60 | const vector = embedding.data[0].embedding;
61 |
62 | const res = await collection.insertOne({
63 | $vector: vector,
64 | text: chunk
65 | });
66 | console.log(res)
67 | }
68 | }
69 | };
70 |
71 | const scrapePage = async (url: string) => {
72 | const loader = new PuppeteerWebBaseLoader(url, {
73 | launchOptions: {
74 | headless: true
75 | },
76 | gotoOptions: {
77 | waitUntil: "domcontentloaded",
78 | },
79 | evaluate: async (page, browser) => {
80 | const result = await page.evaluate(() => document.body.innerHTML);
81 | await browser.close();
82 | return result;
83 | },
84 | });
85 | return (await loader.scrape())?.replace(/<[^>]*>?/gm, '');
86 | };
87 |
88 | createCollection().then(() => loadSampleData());
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
6 | ],
7 | future: {
8 | hoverOnlyWhenSupported: true,
9 | },
10 | theme: {
11 | extend: {
12 | fontFamily: {
13 | sans: ["var(--font-geist-sans)"],
14 | },
15 | screens: {
16 | origin: "800px",
17 | },
18 | },
19 | },
20 | plugins: [],
21 | };
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": false,
11 | "noEmit": true,
12 | "incremental": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ]
24 | },
25 | "include": [
26 | "next-env.d.ts",
27 | ".next/types/**/*.ts",
28 | "**/*.ts",
29 | "**/*.tsx"
30 | ],
31 | "ts-node": {
32 | // these options are overrides used only by ts-node
33 | // same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
34 | "compilerOptions": {
35 | "module": "commonjs"
36 | }
37 | },
38 | "exclude": [
39 | "node_modules"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------