# Project export: ON-Board

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Revolutionize US naturalization prep for immigrant seniors with our learning app. It gamifies learning, personalizes content, making study impactful and engaging for those often left to study alone.
- Devpost: https://devpost.com/software/on-board-8c5uht
- GitHub: https://github.com/evyshen/cedar
- Video: https://www.youtube.com/embed/zQzImWW8I70?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — blackdiamand (20 commits), nathanjpaek (3 commits), AffectionateCurry (3 commits)

## Devpost submission (written by the team)

### Inspiration

As children of immigrant parents, we have witnessed the many challenges that senior citizens face when studying for the naturalization exam. From seeing our grandparents and relatives struggle to memorize through paper flashcards and not having people to practice with due to the busyness of their children, we felt inspired to create an app that offers a fun way to help seniors to learn. While there exists a multitude of resources for gamified language learning or AI- powered SAT learning plans, seniors are *constantly left out * of this digital and educational revolution. Existing mobile applications that offer naturalization exam practices are bleakly designed and not interactive (only showing questions and solutions without an interactive way for users to keep track of their progress or truly engage with the correct answers). To this end, we hope to close the gap and offer more fun, direct and resourceful access for senior citizens to study for the naturalization exam without having to read the 40-page long tutorial on the Internet.

### What it does

Our app offers an interactive, AI-powered, and creative way of helping senior citizens familiarize themselves with the naturalization process and ace the exam. With ON-Board, naturalization is made easier, and more grandparents (whose children are busy at work) can find a community, learn about the history of the country, and more quickly become accepted to the country legally. Our app offers a customized learning and practicing experience for the naturalization exam. Users first put in their age and state, and depending on their responses, get a specific list of practice questions (citizens over 65 years old and have been in the US for more than 20 years only need to study for a subset of questions). On the home page, users can take a practice test resembling the actual exam (with 10 randomly selected questions), or tests in specific categories of government, integrated civic, and history. Users can access their past exam history as well. For each question, the user can interact with an AI-agent to get unlimited hints on the question. Once they answer the question, explanations of the correct and incorrect responses are also provided. Once the user finishes the practices, the results page offers additional resources and provides access to the incorrect answers.

### How we built it

We began by collecting the 100 naturalization exam questions and feeding them into GPT4 to get incorrect answers for each question to make the questions all multiple-choice-based. Then, we cleaned the data into json format and used React Native to build the app. We prompt-tuned GPT 3.5 to provide hints for each question, and after arriving at a optimal prompt, we created a function to make API calls to the model within the app.

### Challenges we ran into

With three of the team members having little/no experience in app development, we faced many difficulties installing the programming environment Expo. Simply setting up the environment took us several hours, but we were eventually able to push it through. We experienced difficulties with prompt engineering, creating the server, as well as using the React Native framework to call the GPT model continuously. Even after implementing the React Native code, we had trouble with how text was appearing on the app. However, through these difficulties, we believe we accomplished something we never thought we could have done before TreeHacks!

### Accomplishments we're proud of

We are very proud of how we were able to embed an "AI agent" inside our app. Traditionally, quiz-styled applications are hard-coded and do not offer an interactive interface where the users can directly interact with each question dynamically. Our app helps to resolve this issue by including a chatbot that users can interact with to have a better experience answering and understanding the questions.

### What we learned

With a team of 3 first-time hackathon participants and 1 beginner hackathon participant, we were very inspired by the power of teamwork. Each member made unique contributions and worked collaboratively to make the project happen. Kevin wrote the React Native code for the front-end. Evy created the JSON test bank of answers for 100 questions as well as designed the app and the task-flows using Figma. Nathan and Sokserey focused on prompt-tuning GPT and worked with Kevin to embed the model into the app.

### What's next

for On-Board Our main next focus will be including options for users of many different languages. We plan on implementing a community tab to help senior citizens find friends who are also preparing for the naturalization test. We also plan on conducting more research on what educational methods enhance learning for senior citizens, incorporating these strategies to our gamification / system design. We also plan on improving personalized dialogues and finding some way to include the writing and speaking portions of the naturalization test in our app as well.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 10 recognized source files, 81 KB.
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (13 of 13)

```
.gitignore
App.js
app.json
babel.config.js
Components/Dashboard.js
Components/Gpt.js
Components/Home.js
Components/Question.js
Components/QuestionButtons.js
Components/WrongQuestion.js
Contexts.js
naturalization_test_bank.js
package.json
```

