Skip to content
Merged
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
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ This project takes an equation uses Chat GPT to do computations that the user sp

## How to Run

### Devcontainers (Nodejs 18)
### Prerequisites

This project uses devcontainers to make it easy to run. If you have VSCode and a working installation of Docker, you can just open the project in a container and it will automatically install all the dependencies and run the project.
Create an Open AI account and get an API key. Then, create a file called `.env.local` in the root directory and add the key to the environment variable `OPENAI_API_KEY`.

Once inside the devcontainer, run the following to get the Next.JS site to run:
```
OPENAI_API_KEY=************************
```

### Devcontainers/Codespaces (Nodejs 18)

This project uses devcontainers to make it easy to run. If you have VSCode and a working installation of Docker, you can just open the project in a container with a full IDE configuration. You can also open it in a codespace on GitHub.

Once inside the devcontainer, run the following to open the project in a browser.

```
npm i
Expand Down
103 changes: 103 additions & 0 deletions components/Answer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ApiReturnSchema } from '@/types/apiTypes'
import { useToast, Box, Text, Alert, AlertIcon, AlertDescription, AlertTitle, Button, Link } from '@chakra-ui/react'
import { CopyIcon, ExternalLinkIcon } from '@chakra-ui/icons';
import { Grid } from 'react-loading-icons'
import dynamic from 'next/dynamic';
import NextLink from 'next/link'

// shows the answer, or its loading state

const StaticMathField = dynamic(() => import('@/components/StaticMath'), {
ssr: false,
});

export type AnswerState = { tag: 'idle' } | { tag: 'loading' } | ApiReturnSchema

const ShowAnswer: React.FC<{ answer: AnswerState }> = ({ answer }) => {
const toast = useToast()

if (answer.tag === 'idle') {
return null
}

if (answer.tag === 'loading') {
const loadingTexts = [
'Completing your homework',
'Generating Latex',
'Consulting the AI Singularity',
'Solving the P vs NP problem',
'Running superior wolfram alpha',
'Doing super complex computations'
]
// https://stackoverflow.com/a/5915122
const randText = loadingTexts[loadingTexts.length * Math.random() | 0]
return <Box display='flex' alignItems='center' flexDirection='column' gap={2}>
<Text fontSize='md'>{randText}...</Text>
<Grid />
</Box>
}

if (answer.tag === 'error') {
return <Alert status='error'>
<AlertIcon />
<Box>
<AlertTitle>There was an error!</AlertTitle>
<AlertDescription>
<Text as='b'>Message:</Text> {answer.error}
</AlertDescription>
</Box>
</Alert>
}

if (answer.tag === 'openAIAPIError' && answer.data.statusText === 'Too Many Requests') {
return <Alert status='error'>
<AlertIcon />
<Box>
<AlertTitle>Rate Limit Reached!</AlertTitle>
<AlertDescription>
<Text>We're sorry, we've seemed to hit out maximum monthly spending limit for OpenAI's API (we're poor students!)</Text>
If you're interested in this project, check out our
<Link color='#c5aaff' href='https://devpost.com/software/math-gpt'> Hackathon's devpost <ExternalLinkIcon /></Link>,
<Link as={NextLink} color='#c5aaff' href='https://github.com/hackathon-group-301/mathgpt#how-to-run' isExternal> Run this yourself <ExternalLinkIcon /> </Link> or get in contact with us!
</AlertDescription>
</Box>
</Alert>
}

if (answer.tag === 'openAIAPIError') {
return <Alert status='error'>
<AlertIcon />
<Box>
<AlertTitle>OpenAI API Error</AlertTitle>
<AlertDescription>
<Text>Status code: {answer.data.status}</Text>
<Text>Status text: {answer.data.statusText}</Text>
</AlertDescription>
</Box>
</Alert>
}


const copyToClipboard = () => {
navigator.clipboard.writeText(answer.promptReturn)
toast({
title: 'Copied to clipboard!',
status: 'info',
duration: 9000,
isClosable: true,
})
}

return (
<Box display='flex' alignItems='center' w='full' pt='4' pb='4'>
<Box w='full' overflowX='auto' pt='4' pb='4' mr='2'>
<StaticMathField src={answer.promptReturn} id='apiAnswer' />
</Box>
<Button onClick={() => copyToClipboard()}>
<CopyIcon />
</Button>
</Box>
)
}

export default ShowAnswer;
18 changes: 18 additions & 0 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Container, Stack, ButtonGroup, IconButton, Text } from '@chakra-ui/react'
import { FaGithub } from 'react-icons/fa'


export default () => (
<Container as="footer" role="contentinfo" pt={{ base: '12', md: '16' }}>
<Stack spacing={{ base: '4', md: '5' }}>
<Stack justify="space-between" direction="row" align="center">
<Text fontSize="sm" color="subtle">
MathGPT, built for Hackrithmitic 2 2023
</Text>
<ButtonGroup variant="ghost">
<IconButton as="a" href="https://github.com/hackathon-group-301/mathgpt" aria-label="GitHub" icon={<FaGithub fontSize="1.25rem" />} />
</ButtonGroup>
</Stack>
</Stack>
</Container>
)
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"openai": "^3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.7.1",
"react-loading-icons": "^1.1.0",
"react-math-view": "^1.3.2",
"react18-mathquill": "^1.0.1",
Expand Down
26 changes: 14 additions & 12 deletions pages/api/gpt3.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { ApiReturnSchema, ApiSchema } from '@/types/apiTypes';
import { ApiReturnSchema, ApiSchema, OpenAIAPIError } from '@/types/apiTypes';
import type { NextApiRequest, NextApiResponse } from 'next'
import { Configuration, OpenAIApi } from 'openai';
import { z } from 'zod';
Expand All @@ -8,8 +8,6 @@ const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});

const openai = new OpenAIApi(configuration);


const PROMPT_CONTEXT = `Make a math bot for school.
This bot helps with schoolwork by taking in a mathematical problem and solving it, outputting the intermediate steps as well. Mathematical symbols in the input and outputs, as well as the steps, are all done in LaTeX.
Expand All @@ -18,11 +16,6 @@ A: $$\\int x^{2}dx = \\frac{1}{3} x^3 + C$$
Q: Solve the following: $$\\int_{1}^{3}x^{2}dx$$
A: $$\\int_{1}^{2}x^{2} = \\left[ \\frac{1}{2} x^3 \\right]_{1}^{2} = \\frac{2^3}{3} - \\frac{1^3}{3} = \\frac{7}{3}$$`

function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

export default async function handler(
req: NextApiRequest,
Expand Down Expand Up @@ -53,7 +46,7 @@ export default async function handler(
prompt: total_prompt,
temperature: 0.3,
max_tokens: 500
});
})
console.log('Completion:', completion.data)
const answer = completion.data.choices[0].text;
if (!answer) {
Expand All @@ -67,8 +60,17 @@ export default async function handler(
}

} catch (e) {
// somehow, JSON.stringify() returns an empty object
console.log('Error in chatGPT:', e, String(e))
res.status(500).send({ tag: 'error', error: String(e) })
const parsedError = OpenAIAPIError.safeParse(e);

if (!parsedError.success) {
// unknown error
console.log('Unknown error: cannot parse!', parsedError.error)
return res.status(500).send({ tag: 'error', error: String(e) })
} else {
const { status, statusText } = parsedError.data.response;
console.log('OpenAI API error:', status, statusText)
return res.status(500).send({ tag: 'openAIAPIError', data: { status, statusText } })
}
}
}

98 changes: 15 additions & 83 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import Head from 'next/head'
import Image from 'next/image'
import { Inter } from '@next/font/google'
import styles from '@/styles/Home.module.css'
import React, { useEffect, useState, useReducer } from 'react';
import React, { useState, useReducer } from 'react';
import dynamic from 'next/dynamic';
import {
Button, ButtonGroup, Select,
Text, Card, Box, useToast,
Alert, AlertTitle, AlertDescription, AlertIcon,
Accordion, AccordionButton, AccordionItem, AccordionPanel, AccordionIcon,
Input
Button, Select, Text, Card, Box, useToast,
Accordion, AccordionButton, AccordionItem, AccordionPanel, AccordionIcon, Input
} from '@chakra-ui/react'
import { Grid } from 'react-loading-icons'
import { ApiReturnSchema } from '@/types/apiTypes';
import { CopyIcon } from '@chakra-ui/icons';
import ShowAnswer, { AnswerState } from '@/components/Answer';
import Footer from '@/components/Footer';


const EquationInput: React.FC<{ setLatex: React.Dispatch<React.SetStateAction<string>>, latex: string }> = ({ setLatex, latex }) => {
return (
Expand All @@ -31,70 +27,6 @@ const DynamicMathField = dynamic(() => import('react18-mathquill').then(mod => {
ssr: false,
});

const StaticMathField = dynamic(() => import('@/components/StaticMath'), {
ssr: false,
});

type Answer = { tag: 'idle' } | { tag: 'loading' } | { tag: 'success', response: string } | { tag: 'error', error: string }

const ShowAnswer: React.FC<{ answer: Answer }> = ({ answer }) => {
const toast = useToast()

if (answer.tag === 'idle') {
return null
}

if (answer.tag === 'loading') {
const loadingTexts = [
'Completing your homework',
'Generating Latex',
'Consulting the AI Singularity',
'Solving the P vs NP problem',
'Running superior wolfram alpha',
'Doing super complex computations'
]
// https://stackoverflow.com/a/5915122
const randText = loadingTexts[loadingTexts.length * Math.random() | 0]
return <Box display='flex' alignItems='center' flexDirection='column' gap={2}>
<Text fontSize='md'>{randText}...</Text>
<Grid />
</Box>
}

if (answer.tag === 'error') {
return <Alert status='error'>
<AlertIcon />
<Box>
<AlertTitle>There was an error!</AlertTitle>
<AlertDescription>
<Text as='b'>Message:</Text> {answer.error}
</AlertDescription>
</Box>
</Alert>
}

const copyToClipboard = () => {
navigator.clipboard.writeText(answer.response)
toast({
title: 'Copied to clipboard!',
status: 'info',
duration: 9000,
isClosable: true,
})
}

return (
<Box display='flex' alignItems='center' w='full' pt='4' pb='4'>
<Box w='full' overflowX='auto' pt='4' pb='4' mr='2'>
<StaticMathField src={answer.response} id='apiAnswer' />
</Box>
<Button onClick={() => copyToClipboard()}>
<CopyIcon />
</Button>
</Box>
)
}

interface CustomPromptInputProps {
dropdownData: DropdownData
dropdownDispatch: React.Dispatch<DropdownAction>
Expand Down Expand Up @@ -155,7 +87,7 @@ type DropdownReducer = typeof dropdownReducer;

export default function Home() {
const [latex, setLatex] = useState('\\frac{1}{\\sqrt{2}}\\cdot 2')
const [answer, setAnswer] = useState<Answer>({ tag: 'idle' })
const [answer, setAnswer] = useState<AnswerState>({ tag: 'idle' })
const [dropdownState, dropdownDispatch] = useReducer<DropdownReducer>(dropdownReducer, { value: 'Solve', customPrompt: '' })
const toast = useToast()

Expand Down Expand Up @@ -194,15 +126,11 @@ export default function Home() {
console.log('Got response', responseJson)
const parsed = ApiReturnSchema.safeParse(responseJson)
if (!parsed.success) {
// if we used typc, we wouldn't be in this situation...
setAnswer({ tag: 'error', error: 'Error parsing result: ' + parsed.error.toString() })
} else {
console.log('Got answer', parsed.data)
if (parsed.data.tag === 'error') {
setAnswer({ tag: 'error', error: parsed.data.error })
return
} else {
setAnswer({ tag: 'success', response: parsed.data.promptReturn })
}
setAnswer(parsed.data);
}
} catch (e) {
setAnswer({ tag: 'error', error: JSON.stringify(e) })
Expand All @@ -225,9 +153,12 @@ export default function Home() {
borderWidth={'5px'}
borderColor={'#0C1220'}
gap={8}
maxW='3xl'
maxW='5xl'
width='full'>
<Text align="center" bgGradient='linear(to-l, #6931E0, #D41795)' bgClip='text' fontSize='70px' fontWeight='extrabold'>Math GPT</Text>
<Box textAlign='center'>
<Text align="center" bgGradient='linear(to-l, #6931E0, #D41795)' bgClip='text' fontSize='70px' fontWeight='extrabold'>Math GPT</Text>
<Text fontSize='2xl' fontStyle='italic' align='center'>Solving Math with GPT-3</Text>
</Box>
<Select size='md'
value={dropdownState.value}
onChange={(e) => dropdownDispatch(e.target.value.toString() as Dropdown)}>
Expand Down Expand Up @@ -259,6 +190,7 @@ export default function Home() {
<Button textColor={'white'} bgGradient='linear(to-r, #187D71, #151394)' colorScheme='teal' onClick={() => demo1()}>Try solving!</Button>
<Button textColor={'white'} bgGradient='linear(to-r, #8D9C0E, #359600)' colorScheme='teal' onClick={() => demo2()}>Try finding x!</Button>
</Card>
<Footer />
</main>
</>
)
Expand Down
Empty file modified tsconfig.json
100644 → 100755
Empty file.
Loading