# Project export: dialogixAI

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 language learning with our AI-driven app: Engage in real conversations, receive instant feedback, and master languages naturally. Beyond rote, towards fluency.
- Devpost: https://devpost.com/software/dialogixai
- GitHub: https://github.com/cherylwu834/dialogixAI
- Demo: https://www.loom.com/share/0b783dc893924cd9b2ccb0c7bd231967?sid=a7b22f3b-5661-4b0f-944e-4d72690d3caf
- Result: winner (Wispr.ai: Best Voice Experience (4x Interview with Wispr founders and visit to Wispr HQ))
- Team: 3 GitHub contributor(s) — Bryan Wong (17 commits), Cheryl Wu (8 commits), anunayajoshi (3 commits)

## Devpost submission (written by the team)

### Inspiration

Our journey with language learning in class and on apps revealed their limitations—rote memorization and the absence of real-life conversations. Other language learners I met through Toastmasters Club, a public speaking club, echoed the same sentiment. Recognizing the importance of practical speaking skills and interactive learning, we set out to create DialogixAI, a platform that simulates conversational experiences with AI, making language learning more natural, engaging, and effective. What It Does DialogixAI bridges the gap between short phrases or words learned on other language platforms and real-life conversations by enabling users to engage in voice-based conversations with an AI. The AI bot listens, responds, and provides instant feedback on grammar and expression for the user. This real-time feedback enhances speaking skills and boosts confidence. How We Built It Leveraging cutting-edge AI technologies, including large language models (LLMs), DialogixAI processes user speech, generates contextually relevant responses, and evaluates language use for constructive feedback. We utilized open-source libraries and APIs, ensuring robust performance and accelerating development. Challenges We Ran Into Developing an AI that can handle the nuances of human language, including accents, slang, and idiomatic expressions, was challenging. Ensuring the AI's responses feel natural and engaging required intricate integration. Technical hurdles surfaced in seamlessly combining various AI components and ensuring global scalability and accessibility. Accomplishments That We're Proud Of We're proud of our clean UI and conquering the complexities of integrating the AI component into our web app. What We Learned This project deepened our understanding of AI's educational potential. We gained insights into language processing complexities and the importance of user experience design in educational technologies. The journey taught us teamwork, problem-solving, and the iterative design and development process. What's Next for DialogixAI Looking ahead, we aim to expand DialogixAI's capabilities to include more languages and dialects, making it accessible to a broader audience. We also plan to include an AI generated human avatar to simulate the interaction with a human in the future.

## README (from the GitHub repository)

# dialogixAI
Speak. Learn. Connect. Your AI Language Companion.

Built for language learners. You can talk to our AI chatbot in the language you're trying to learn, have a full conversation, and receive feedback on your grammar and sentence structure at the end.

**Video Demo:** https://www.youtube.com/watch?v=y3HZHwHdl6M

## How to run
To run the frontend:
```
npm run dev
```
The backend requires API keys to run.


## Detected evidence (automated analysis)

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

## Codebase structure (from repository index)

### Files (18 of 18)

```
dialogix-ai/.eslintrc.json
dialogix-ai/.gitignore
dialogix-ai/.prettierrc
dialogix-ai/.vscode/settings.json
dialogix-ai/app/globals.css
dialogix-ai/app/layout.js
dialogix-ai/app/lesson/chatbox.js
dialogix-ai/app/lesson/page.js
dialogix-ai/app/page.js
dialogix-ai/app/quiz/page.js
dialogix-ai/app/quiz/typeform.js
dialogix-ai/jsconfig.json
dialogix-ai/next.config.mjs
dialogix-ai/package.json
dialogix-ai/postcss.config.js
dialogix-ai/tailwind.config.js
LICENSE
README.md
```

### Dependencies

- dialogix-ai/package.json: autoprefixer@^10.0.1, eslint@^8, eslint-config-next@14.1.0, eslint-config-prettier@^9.1.0, next@14.1.0, postcss@^8, prettier@^3.2.5, prettier-plugin-tailwindcss@^0.5.11, react@^18, react-dom@^18, react-icons@^5.0.1, tailwindcss@^3.3.0, vocode@^1.0.37

### Recent commits (newest first)

- Update README.md
- Create LICENSE
- ui changes for lesson page
- remove vocode
- vocode
- Merge branch 'main' of https://github.com/cherylwu834/dialogixAI
- voice stuff
- change metadata
- Merge pull request #5 from cherylwu834/quiz-minor-changes
- made styling changes to the quiz
- Merge pull request #4 from cherylwu834/bryan/lesson-page
- draft frontend of lesson page
- Merge pull request #3 from cherylwu834/landing-page-frontend
- merge with main
- add button hover styling
- change to radio
- Merge pull request #2 from cherylwu834/bryan/style-quiz-page
- finalise quiz page style
- Basic HTML and styling for landing page
- outline of quiz page

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

