diff --git a/src/components/Message.tsx b/src/components/Message.tsx new file mode 100644 index 0000000..66db58b --- /dev/null +++ b/src/components/Message.tsx @@ -0,0 +1,66 @@ +import React, { useState } from "react"; + +interface CodeBlockProps { + code: string; +} + +const CodeBlock: React.FC = ({ code }) => { + const [copyStatus, setCopyStatus] = useState<"idle" | "copied">("idle"); + + const handleCopyClick = () => { + navigator.clipboard.writeText(code).then(() => { + setCopyStatus("copied"); + + setTimeout(() => { + setCopyStatus("idle"); + }, 2000); + }); + }; + return ( +
+
+        {code}
+      
+ +
+ ); +}; + +interface MessageProps { + role: "user" | "system" | "assistant"; + content: string; +} + +const Message: React.FC = ({ role, content }) => { + const parts = content.split(/(```[\s\S]+?```)/g); + + return ( +
+
+ {parts.map((part, index) => { + if (part.startsWith("```")) { + const codeContent = part.slice(3, part.length - 3); + return ; + } else { + return {part}; + } + })} +
+
+ ); +}; + +export default Message; diff --git a/src/components/MessageInput.tsx b/src/components/MessageInput.tsx new file mode 100644 index 0000000..4515c47 --- /dev/null +++ b/src/components/MessageInput.tsx @@ -0,0 +1,41 @@ +import React, { useState } from "react"; + +interface MessageInputProps { + onSend: (message: string) => void; +} + +const MessageInput: React.FC = ({ onSend }) => { + const [input, setInput] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (input.trim() !== "") { + onSend(input); + setInput(""); + } + }; + + return ( +
+ setInput(e.target.value)} + className="w-full rounded-lg border px-4 py-2 focus:border-blue-300 focus:outline-none focus:ring" + placeholder="Type your message..." + maxLength={2000} + /> + +
+ ); +}; + +export default MessageInput; diff --git a/src/components/TypingIndicator.tsx b/src/components/TypingIndicator.tsx new file mode 100644 index 0000000..cb9d6df --- /dev/null +++ b/src/components/TypingIndicator.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +const TypingIndicator = () => { + return ( +
+
+
+
+
+
+
+ ); +}; + +export default TypingIndicator; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 513b41d..6bc8c9f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -14,7 +14,6 @@ const MyApp: AppType<{ session: Session | null }> = ({ }) => { return ( - ); diff --git a/src/pages/api/chatgpt.ts b/src/pages/api/chatgpt.ts index 5507548..07dd3c1 100644 --- a/src/pages/api/chatgpt.ts +++ b/src/pages/api/chatgpt.ts @@ -1,46 +1,42 @@ +import type { NextApiRequest, NextApiResponse } from "next"; import { Configuration, OpenAIApi } from "openai"; -import {topics} from '../../constants/topic'; const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); const openai = new OpenAIApi(configuration); -const constructPrompt = (topic: string, prompt='', language = 'javascript', level = 'junior', position = 'full-stack engineer') => { - switch(topic) { - case topics.EXPLAIN_CODE: - return `Explain this block of code: ${prompt}`; - case topics.FIX_CODE: - return `Is there anything wrong this block of code: ${prompt}`; - case topics.WRITE_CODE: - return `Write Some Code that does the following: ${prompt}`; - case topics.INTERVIEW_QUESTION: - return `Give me a list of interview questions for a ${level} ${position} developer.` - case topics.CODING_QUESTION: - return `Give me a list of ${level} coding questions in ${language}` - case topics.CODING_QUESTION_ANSWER: - return `Give me the answers to the following questions: ${prompt}` - default: - return prompt; +export default async function chatGpt( + req: NextApiRequest, + res: NextApiResponse +) { + const messages = req.body; + + if (!messages || !Array.isArray(messages)) { + res.status(400).json({ error: "Invalid request: messages should be an array" }); + return; + } + + const openaiMessages = messages.map((message) => ({ + role: message.role, + content: message.content, + })); + + try { + const completion = await openai.createChatCompletion({ + messages: openaiMessages, + temperature: 0.7, + max_tokens: 1500, + top_p: 1, + frequency_penalty: 0, + presence_penalty: 0, + model: "gpt-3.5-turbo", + }); + + const response = completion?.data?.choices[0]?.message?.content; + res.status(200).json({ role: "assistant", content: response }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "An error occurred while processing the request" }); } } -export default async function chatGpt(req, res) { - const { prompt, language, level, position, topic } = req.body; - const content = constructPrompt(topic, prompt, language, level, position); - const completion = await openai.createChatCompletion({ - messages: [ - { - role: "user", - content: content + '\n\n###\n\n', - } - ], - temperature: 0.7, - max_tokens: 256, - top_p: 1, - frequency_penalty: 0, - presence_penalty: 0, - model: "gpt-3.5-turbo", -}); - const response = completion?.data?.choices[0]?.message?.content; - res.status(200).json({ result: response }); -} \ No newline at end of file diff --git a/src/pages/assistiveintel.tsx b/src/pages/assistiveintel.tsx index 75f4262..eca92ca 100644 --- a/src/pages/assistiveintel.tsx +++ b/src/pages/assistiveintel.tsx @@ -1,147 +1,132 @@ -import React, { useState } from 'react' -import { TailSpin } from 'react-loader-spinner' -import Answer from '../components/Answer' -import Interview from '../components/Interview' -import Prompt from '../components/Prompt' -import Tabs from '../components/Tabs' -import { topics } from '../constants/topic' +import React, { useState, useEffect, useRef } from "react"; +import Message from "../components/Message"; +import MessageInput from "../components/MessageInput"; +import TypingIndicator from "../components/TypingIndicator"; -interface AnswerProps { - question: string - answer: string -} +const systemPrompts = { + TECH_QUESTION: `Hello! I'm an AI developer assistant. Feel free to ask me any technical questions, and I'll do my best to help you out.`, + EXPLAIN_CODE: `As a helpful assistant, I can help you understand blocks of code. Please provide the code you'd like me to explain, and I'll walk you through it.`, + FIX_CODE: `I'm here to help you fix errors in your code. Please share the problematic code block, and I'll analyze it for any issues or improvements.`, + WRITE_CODE: `I can write code to help you solve a specific problem. Please describe the problem, and I'll provide you with a solution in code.`, + INTERVIEW_QUESTION: `Welcome to our virtual interview! I'm a professional developer who will be asking you questions. To get started, please tell describe the role you're interviewing for. (you can paste the job description here)`, +}; + +const buttonGroupClasses = { + first: + "relative inline-flex items-center rounded-l-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-indigo-500 focus:z-10", + middle: + "relative -ml-px inline-flex items-center bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-indigo-500 focus:z-10", + last: "relative -ml-px inline-flex items-center rounded-r-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-indigo-500 focus:z-10", +}; -const selects = [ - { - label: 'What Position', - value: 'position', - options: [ - { label: 'Frontend', value: 'frontend' }, - { label: 'Backend', value: 'backend' }, - { label: 'Fullstack', value: 'fullstack' }, - { label: 'React Dev', value: 'react developer' }, - { label: 'Vuejs', value: 'vue.js developer' }, - { label: 'Angular', value: 'angular developer' }, - { label: 'Nodejs', value: 'node.js developer' }, - { label: 'Python', value: 'python developer' }, - { label: 'Java', value: 'java developer' }, - ], - }, - { - label: 'Experience level', - value: 'experience', - options: [ - { label: 'Junior', value: 'junior' }, - { label: 'Mid', value: 'mid' }, - { label: 'Senior', value: 'senior' }, - ], - }, -] +interface IMessage { + role: "user" | "system" | "assistant"; + content: string; +} -export default function AssistiveIntel() { - const tabs = ['Tech Question', 'Fix my code', 'Write my code', 'Explain my code', 'Interview'] - const [question, setQuestion] = useState('') - const [loading, setLoading] = useState(false) - const [answer, setAnswer] = useState([]) - const [tab, setTab] = useState('Tech Question') - const [selectOptions, setSelectOptions] = useState<{ [key: string]: string }[]>([{position: 'frontend'}, {experience: 'junior'}]) +const AssistiveIntel: React.FC = () => { + const [messages, setMessages] = useState>( + Object.keys(systemPrompts).reduce( + (acc, prompt) => ({ ...acc, [prompt]: [] }), + {} + ) + ); - const convertTabToTopic = (tab: string) => { - switch (tab) { - case 'Tech Question': - return topics.TECH_QUESTION - case 'Fix my code': - return topics.FIX_CODE - case 'Write my code': - return topics.WRITE_CODE - case 'Explain my code': - return topics.EXPLAIN_CODE - case 'Interview': - return topics.INTERVIEW_QUESTION - default: - return topics.TECH_QUESTION - } - } + const [loading, setLoading] = useState(false); + const [tab, setTab] = useState("TECH_QUESTION"); + const handleTabChange = (tab: string) => { + setTab(tab); + }; + const messagesEndRef = useRef(null); + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + useEffect(() => { + scrollToBottom(); + }, [messages, loading]); - const askQuestion = async () => { - setLoading(true) - const response = await fetch('/api/chatgpt', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ prompt: question, topic: convertTabToTopic(tab) }), - }) - const data = await response.json() - setAnswer([...answer, {question: question, answer: data.result}]) - setQuestion('') - setLoading(false) - } + const sendMessage = async (content: string) => { + // Create a new message object + const newMessage: IMessage = { role: "user", content }; + // Add the new message to the existing messages in state + setMessages((prevMessages) => ({ + ...prevMessages, + [tab]: [...prevMessages[tab], newMessage], + })); + // Set loading to true to display the loading indicator + setLoading(true); - const clearResponse = async () => { - setAnswer([]) - } + // Call the /api/chatgpt endpoint with the messages as the body + const response = await fetch("/api/chatgpt", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([ + { role: "system", content: systemPrompts[tab] }, + ...messages[tab], + newMessage, + ]), + }); + // Get the response from the /api/chatgpt endpoint + const data = await response.json(); - const changeTopic = (item: string) => { - setTab(item); - setAnswer([]); - } - const interviewRequest = async () => { - setLoading(true) - console.log('options', selectOptions) - const response = await fetch('/api/chatgpt', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({topic: convertTabToTopic(tab), position: selectOptions[0]?.position, level: selectOptions[1]?.experience }), - }) - const data = await response.json() - setAnswer([...answer, {question: question, answer: data.result}]) - setLoading(false) - } + // Set loading to false to stop displaying the loading indicator + setLoading(false); + // Add the response from the /api/chatgpt endpoint to the messages in state + setMessages((prevMessages) => ({ + ...prevMessages, + [tab]: [...prevMessages[tab], { role: data.role, content: data.content }], + })); + }; return ( -
-
-
-
-

- Assistive Intelligence -

-

- Allow Assistive Intelligence to be your AI assistant. Select a category and ask away. -

-
-
- -
- { - tab === 'Interview' ? - : - - } +
+
+

+ Assistive Intelligence +

+

+ Your virtual coding assistant, without the salary or benefits. +

+
+ {/* // This code maps through each assistant in systemPrompts and returns a button element for each one +// The button element has a key value of the assistant name, a className value that toggles between the first, middle, and last button styles, and an onClick value that calls handleTabChange with the assistant name as an argument +// The button text is the assistant name with underscores replaced with spaces */} + + {Object.keys(systemPrompts).map((assistant, index) => ( + + ))}
-
- {loading && ( -
- -
+
+ + {messages[tab].map( + (msg, index) => + msg.role !== "system" && ( + + ) )} - + + {loading && } +
+
- ) -} + ); +}; + +export default AssistiveIntel; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4c4e7d3..8a3b041 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,49 +4,22 @@ import Link from "next/link"; import { signIn, signOut, useSession } from "next-auth/react"; import { trpc } from "../utils/trpc"; +import AssistiveIntel from "./assistiveintel"; const Home: NextPage = () => { - const hello = trpc.example.hello.useQuery({ text: "from tRPC" }); - return ( <> - Create Stackosaurus App - + Assistive Intelligence + -