Skip to content

Commit dea945c

Browse files
authored
Post hackathon improvements (#1)
* Add better error handling * Add footer with github link * improve readme about how to run * update footer padding
1 parent 5b1b8b2 commit dea945c

File tree

9 files changed

+191
-99
lines changed

9 files changed

+191
-99
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ This project takes an equation uses Chat GPT to do computations that the user sp
66

77
## How to Run
88

9-
### Devcontainers (Nodejs 18)
9+
### Prerequisites
1010

11-
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.
11+
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`.
1212

13-
Once inside the devcontainer, run the following to get the Next.JS site to run:
13+
```
14+
OPENAI_API_KEY=************************
15+
```
16+
17+
### Devcontainers/Codespaces (Nodejs 18)
18+
19+
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.
20+
21+
Once inside the devcontainer, run the following to open the project in a browser.
1422

1523
```
1624
npm i

components/Answer.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { ApiReturnSchema } from '@/types/apiTypes'
2+
import { useToast, Box, Text, Alert, AlertIcon, AlertDescription, AlertTitle, Button, Link } from '@chakra-ui/react'
3+
import { CopyIcon, ExternalLinkIcon } from '@chakra-ui/icons';
4+
import { Grid } from 'react-loading-icons'
5+
import dynamic from 'next/dynamic';
6+
import NextLink from 'next/link'
7+
8+
// shows the answer, or its loading state
9+
10+
const StaticMathField = dynamic(() => import('@/components/StaticMath'), {
11+
ssr: false,
12+
});
13+
14+
export type AnswerState = { tag: 'idle' } | { tag: 'loading' } | ApiReturnSchema
15+
16+
const ShowAnswer: React.FC<{ answer: AnswerState }> = ({ answer }) => {
17+
const toast = useToast()
18+
19+
if (answer.tag === 'idle') {
20+
return null
21+
}
22+
23+
if (answer.tag === 'loading') {
24+
const loadingTexts = [
25+
'Completing your homework',
26+
'Generating Latex',
27+
'Consulting the AI Singularity',
28+
'Solving the P vs NP problem',
29+
'Running superior wolfram alpha',
30+
'Doing super complex computations'
31+
]
32+
// https://stackoverflow.com/a/5915122
33+
const randText = loadingTexts[loadingTexts.length * Math.random() | 0]
34+
return <Box display='flex' alignItems='center' flexDirection='column' gap={2}>
35+
<Text fontSize='md'>{randText}...</Text>
36+
<Grid />
37+
</Box>
38+
}
39+
40+
if (answer.tag === 'error') {
41+
return <Alert status='error'>
42+
<AlertIcon />
43+
<Box>
44+
<AlertTitle>There was an error!</AlertTitle>
45+
<AlertDescription>
46+
<Text as='b'>Message:</Text> {answer.error}
47+
</AlertDescription>
48+
</Box>
49+
</Alert>
50+
}
51+
52+
if (answer.tag === 'openAIAPIError' && answer.data.statusText === 'Too Many Requests') {
53+
return <Alert status='error'>
54+
<AlertIcon />
55+
<Box>
56+
<AlertTitle>Rate Limit Reached!</AlertTitle>
57+
<AlertDescription>
58+
<Text>We're sorry, we've seemed to hit out maximum monthly spending limit for OpenAI's API (we're poor students!)</Text>
59+
If you're interested in this project, check out our
60+
<Link color='#c5aaff' href='https://devpost.com/software/math-gpt'> Hackathon's devpost <ExternalLinkIcon /></Link>,
61+
<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!
62+
</AlertDescription>
63+
</Box>
64+
</Alert>
65+
}
66+
67+
if (answer.tag === 'openAIAPIError') {
68+
return <Alert status='error'>
69+
<AlertIcon />
70+
<Box>
71+
<AlertTitle>OpenAI API Error</AlertTitle>
72+
<AlertDescription>
73+
<Text>Status code: {answer.data.status}</Text>
74+
<Text>Status text: {answer.data.statusText}</Text>
75+
</AlertDescription>
76+
</Box>
77+
</Alert>
78+
}
79+
80+
81+
const copyToClipboard = () => {
82+
navigator.clipboard.writeText(answer.promptReturn)
83+
toast({
84+
title: 'Copied to clipboard!',
85+
status: 'info',
86+
duration: 9000,
87+
isClosable: true,
88+
})
89+
}
90+
91+
return (
92+
<Box display='flex' alignItems='center' w='full' pt='4' pb='4'>
93+
<Box w='full' overflowX='auto' pt='4' pb='4' mr='2'>
94+
<StaticMathField src={answer.promptReturn} id='apiAnswer' />
95+
</Box>
96+
<Button onClick={() => copyToClipboard()}>
97+
<CopyIcon />
98+
</Button>
99+
</Box>
100+
)
101+
}
102+
103+
export default ShowAnswer;