### dialogix-ai/package.json

```
{
    "name": "dialogix-ai",
    "version": "0.1.0",
    "private": true,
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
    },
    "dependencies": {
        "next": "14.1.0",
        "react": "^18",
        "react-dom": "^18",
        "react-icons": "^5.0.1",
        "vocode": "^1.0.37"
    },
    "devDependencies": {
        "autoprefixer": "^10.0.1",
        "eslint": "^8",
        "eslint-config-next": "14.1.0",
        "eslint-config-prettier": "^9.1.0",
        "postcss": "^8",
        "prettier": "^3.2.5",
        "prettier-plugin-tailwindcss": "^0.5.11",
        "tailwindcss": "^3.3.0"
    }
}

```

### dialogix-ai/app/layout.js

```javascript
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
    title: 'dialogixAI',
}

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body className={inter.className}>{children}</body>
        </html>
    )
}

```

### dialogix-ai/app/page.js

```javascript
'use client';
import Image from "next/image";
import React from 'react';
import { useRouter } from 'next/navigation';

export default function Home() {
  const router = useRouter();

  const handleClick = () => {
    router.push('/quiz')
  };

  return (
    <main className="static font-[Helvetica_World] tracking-wide">
      <Image className="z-2 object-cover"
        src="/blob.svg"
        alt="Blue Element"
        fill={true}
      />
      <Image className="z-4 object-cover"
        src="/chatting.svg"
        alt="Blue Element"
        fill={true}
      />
      <Image className="z-4 fixed top-0 left-0 pt-2 pl-4 flex" //Kinda wonky
        src="/logo.svg"  
        alt="Logo"
        width = {100}
        height = {100}
      />
      <div className="z-6 fixed top-40 left-40 pt-20 pr-4 flex flex-col items-left justify-between">
        <div className="flex flex-row ">
          <h1 className="text-[#0063E2] text-6xl"> dialogix </h1> 
          <h1 className="text-[#0063E2] text-6xl font-bold"> AI </h1>
        </div>
        <h2 className="text-[#0063E2] text-3xl mt-2">
          Speak. Learn. Connect. 
        </h2>
        <p className="text-lg pt-8">
        Get feedback on your
        </p>
        <div className="flex flex-row text-lg">
          <p className="font-bold">pronunciation</p>
          <p>&nbsp;and&nbsp;</p>
          <p className="font-bold">expression</p>
        </div>
        <button onClick={handleClick} className="group transition duration-300 bg-[#0063E2] hover:bg-[#324781] text-lg text-white mt-10 p-2 rounded-md">
          Let's start {""}
          <span className="inline-block transition-transform group-hover:translate-x-3 motion-reduce:transform-none">
              -&gt;
          </span>
        </button>
        
      </div>

      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
      </div>
    </main>
  );
}

```

### dialogix-ai/app/quiz/page.js

```javascript
import Typeform from './typeform'

export default function Quiz() {
    return (
        <main>
            <Typeform />
        </main>
    )
}

```

### dialogix-ai/app/lesson/page.js