### Dependencies

- package.json: @babel/core@^7.20.0, @expo-google-fonts/dev@^0.2.3, @expo-google-fonts/josefin-slab@^0.2.3, @react-native-async-storage/async-storage@1.21.0, @react-navigation/native@^6.1.10, @react-navigation/native-stack@^6.9.18, dotenv@^16.4.4, expo@~50.0.7, expo-app-loading@^2.1.1, expo-font@~11.10.3, expo-status-bar@~1.11.1, react@18.2.0, react-native@0.73.4, react-native-confetti-cannon@^1.5.2, react-native-elements@^3.4.3, react-native-gifted-chat@^2.4.0, react-native-safe-area-context@4.8.2, react-native-screens@~3.29.0

### Recent commits (newest first)

- Merge branch 'main' of https://github.com/blackdiamand/cedar
- gpt
- confetti
- Update .gitignore
- Merge branch 'main' of https://github.com/blackdiamand/cedar
- squash
- knkjn
- merge
- UI designer
- gpt again
- Merge branch 'main' of https://github.com/blackdiamand/cedar
- giant dict => categories
- Merge branch 'main' of https://github.com/blackdiamand/cedar
- testing gpt
- bank
- dash board
- wrong answers
- global context
- correct answer
- Giant test bank go!

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "cedar",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@expo-google-fonts/dev": "^0.2.3",
    "@expo-google-fonts/josefin-slab": "^0.2.3",
    "@react-native-async-storage/async-storage": "1.21.0",
    "@react-navigation/native": "^6.1.10",
    "@react-navigation/native-stack": "^6.9.18",
    "dotenv": "^16.4.4",
    "expo": "~50.0.7",
    "expo-app-loading": "^2.1.1",
    "expo-font": "~11.10.3",
    "expo-status-bar": "~1.11.1",
    "react": "18.2.0",
    "react-native": "0.73.4",
    "react-native-confetti-cannon": "^1.5.2",
    "react-native-elements": "^3.4.3",
    "react-native-gifted-chat": "^2.4.0",
    "react-native-safe-area-context": "4.8.2",
    "react-native-screens": "~3.29.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0"
  },
  "private": true
}

```

### App.js

```javascript
import { StatusBar } from 'expo-status-bar';
import {Button, StyleSheet, Text, View} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import HomeScreen from "./Components/Home";
import QuestionScreen from "./Components/Question";
import {createContext, useState} from "react";
import {GlobalContext, QuestionContext} from "./Contexts";
import {questionBank} from "./naturalization_test_bank";
import WrongQuestion from "./Components/WrongQuestion";
import Dashboard from "./Components/Dashboard";

const Stack = createNativeStackNavigator();

const App = () => {

  const [pointsContext, setPointsContext] = useState({score:0, wrongAnswers : [], bankState: "all", hint: ""});
  const [questionContext, setQuestionContext] = useState(questionBank[Math.floor(Math.random() * 200)]);

  return (
    <QuestionContext.Provider value={{questionContext:questionContext, setQuestionContext:setQuestionContext}}>
      <GlobalContext.Provider value={{pointsContext:pointsContext, setPointsContext:setPointsContext}}>
        <NavigationContainer>
          <Stack.Navigator screenOptions={{
            contentStyle:{
              backgroundColor:'#FBF9F1'
            }
          }}
            initialRouteName="Home">
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Dashboard" component={Dashboard} />
            <Stack.Screen name="Questions" component={QuestionScreen} />
            <Stack.Screen name="Wrong Questions" component={WrongQuestion} />
          </Stack.Navigator>
        </NavigationContainer>
      </GlobalContext.Provider>
    </QuestionContext.Provider>
  );
}

export default App;
```

### babel.config.js

```javascript
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
  };
};

```

### Contexts.js

```javascript
import {createContext, useState} from "react";
import {questionBank} from "./naturalization_test_bank";

export const QuestionContext = createContext(questionBank[Math.floor(Math.random() * 200)]);

export const GlobalContext = createContext({score: 0, wrongAnswers : [], bankState: "all", hint: ""});
```

### Components/Home.js

```javascript
import {Button, Image, Text, View} from "react-native";
import * as React from "react";
import {useContext} from "react";
import {GlobalContext} from "../Contexts";

import DefaultImage from '../assets/eagle.png';