components/Footer.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Container, Stack, ButtonGroup, IconButton, Text } from '@chakra-ui/react'
2+
import { FaGithub } from 'react-icons/fa'
3+
4+
5+
export default () => (
6+
<Container as="footer" role="contentinfo" pt={{ base: '12', md: '16' }}>
7+
<Stack spacing={{ base: '4', md: '5' }}>
8+
<Stack justify="space-between" direction="row" align="center">
9+
<Text fontSize="sm" color="subtle">
10+
MathGPT, built for Hackrithmitic 2 2023
11+
</Text>
12+
<ButtonGroup variant="ghost">
13+
<IconButton as="a" href="https://github.com/hackathon-group-301/mathgpt" aria-label="GitHub" icon={<FaGithub fontSize="1.25rem" />} />
14+
</ButtonGroup>
15+
</Stack>
16+
</Stack>
17+
</Container>
18+
)

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"openai": "^3.1.0",
2121
"react": "18.2.0",
2222
"react-dom": "18.2.0",
23+
"react-icons": "^4.7.1",
2324
"react-loading-icons": "^1.1.0",
2425
"react-math-view": "^1.3.2",
2526
"react18-mathquill": "^1.0.1",

pages/api/gpt3.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2-
import { ApiReturnSchema, ApiSchema } from '@/types/apiTypes';
2+
import { ApiReturnSchema, ApiSchema, OpenAIAPIError } from '@/types/apiTypes';
33
import type { NextApiRequest, NextApiResponse } from 'next'
44
import { Configuration, OpenAIApi } from 'openai';
55
import { z } from 'zod';
@@ -8,8 +8,6 @@ const configuration = new Configuration({
88
apiKey: process.env.OPENAI_API_KEY,
99
});
1010

11-
const openai = new OpenAIApi(configuration);
12-
1311

1412
const PROMPT_CONTEXT = `Make a math bot for school.
1513
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.
@@ -18,11 +16,6 @@ A: $$\\int x^{2}dx = \\frac{1}{3} x^3 + C$$
1816
Q: Solve the following: $$\\int_{1}^{3}x^{2}dx$$
1917
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}$$`
2018

21-
function sleep(ms: number) {
22-
return new Promise((resolve) => {
23-
setTimeout(resolve, ms);
24-
});
25-
}
2619

2720
export default async function handler(
2821
req: NextApiRequest,
@@ -53,7 +46,7 @@ export default async function handler(
5346
prompt: total_prompt,
5447
temperature: 0.3,
5548
max_tokens: 500
56-
});
49+
})
5750
console.log('Completion:', completion.data)
5851
const answer = completion.data.choices[0].text;
5952
if (!answer) {
@@ -67,8 +60,17 @@ export default async function handler(
6760
}
6861

6962
} catch (e) {
70-
// somehow, JSON.stringify() returns an empty object
71-
console.log('Error in chatGPT:', e, String(e))
72-
res.status(500).send({ tag: 'error', error: String(e) })
63+
const parsedError = OpenAIAPIError.safeParse(e);
64+
65+
if (!parsedError.success) {
66+
// unknown error
67+
console.log('Unknown error: cannot parse!', parsedError.error)
68+
return res.status(500).send({ tag: 'error', error: String(e) })
69+
} else {
70+
const { status, statusText } = parsedError.data.response;
71+
console.log('OpenAI API error:', status, statusText)
72+
return res.status(500).send({ tag: 'openAIAPIError', data: { status, statusText } })
73+
}
7374
}
7475
}
76+

pages/index.tsx

Lines changed: 15 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import Head from 'next/head'
2-
import Image from 'next/image'
3-
import { Inter } from '@next/font/google'
42
import styles from '@/styles/Home.module.css'
5-
import React, { useEffect, useState, useReducer } from 'react';
3+
import React, { useState, useReducer } from 'react';
64
import dynamic from 'next/dynamic';
75
import {
8-
Button, ButtonGroup, Select,
9-
Text, Card, Box, useToast,
10-
Alert, AlertTitle, AlertDescription, AlertIcon,
11-
Accordion, AccordionButton, AccordionItem, AccordionPanel, AccordionIcon,
12-
Input
6+
Button, Select, Text, Card, Box, useToast,
7+
Accordion, AccordionButton, AccordionItem, AccordionPanel, AccordionIcon, Input
138
} from '@chakra-ui/react'
14-
import { Grid } from 'react-loading-icons'
159
import { ApiReturnSchema } from '@/types/apiTypes';
16-
import { CopyIcon } from '@chakra-ui/icons';
10+
import ShowAnswer, { AnswerState } from '@/components/Answer';
11+
import Footer from '@/components/Footer';
12+
1713

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