```javascript
'use client'

import { useState, useEffect } from 'react'
import ChatBox from './chatbox'
import { useConversation } from 'vocode'

export default function Lesson() {
    useEffect(() => {
        ;(async () => {
            await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
            let devices = await navigator.mediaDevices.enumerateDevices()
            console.log(devices)
        })()
        navigator.mediaDevices
            .enumerateDevices()
            .then((devices) => {
                console.log('devices', devices)
                setInputDevices(devices.filter((device) => device.deviceId && device.kind === 'audioinput'))
                setOutputDevices(devices.filter((device) => device.deviceId && device.kind === 'audiooutput'))
            })
            .catch((err) => {
                console.error(err)
            })
    }, [])

    const [audioDeviceConfig, setAudioDeviceConfig] = useState({})
    const [inputDevices, setInputDevices] = useState([])
    const [outputDevices, setOutputDevices] = useState([])
    const { status, start, stop, analyserNode, transcripts } = useConversation({
        backendUrl: 'ws://localhost:8086/conversation',
        subscribeTranscript: false,
        audioDeviceConfig,
    })

    const renderButton = () => {
        if (status === 'connected')
            return (
                <button
                    disabled={['connecting', 'error'].includes(status)}
                    onClick={stop}
                    className="rounded bg-[#00A651] px-4 py-2 text-white duration-300 ease-in-out hover:bg-[#33B864]"
                >
                    Stop talking
                </button>
            )
        return (
            <button
                disabled={['connecting', 'error'].includes(status)}
                onClick={status === 'connected' ? stop : start}
                className="rounded bg-blue px-4 py-2 text-white duration-300 ease-in-out hover:bg-[#2A61BB]"
            >
                Start talking
            </button>
        )
    }

    return (
        <div className="h-screen">
            <div className="flex h-full flex-col items-center justify-center">
                <div>{renderButton()}</div>
                {inputDevices.length != -1 && (
                    <select
                        name="input speaker"
                        onChange={(event) =>
                            setAudioDeviceConfig({
                                ...audioDeviceConfig,
                                inputDeviceId: event.target.value,
                            })
                        }
                        value={audioDeviceConfig.inputDeviceId}
                        className="mt-4 block w-full rounded-md border border-gray-300 bg-white p-2 text-blue shadow-sm focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue"
                    >
                        {inputDevices.map((device, i) => {
                            return (
                                <option key={i} value={device.deviceId}>
                                    {device.label}
                                </option>
                            )
                        })}
                    </select>
                )}
                {
                    <select
                        name="output speaker"
                        onChange={(event) =>
                            setAudioDeviceConfig({
                                ...audioDeviceConfig,
                                outputDeviceId: event.target.value,
                            })
                        }
                        value={audioDeviceConfig.outputDeviceId}
                        className="mt-4 block w-full rounded-md border border-gray-300 bg-white p-2 text-blue shadow-sm focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue"
                    >
                        {outputDevices.map((device, i) => {
                            return (
                                <option key={i} value={device.deviceId}>
                                    {device.label}
                                </option>
                            )
                        })}
                    </select>
                }
                {transcripts.length > 0 &&
                    transcripts.map((item, index) => {
                        return (
                            <div key={'t' + index.toString()} color="white">
                                {item.sender}: {item.text}
                            </div>
                        )
                    })}
            </div>
        </div>
    )
}

```

### dialogix-ai/postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

```

### dialogix-ai/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
    content: ['./pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}'],
    theme: {
        extend: {
            colors: {
                blue: '#0063E2',
                'hover-blue': '#337BFF',
            },
            backgroundImage: {
                'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
                'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
            },
        },
    },
    plugins: [],
}

```

### dialogix-ai/app/globals.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 255, 255, 255;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateX(-30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideOut {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(30px);
  }
}

.slideIn {
  animation-name: slideIn;
}

.slideOut {
  animation-name: slideOut;
}


```

### dialogix-ai/app/lesson/chatbox.js

```javascript
// components/ChatBox.js
const ChatBox = ({ messages, onSendMessage }) => {
    return (
        <div className="flex h-screen flex-col border-l">
            <div className="flex-none border-b bg-gray-100 p-4">
                {/* Header Content */}
                <div className="flex items-center">
                    <span className="text-md ml-2 font-semibold">Transcription</span>
                </div>
            </div>
            <div className="flex-grow overflow-auto p-4">
                {/* Messages List */}
                <ul>
                    {messages.map((message, index) => (
                        <li key={index} className={`mb-2 ${message.isOwn ? 'text-right' : 'text-left'}`}>
                            <div className={`inline-block rounded-lg px-4 py-2 ${message.isOwn ? 'bg-blue text-white' : 'bg-gray-300'}`}>
                                {message.text}
                            </div>
                        </li>
                    ))}
                </ul>
            </div>
            <div className="flex-none bg-gray-100 p-4">
                {/* Input Field */}
                <form
                    onSubmit={(event) => {
                        event.preventDefault()
                        const message = event.target.elements.message.value
                        onSendMessage(message)
                        event.target.elements.message.value = ''
                    }}
                    className="flex"
                >
                    <input
                        type="text"
                        name="message"
                        placeholder="Type your message..."
                        className="flex-grow rounded-l border p-2 focus:outline-none"
                    />
                    <button type="submit" className="bg-blue hover:bg-hover-blue rounded-r-lg px-4 py-2 text-white focus:outline-none">
                        Send
                    </button>
                </form>
            </div>
        </div>
    )
}

export default ChatBox

```

### dialogix-ai/app/quiz/typeform.js

```javascript
'use client'

import { useState } from 'react'
import { IoChevronBack, IoChevronForward } from 'react-icons/io5'
import { useRouter } from 'next/navigation'

