-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c91747b
commit 441fa74
Showing
25 changed files
with
1,616 additions
and
283 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import mongoose, { Schema } from "mongoose"; | ||
|
||
mongoose.connect(process.env.MONGODB_URI); | ||
mongoose.Promise = global.Promise; | ||
|
||
const gamesSchema = new Schema({ | ||
AppID: String, | ||
Name: String, | ||
Releasedate: String, | ||
Price: String, | ||
Aboutthegame: { type: String, text: true }, | ||
Windows: String, | ||
Mac: String, | ||
Linux: String, | ||
Userscore: String, | ||
Positive: String, | ||
Negative: String, | ||
Developers: String, | ||
Publishers: String, | ||
Categories: String, | ||
Genres: String, | ||
Tags: String, | ||
}); | ||
|
||
const gamesDescriptionsSchema = new Schema({ | ||
Name: String, | ||
Description: { type: String, text: true }, | ||
Tags: String, | ||
}); | ||
|
||
const allgames = | ||
mongoose.models.allgames || mongoose.model("allgames", gamesSchema); | ||
|
||
const gamesDescriptions = | ||
mongoose.models.descriptions || | ||
mongoose.model("descriptions", gamesDescriptionsSchema); | ||
|
||
export { allgames, gamesDescriptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { allgames, gamesDescriptions } from "@/app/(models)/DB"; | ||
import { initialPrompt, afterQueryPrompt } from "@/app/utils/prompts"; | ||
import { gameWithNameQuery, noNameQuery } from "@/app/utils/queries"; | ||
import { Message, MessageArraySchema } from "@/app/lib/validators/message"; | ||
import { | ||
ChatGPTMessage, | ||
OpenAIStream, | ||
OpenAIStreamPayload, | ||
} from "@/app/lib/openai-stream"; | ||
|
||
async function getQuery(messages: ChatGPTMessage[]) { | ||
try { | ||
messages.unshift({ | ||
role: "system", | ||
content: initialPrompt, | ||
}); | ||
|
||
const res = await fetch(`${process.env.OPENAI_URL}`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, | ||
}, | ||
body: JSON.stringify({ | ||
model: "gpt-3.5-turbo", | ||
messages: messages, | ||
temperature: 0.1, | ||
top_p: 1, | ||
frequency_penalty: 0, | ||
presence_penalty: 0, | ||
n: 1, | ||
}), | ||
}); | ||
|
||
if (res.ok) { | ||
const data = await res.json(); | ||
const responseBody = data.choices[0].message.content; | ||
const parsedBody = JSON.parse(responseBody); | ||
if (!parsedBody) { | ||
return "No games found"; | ||
} | ||
let query = {}; | ||
let aggregationPipeline: any[] = []; | ||
if (parsedBody && parsedBody[0].name) { | ||
query = { Name: parsedBody[0].name }; | ||
const gameWithName = await gamesDescriptions.findOne({ | ||
Name: { $regex: new RegExp("^" + parsedBody[0].name + "$", "i") }, | ||
}); | ||
if (gameWithName && gameWithName.Tags) { | ||
const gameWithNameGenres = gameWithName.Tags.split(","); | ||
const gameWithNameAggregation = gameWithNameQuery(gameWithNameGenres); | ||
|
||
aggregationPipeline = gameWithNameAggregation; | ||
} else return "No games found"; | ||
} else { | ||
const noNameQueryArr = noNameQuery(parsedBody[0]); | ||
aggregationPipeline = noNameQueryArr; | ||
} | ||
|
||
const games = await gamesDescriptions.aggregate(aggregationPipeline); | ||
|
||
if (games) { | ||
return games; | ||
} else return "No games found"; | ||
} | ||
} catch (error) { | ||
return null; | ||
} | ||
} | ||
|
||
export async function POST(req: Request) { | ||
const { messages } = await req.json(); | ||
|
||
const parsedMessages = MessageArraySchema.parse(messages); | ||
|
||
const filteredMessages = parsedMessages.filter((message) => { | ||
if (message.text === "..." && !message.isUserMessage) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}); | ||
|
||
const outboundMessages: ChatGPTMessage[] = filteredMessages.map((message) => { | ||
return { | ||
role: message.isUserMessage ? "user" : "system", | ||
content: message.text, | ||
}; | ||
}); | ||
|
||
const initialMessageList = [...outboundMessages]; | ||
|
||
initialMessageList.reverse(); | ||
|
||
const initialRes = await getQuery([initialMessageList[0]]); | ||
|
||
if (!initialRes) { | ||
throw new Error("Something went wrong, please try again later"); | ||
} | ||
|
||
for (let i = outboundMessages.length - 1; i >= 0; i--) { | ||
if (outboundMessages[i].role === "system") { | ||
outboundMessages.splice(i, 1); | ||
} | ||
} | ||
|
||
const chatbotPrompt = afterQueryPrompt(initialRes); | ||
outboundMessages.unshift({ | ||
role: "system", | ||
content: chatbotPrompt, | ||
}); | ||
|
||
const payload: OpenAIStreamPayload = { | ||
model: "gpt-3.5-turbo", | ||
messages: outboundMessages, | ||
temperature: 0.4, | ||
top_p: 1, | ||
frequency_penalty: 0, | ||
presence_penalty: 0, | ||
stream: true, | ||
n: 1, | ||
}; | ||
|
||
const stream = await OpenAIStream(payload); | ||
|
||
return new Response(stream); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import React, { FC } from "react"; | ||
import ChatInput from "./chatInput"; | ||
import ChatMessages from "./chatMessages"; | ||
|
||
const Chat: FC = () => { | ||
return ( | ||
<div | ||
style={{ backgroundColor: "rgba(0, 0, 0, 0.2)" }} | ||
className="flex flex-grow flex-col w-[70%] h-full p-5 rounded-lg" | ||
> | ||
<ChatMessages className="px-2 py-3 flex-1" /> | ||
<ChatInput className="px-4" /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Chat; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
"use client"; | ||
import React, { FC, HTMLAttributes, useContext, useRef, useState } from "react"; | ||
import { cn } from "../utils/utils"; | ||
import TextareaAutosize from "react-textarea-autosize"; | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { nanoid } from "nanoid"; | ||
import { Message } from "../lib/validators/message"; | ||
import { MessagesContext } from "../context/messages"; | ||
import { Loader2, CornerDownLeft } from "lucide-react"; | ||
import { toast } from "react-hot-toast"; | ||
|
||
interface ChatInputProps extends HTMLAttributes<HTMLDivElement> {} | ||
|
||
const ChatInput: FC<ChatInputProps> = ({ className, ...props }) => { | ||
const [input, setInput] = useState<string>(""); | ||
const [inputFallback, setInputFallback] = useState<string>(""); | ||
const { | ||
messages, | ||
addMessage, | ||
removeMessage, | ||
updateMessage, | ||
isMessageUpdating, | ||
setIsMessageUpdating, | ||
} = useContext(MessagesContext); | ||
const textareaAutoRef = useRef<null | HTMLTextAreaElement>(null); | ||
|
||
const { mutate: sendMessage, isPending } = useMutation({ | ||
mutationFn: async (message: Message) => { | ||
const response = await fetch("/api/message", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ messages }), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(); | ||
} | ||
|
||
return response.body; | ||
}, | ||
onMutate(message) { | ||
addMessage(message); | ||
setInputFallback(input); | ||
setInput(""); | ||
}, | ||
onSuccess: async (stream) => { | ||
if (!stream) throw new Error("Something went wrong"); | ||
const id = nanoid(); | ||
const responseMessage: Message = { | ||
id, | ||
isUserMessage: false, | ||
text: "", | ||
}; | ||
addMessage(responseMessage); | ||
setIsMessageUpdating(true); | ||
|
||
const reader = stream.getReader(); | ||
const decoder = new TextDecoder(); | ||
let done = false; | ||
|
||
while (!done) { | ||
const { value, done: doneReading } = await reader.read(); | ||
done = doneReading; | ||
const chunkValue = decoder.decode(value); | ||
updateMessage(id, (prev) => prev + chunkValue); | ||
} | ||
|
||
setIsMessageUpdating(false); | ||
setTimeout(() => { | ||
textareaAutoRef.current?.focus(); | ||
}, 10); | ||
}, | ||
onError: (_, message) => { | ||
toast.error("Something went wrong. Please try again."); | ||
removeMessage(message.id); | ||
setInput(inputFallback); | ||
setIsMessageUpdating(false); | ||
setTimeout(() => { | ||
textareaAutoRef.current?.focus(); | ||
}, 10); | ||
}, | ||
}); | ||
|
||
return ( | ||
<div {...props} className={cn("border-t border-gray-500", className)}> | ||
<div className="relative mt-4 flex-1 overflow-hidden rounded-lg border-none outline-none"> | ||
<TextareaAutosize | ||
style={{ background: "rgba( 0, 0, 0, 0.2 )" }} | ||
ref={textareaAutoRef} | ||
rows={2} | ||
onKeyDown={(e) => { | ||
if (e.key === "Enter" && !e.shiftKey) { | ||
e.preventDefault(); | ||
|
||
const message: Message = { | ||
id: nanoid(), | ||
isUserMessage: true, | ||
text: input, | ||
}; | ||
|
||
sendMessage(message); | ||
} | ||
}} | ||
disabled={isPending} | ||
maxRows={4} | ||
value={input} | ||
onChange={(e) => setInput(e.target.value)} | ||
autoFocus | ||
placeholder="Write a message..." | ||
className="peer disabled:opacity-50 resize-none block w-full border-0 py-3 px-5 text-gray-200 focus:ring-0 text-sm sm:leading-6" | ||
/> | ||
|
||
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-1.5"> | ||
<kbd className="inline-flex items-center rounded border bg-[#2e4969] border-none px-1 font-sans text-xs text-gray-200"> | ||
{isPending || isMessageUpdating ? ( | ||
<Loader2 className="h-3 animate-spin" /> | ||
) : ( | ||
<CornerDownLeft className="h-3" /> | ||
)} | ||
</kbd> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ChatInput; |
Oops, something went wrong.