34-
const StaticMathField = dynamic(() => import('@/components/StaticMath'), {
35-
ssr: false,
36-
});
37-
38-
type Answer = { tag: 'idle' } | { tag: 'loading' } | { tag: 'success', response: string } | { tag: 'error', error: string }
39-
40-
const ShowAnswer: React.FC<{ answer: Answer }> = ({ answer }) => {
41-
const toast = useToast()
42-
43-
if (answer.tag === 'idle') {
44-
return null
45-
}
46-
47-
if (answer.tag === 'loading') {
48-
const loadingTexts = [
49-
'Completing your homework',
50-
'Generating Latex',
51-
'Consulting the AI Singularity',
52-
'Solving the P vs NP problem',
53-
'Running superior wolfram alpha',
54-
'Doing super complex computations'
55-
]
56-
// https://stackoverflow.com/a/5915122
57-
const randText = loadingTexts[loadingTexts.length * Math.random() | 0]
58-
return <Box display='flex' alignItems='center' flexDirection='column' gap={2}>
59-
<Text fontSize='md'>{randText}...</Text>
60-
<Grid />
61-
</Box>
62-
}
63-
64-
if (answer.tag === 'error') {
65-
return <Alert status='error'>
66-
<AlertIcon />
67-
<Box>
68-
<AlertTitle>There was an error!</AlertTitle>
69-
<AlertDescription>
70-
<Text as='b'>Message:</Text> {answer.error}
71-
</AlertDescription>
72-
</Box>
73-
</Alert>
74-
}
75-
76-
const copyToClipboard = () => {
77-
navigator.clipboard.writeText(answer.response)
78-
toast({
79-
title: 'Copied to clipboard!',
80-
status: 'info',
81-
duration: 9000,
82-
isClosable: true,
83-
})
84-
}
85-
86-
return (
87-
<Box display='flex' alignItems='center' w='full' pt='4' pb='4'>
88-
<Box w='full' overflowX='auto' pt='4' pb='4' mr='2'>
89-
<StaticMathField src={answer.response} id='apiAnswer' />
90-
</Box>
91-
<Button onClick={() => copyToClipboard()}>
92-
<CopyIcon />
93-
</Button>
94-
</Box>
95-
)
96-
}
97-
9830
interface CustomPromptInputProps {
9931
dropdownData: DropdownData
10032
dropdownDispatch: React.Dispatch<DropdownAction>
@@ -155,7 +87,7 @@ type DropdownReducer = typeof dropdownReducer;
15587

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

@@ -194,15 +126,11 @@ export default function Home() {
194126
console.log('Got response', responseJson)
195127
const parsed = ApiReturnSchema.safeParse(responseJson)
196128
if (!parsed.success) {
129+
// if we used typc, we wouldn't be in this situation...
197130
setAnswer({ tag: 'error', error: 'Error parsing result: ' + parsed.error.toString() })
198131
} else {
199132
console.log('Got answer', parsed.data)
200-
if (parsed.data.tag === 'error') {
201-
setAnswer({ tag: 'error', error: parsed.data.error })
202-
return
203-
} else {
204-
setAnswer({ tag: 'success', response: parsed.data.promptReturn })
205-
}
133+
setAnswer(parsed.data);
206134
}
207135
} catch (e) {
208136
setAnswer({ tag: 'error', error: JSON.stringify(e) })
@@ -225,9 +153,12 @@ export default function Home() {
225153
borderWidth={'5px'}
226154
borderColor={'#0C1220'}
227155
gap={8}
228-
maxW='3xl'
156+
maxW='5xl'
229157
width='full'>
230-
<Text align="center" bgGradient='linear(to-l, #6931E0, #D41795)' bgClip='text' fontSize='70px' fontWeight='extrabold'>Math GPT</Text>
158+
<Box textAlign='center'>
159+
<Text align="center" bgGradient='linear(to-l, #6931E0, #D41795)' bgClip='text' fontSize='70px' fontWeight='extrabold'>Math GPT</Text>
160+
<Text fontSize='2xl' fontStyle='italic' align='center'>Solving Math with GPT-3</Text>
161+
</Box>
231162
<Select size='md'
232163
value={dropdownState.value}
233164
onChange={(e) => dropdownDispatch(e.target.value.toString() as Dropdown)}>
@@ -259,6 +190,7 @@ export default function Home() {
259190
<Button textColor={'white'} bgGradient='linear(to-r, #187D71, #151394)' colorScheme='teal' onClick={() => demo1()}>Try solving!</Button>
260191
<Button textColor={'white'} bgGradient='linear(to-r, #8D9C0E, #359600)' colorScheme='teal' onClick={() => demo2()}>Try finding x!</Button>
261192
</Card>
193+
<Footer />
262194
</main>
263195
</>
264196
)

tsconfig.json

100644100755
File mode changed.

0 commit comments

Comments
 (0)