const questions = [
    {
        id: 1,
        type: 'dropdown',
        question: 'Which language are you trying to learn?*',
        options: ['English', 'Chinese', 'French'],
    },
    {
        id: 2,
        type: 'checkbox',
        question: 'What would you like to talk about?*',
        options: ['Technology', 'Art', 'Science', 'Food'],
    },
]

export default function Form() {
    const router = useRouter()
    const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
    const [language, setLanguage] = useState('English')
    const [topic, setTopic] = useState('')
    const [transitionState, setTransitionState] = useState('slideIn')

    const handleNext = () => {
        setTransitionState('slideOut')
        // Wait for the animation to complete before changing the question.
        setTimeout(() => {
            setCurrentQuestionIndex(currentQuestionIndex + 1)
            setTransitionState('slideIn')
        }, 300) // This should match the animation-duration.
    }

    const handlePrevious = () => {
        setTransitionState('slideOut')
        // Wait for the animation to complete before changing the question.
        setTimeout(() => {
            setCurrentQuestionIndex((prev) => (prev > 0 ? prev - 1 : 0))
            setTransitionState('slideIn')
        }, 300) // This should match the animation-duration.
    }

    const handleLanguageChange = (event) => {
        setLanguage(event.target.value)
    }

    const handleTopicChange = (event) => {
        if (topic === event.target.value) {
            setTopic('')
            return
        }
        setTopic(event.target.value)
    }

    const handleSubmit = () => {
        router.push(`/lesson?language=${language.toLocaleLowerCase()}&topic=${topic.toLocaleLowerCase()}`)
    }

    const isInputEmpty = (input) => {
        return input == ''
    }

    const renderQuestionInput = (question) => {
        switch (question.type) {
            case 'dropdown':
                return (
                    <select
                        name={String(question.id)}
                        onChange={handleLanguageChange}
                        className="focus:ring-blue text-blue mt-4 block w-full rounded-md border border-gray-300 bg-white p-2 shadow-sm focus:border-transparent focus:outline-none focus:ring-2"
                        value={language}
                    >
                        <option disabled>Select your option</option>
                        {question.options.map((option) => (
                            <option key={option} value={option}>
                                {option}
                            </option>
                        ))}
                    </select>
                )
            case 'checkbox':
                return question.options.map((option) => (
                    <div
                        key={option}
                        className="border-blue mt-2 flex w-[190px] items-center rounded-md border border-solid bg-[#E7ECF7] p-2 hover:bg-[#B7C6E8] active:border-[#0445AF]"
                    >
                        <input
                            type="radio"
                            id={`${question.id}-${option}`}
                            name={String(question.id)}
                            value={option}
                            onChange={handleTopicChange}
                            className="text-blue focus:ring-blue h-4 w-4 rounded border-gray-300"
                        />
                        <label htmlFor={`${question.id}-${option}`} className="ml-2 block text-sm text-gray-900">
                            {option}
                        </label>
                        {/* {topic === option && <FaCheck className="ml-auto" color="#0445AF" />} */}
                    </div>
                ))
            default:
                return null
        }
    }

    return (
        <div className="flex min-h-screen flex-col items-center justify-center bg-white p-4">
            <div className={`flex items-center justify-center mt-auto w-full rounded-lg bg-white p-6 ${transitionState}`} style={{ animationDuration: '0.3s' }}>
                <div className="max-w-3xl">
                    <div className={'question pb-4 text-2xl font-normal items-center text-black'}>{questions[currentQuestionIndex].question}</div>
                    <form className="mb-8 mt-4">{renderQuestionInput(questions[currentQuestionIndex])}</form>
                    {currentQuestionIndex === questions.length - 1 && !isInputEmpty(topic) && (
                        <button
                            type="button"
                            onClick={handleSubmit}
                            className="bg-blue text-white rounded px-4 py-2 duration-300 ease-in-out hover:bg-[#2A61BB]"
                        >
                            Submit
                        </button>
                    )}
                </div>
            </div>
            <div className="mt-auto flex justify-end">
                <div className="bg-blue mb-4 rounded">
                    <button
                        type="button"
                        onClick={handlePrevious}
                        className="rounded-l border-r border-gray-200 px-4  py-2 duration-300 ease-in-out hover:bg-[#2A61BB]"
                        disabled={currentQuestionIndex === 0}
                    >
                        <IoChevronBack className="text-white " />
                    </button>
                    <button
                        type="button"
                        onClick={handleNext}
                        className="rounded-r px-4 py-2 duration-300 ease-in-out hover:bg-[#2A61BB]"
                        disabled={currentQuestionIndex === questions.length - 1}
                    >
                        <IoCh
[truncated — 134 more characters]
```