const DEFAULT_IMAGE = Image.resolveAssetSource(DefaultImage).uri;
function HomeScreen({ navigation }) {
  const {pointsContext, setPointsContext} = useContext(GlobalContext);
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Image
        source={require('../assets/eagle.png')}
      />
      <Text style={{fontFamily: 'Roboto', fontSize: 50}}>On-Board</Text>
      <Button
        title="Dashboard"
        onPress={() => navigation.navigate('Dashboard')}
      />
    </View>
  );
}

export default HomeScreen;
```

### Components/WrongQuestion.js

```javascript
import {Button, Text, View} from "react-native";
import * as React from "react"
import {useContext} from "react";
import {QuestionContext, GlobalContext} from "../Contexts";
import {questionBank} from "../naturalization_test_bank";
import QuestionButtons from "./QuestionButtons";


function WrongQuestion({ navigation}) {
  const {questionContext, setQuestionContext} = useContext(QuestionContext);
  const {pointsContext, setPointsContext} = useContext(GlobalContext);
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{fontSize:30}}>Question: {questionContext['question']} </Text>
      <QuestionButtons question={questionContext} newQuestion = {pointsContext.wrongAnswers[Math.floor(Math.random() * pointsContext.wrongAnswers.length)]} />
      <Text style={{fontSize:25}}>Points: {pointsContext.score}</Text>
    </View>
  );
}

export default WrongQuestion;
```

### Components/QuestionButtons.js

```javascript
import {useContext} from "react";
import {GlobalContext, QuestionContext} from "../Contexts";
import {View} from "react-native";
import {Input, Button} from 'react-native-elements';
import * as React from "react";

function QuestionButtons({question, newQuestion}){
  const {pointsContext, setPointsContext} = useContext(GlobalContext);
  const {questionContext, setQuestionContext} = useContext(QuestionContext);
  const answersArray = question['incorrect_answers'];
  if (answersArray.length <= 3) {
    answersArray.splice(Math.floor(Math.random() * (question['incorrect_answers'].length + 1)), 0, question['correct_answer']);
  }
  function onCorrectAnswer(){

    let obj = pointsContext;
    obj.score = obj.score + 1;
    setPointsContext(obj);
    setQuestionContext(newQuestion);
  }
  function onWrongAnswer(){
    let obj = pointsContext;
    obj.wrongAnswers.push(question);
    setPointsContext(obj);
  }
  return (
    <View style = {{width: "80%", justifyContent: 'center', flex:1}}>
      {answersArray.map(answer =>
        <View style={{padding:10}} key={answer}>
          <Button
            style={{padding: 10}}
            titleStyle={{
              fontSize: 20,
            }}
            title={answer}
            onPress={() => answer === question['correct_answer'] ? onCorrectAnswer(): onWrongAnswer()}
          />
        </View>
      )}
    </View>
  );
}
export default QuestionButtons;
```

### Components/Dashboard.js

```javascript
import {useContext} from "react";
import {GlobalContext, QuestionContext} from "../Contexts";
import {Image, Text, View} from "react-native";
import {Button} from 'react-native-elements';
import QuestionButtons from "./QuestionButtons";
import {questionBank} from "../naturalization_test_bank";
import * as React from "react";

function Dashboard({ navigation}) {
  const {questionContext, setQuestionContext} = useContext(QuestionContext);
  const {pointsContext, setPointsContext} = useContext(GlobalContext);
  return (
    <View style={{ flex: 1}}>
      <Image
        style={{ alignSelf: 'center' }}
        source={require('../assets/whitehouse.png')}
      />
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="All Questions"
          onPress={() => {
              let obj = pointsContext;
              obj.bankState = "all";
              setPointsContext(obj);
              navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="Principles of American Democracy"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "Principles";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="System of Government"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "System of Government";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="Rights and Responsibilities"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "Rights and Responsibilities";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="Colonial Period History"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "Colonial Period History";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="History"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "History";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="Geography"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "Geography";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        <Button
          titleStyle={{
              fontSize: 15,
          }}
          title="Holidays"
          onPress={() => {
            let obj = pointsContext;
            obj.bankState = "Holidays";
            setPointsContext(obj);
            navigation.navigate('Questions')}}
        />
      </View>
      <View style={{padding:10}}>
        {pointsContext.wrongAnswers.length > 0 &&
          <Button
            titleStyle={{
                fontSize: 15,
            }}
            title="Review Wrong Questions"
            onPress={() => navigation.navigate('Wrong Questions')}
          />}
      </View>
    </View>
  );
}

export default Dashboard;
```

### Components/Question.js

```javascript
import {Button, Text, View, TextInput, StyleSheet} from "react-native";
import * as React from "react"
import {useState, useContext} from "react";
import {QuestionContext, GlobalContext} from "../Contexts";
import {questionBank} from "../naturalization_test_bank";
import QuestionButtons from "./QuestionButtons";
import ConfettiCannon from 'react-native-confetti-cannon';


function QuestionScreen({ navigation}) {


  const system_message = "You are a tutor helping non-native senior citizens prepare for the U.S. naturalization test. You will receive 3 forms of input: 1. A question from the naturalization test; 2. Four multiple choice answers to that question (where 3 are incorrect and 1 is correct; 3. The user's answer choice, which is either one of the four answers (A, B, C, or D) or NONE If the user's answer is correct given the question, congratulate them, explain why they are correct, and provide a few encouraging words. If the user's answer is incorrect given the question, politely express that they are not correct, provide a hint, and ask the user to try again. The hint should not be too obvious. If the user's answer is NONE, provide a not too obvious hint that will help them arrive at the answer, and DO NOT give them the correct answer. The user may then enter into a dialogue with you, asking you questions. You should answer their questions to guide them towards the right answer, without explicitly giving it to them. When mentioning the correct answer, do not give the letter of the correct answer, since the ordering of the questions could be different than the ordering given to you. Adjust the session based on their responses and offer encouragement. Keep responses limited to 25 words maximum."

  const [userInput, setUserInput] = useState('');
  const [hint, setHint] = useState('');


  const {questionContext, setQuestionContext} = useContext(QuestionContext);
  const {pointsContext, setPointsContext} = useContext(GlobalContext);
  let questionBankState = pointsContext.bankState;

  const handleUserInputChange = (text) => {
    setUserInput(text);
  };

  function bank() {
    if (questionBankState === "all") {
      return questionBank[Math.floor(Math.random() * 200)];
    }
    const map = new Map([["Principles" , "A: Principles of American Democracy"],
      ["System of Government", "B: System of Government"], ["Rights and Responsibilities", "C: Rights and Responsibilities"],
    ["Colonial Period History", "A: Colonial Period and Independence (American History)"], ["History", "Recent American History and Other Important Historical Information"],
      ["Geography", "Geography"], ["Holidays", "Holidays"]]);

    if (map.has(questionBankState)){
      let newBank = questionBank.filter((question) => question['category'] === map.get(questionBankState));
      return newBank[Math.floor(Math.random() * newBank.length)];
      //return questionBank[Math.floor(Math.random() * 200)];
    }
    else{
      return questionBank[Math.floor(Math.random() * 200)];
    }
  }

  const handleSubmit = async () => {
    if (userInput.trim() === '') {
      Alert.alert('Please enter your answer');
      return;
    }
    const currentQuestion = questionContext.question;
    const correctAnswer = questionContext.correct_answer;
    const incorrectAnswers = questionContext.incorrect_answers;
    const gptPrompt = `
    Question: ${currentQuestion}
    1) ${correctAnswer} (Correct Answer)
    2) ${incorrectAnswers[0]}
    3) ${incorrectAnswers[1]}
    4) ${incorrectAnswers[2]}
    Your answer: ${userInput || 'NONE'}
  `;
    //console.log(process.env.REACT_APP_API_KEY);

      const response = await fetch('https://api.openai.com/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'API_KEY',
        },
        body: JSON.stringify({
          model: "gpt-3.5-turbo",
          messages: [
          {"role": "system", "content": system_message},
          {"role": "user", "content": gptPrompt},
        ],
        }),
      }).then((response) => console.log(response.json().then((c) => {
        setHint(c.choices[0].message.content);
        console.log(c.choices[0].message.content);
        }
      )));
  };


  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', width: "100%"}}>
      <Text style={{fontSize:30}}>Question: {questionContext['question']} </Text>


      <TextInput
      
        onChangeText={text => setUserInput(text)}
        value={userInput}
        placeholder="What's your question?"
      />
      <Button
        title="Hint"
        onPress={handleSubmit}
      />
      <Text>{hint}</Text>

      <QuestionButtons question={questionContext} newQuestion = {bank()} />
      <Text style={{fontSize:25}}>Points: {pointsContext.score}</Text>
      {pointsContext.score > 0 && pointsContext.score % 10 === 0 && <ConfettiCannon count={200} origin={{x: -10, y: 0}} fadeOut={true}/>}
    </View>
  );
}

export default QuestionScreen;
```