# Project export: TalkThru

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 2025
- Tagline: Practice interviews & difficult conversations.
- Devpost: https://devpost.com/software/talkthru
- GitHub: https://github.com/Andrea-MiramonSerr/tree-hacks.git
- Video: https://www.youtube.com/embed/9GqcxNZc7lU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Nelson Ooi (7 commits), am2389@cornell.edu (6 commits)

## Devpost submission (written by the team)

### Overview

Technical interviews, presentations, salary negotiations, sharing bad news, thesis defenses … what do all these have in common? You need to practice. You need feedback. TalkThru is the professional you need for on-demand, face-to-face practice. Technical or behavioral, emotional or cold: test your speeches, knowledge & improvisation with immediate corrections. You provide a topic to the agent. You provide the agent a role. The agent holds a conversation with you and summarizes what you should improve on at the end. It’s that easy. 5 steps: Program initialization: User provides role & theme for the interview. Query: a) Share the prompt with Perplexity to generate an introduction, b) generate a fitting video with LumaLabs. ElevenLabs: translate Perplexity's text-based response to speech & begin the conversation. Speech2Text: The user's response to the agent's question is translated to text (using python speech_recognition). Re-query: Unless the user wants to end the conversation, repeat steps 2-4. Speech-to-text: pause identification, fast responses, storing & processing audio real-time were challenging aspects of the speech integration. Speech-to-text: pause identification, fast responses, storing & processing audio real-time were challenging aspects of the speech integration. Lip-syncing: realistic interview preparation requires mouth movement. Whereas LumaLabs provides vastly creative features, it does not support facial expressions. Our first feature release will enable lip syncing so that users can carry realistic conversations. Lip-syncing: realistic interview preparation requires mouth movement. Whereas LumaLabs provides vastly creative features, it does not support facial expressions. Our first feature release will enable lip syncing so that users can carry realistic conversations. CUDA integration: lip-syncing requires torch access, CUDA-based GPUs & many pre-trained models. Thanks to our sponsor NVIDIA, we were able to develop this feature. CUDA integration: lip-syncing requires torch access, CUDA-based GPUs & many pre-trained models. Thanks to our sponsor NVIDIA, we were able to develop this feature. Markdown & React.js fans try flask for the first time. GenAI is actually pretty fast. Run LLMs on CUDA! Run LLMs on CUDA! End-to-end integration of LLM queries for on-demand user requests. End-to-end integration of LLM queries for on-demand user requests. Successful project scoping Successful project scoping There is so much ground to cover: [Sun 2/16] Simultaneous conversations [Sun 2/23] Interview Summary & Grading [Sun 2/23] Interview Summary & Grading [Sun 2/23] Programming interviews [Sun 3/2] Design questions uploading Sketches & Block diagrams [Sun 3/9] User Assesment - free product campaign [Sun 3/16] Enhance sentimentality [ Difficult Conversations]

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 12 recognized source files, 34 KB.
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (17 of 17)

```
.DS_Store
.vscode/launch.json
app.py
eltest.py
lumatest.py
main.py
perplexitytest.py
speechrecog.py
static/.DS_Store
static/audio/.DS_Store
static/js/main.js
templates/.DS_Store
templates/about.html
templates/index.html
templates/result.html
tester.py
texttosspeech.py
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Working version
- Ported over functionality to flask app
- Record conversations in webapp
- prompt engineering
- Can have conversation with thing
- Merge pull request #1 from Andrea-MiramonSerr/feature/perplexity-tester
- Confirmed follow-up responses & implemented memory. Confirming memory needs for continuous calling of the ask_perplexity function. This will require limiting history/summarizing. Ready to merge to main.
- Implemented citations
- Speech recognition
- Enabled debugger testing. Memory confirmed. Next: role & streamed responses (streamed gives error).
- Docstring added
- Perplexity MVP: responds to user - HTTS request based. Streamed responses not used yet.
- Added Luma and Perplexity tests

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

### main.py

```python
from tester import ask_perplexity, insert_perplexity
from speechrecog import get_speech
from texttosspeech import tts, play_audio, save_audio

live_convo = True
user_turn = 0
status = ['Not initialized', 'User speaking', 'Tester speaking']
company = 'Big Tech Co.'
interview_topic = 'Electrical Engineering'
job = 'Junior VLSI designer'
# tailor_responses = 'Please also rate on a scale of 1-10 the satisfactoriness of the answer and reply with just the number.'
request_summary = 'Please summarize the prior discussion. Tell me how I could have improved my interview answers, then we can move on to behavioral questions.'
added_text_prompt = ''
n_behavioral = 2
n_end = 5
iteration = 0


def parse_reply(reply):
    out = ''.join([i for i in reply if (i.isalpha() or i.isspace() or i in [',', '.', '?', '!', "'", "-", ";", "+", "=", "*"])])
    return out

while (live_convo):
    # if (iteration == n_rounds):
    #     response = insert_perplexity('Please proceed to the next stage of the interview.')
    #     # response = insert_perplexity('Please begin the behavioral interview stage. Ask questions that reveal how the candidate acts in difficult situations.')
    #     print(response)
    if (user_turn == 0):
        user_turn = 1
        print('asking perplexity')
        insert_perplexity("Your role is an interviewer at {}. Please initiate the interview by introducing yourself, and telling the candidate the structure of the interview. Wait for the candidate confirmation before asking technical questions. The topic is {}. The candidate is interviewing for job {}. Do not apologize unnecessarily!".format(company, interview_topic, job))
        # reply, citations = response
        # reply = parse_reply(reply)
        # print(reply)
    user_spoken = ''
    if (user_turn == 1):
        print(status[user_turn])
        user_spoken = get_speech()
        print(user_spoken)
        if (user_spoken != ''):
            user_turn = -1 * user_turn
    if(user_turn == -1):
        print(status[user_turn])
        response = ask_perplexity(user_spoken + added_text_prompt)
        # print(response)
        try:
            reply, citations = response
            reply = parse_reply(reply)
            print(reply)
            # agent_spoken = tts(reply)
            # play_audio(agent_spoken)
        except ValueError:
            print(response)
        user_turn = -1 * user_turn

    if (iteration == n_behavioral):
        added_text_prompt = request_summary


    iteration += 1



```

### app.py

```python
from flask import Flask, render_template, request, url_for, jsonify, redirect, send_from_directory
from tester import ask_perplexity, insert_perplexity
from texttosspeech import tts, save_audio, play_audio
import speech_recognition as sr
import os
from pydub import AudioSegment
import logging
# import soundfile
# import wave

# Initialize the Flask application
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'static/audio'
ALLOWED_EXTENSIONS = {'wav'}
AUDIO_DIRECTORY = 'static/audio'

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def convert_audio_to_wav(file_path):
    """Convert the audio file to a standard PCM WAV format."""
    sound = AudioSegment.from_file(file_path)
    wav_path = file_path.rsplit('.', 1)[0] + '.wav'
    # Ensure mono channel, 16-bit, and 16kHz sample rate
    sound = sound.set_channels(1).set_frame_rate(16000).set_sample_width(2)
    sound.export(wav_path, format='wav')
    return wav_path

def transcribe_audio(file_path):
    # data, samplerate = soundfile.read(file_path)
    # new_filename = os.path.join(app.config['UPLOAD_FOLDER'], 'audio.wav')
    # soundfile.write(new_filename, data, samplerate, subtype='PCM_16')
    recognizer = sr.Recognizer()
    recognizer.energy_threshold = 386
    # ChatGPT version
    file_path = convert_audio_to_wav(file_path)
    with sr.AudioFile(file_path) as source:
        audio = recognizer.record(source)
        try:
            text = recognizer.recognize_google(audio)
    # with wave.open(file_path, 'rb') as wf:
    #     audio_data = wf.readframes(wf.getnframes())
    #     audio = sr.AudioData(audio_data, wf.getframerate(), wf.getsampwidth())

    #     try:
    #         text = recognizer.recognize_google(audio)
            return text, 0
        except sr.UnknownValueError:
            return "Could not understand audio", 1
        except sr.RequestError as e:
            return f"Could not request results; {e}", 1

def parse_reply(reply):
    out = ''.join([i for i in reply if (i.isalpha() or i.isspace() or i in [',', '.', '?', '!', "'", "-", ";", "+", "=", "*"])])
    return out

# Define the home route with a form
@app.route('/', methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        name = request.form['name']
        message = request.form['message']
        return render_template('result.html', name=name, message=message)
    return render_template('index.html')

@app.route('/ask', methods=['POST'])
def ask():
    if 'file' not in request.files:
        return jsonify({'response': 'No file part'}), 400

    file = request.files['file']
    if file.filename == '':
        return jsonify({'response': 'No selected file'}), 400

    if file and allowed_file(file.filename):
        filename = file.filename
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(file_path)
        # logging.info(f'File saved to {file_path}')
        status = 1
        transcription, status = transcribe_audio(file_path)
        # logging.info(f'Transcribed text: {transcription}')
        user_input = transcription
        reply = None
        if (status == 0):
            response = ask_perplexity(transcription)
            reply, citation = response
            reply = parse_reply(reply)
            audio = tts(reply)
            save_audio(audio, os.path.join(app.config['UPLOAD_FOLDER'], 'reply.mp3'))
            play_audio(audio)
        if (status != 0):
            user_input = 'Please record it again.'
        
        return jsonify({'user_input': user_input, 'status': status, 'response': reply, 'audio_filename': os.path.join(app.config['UPLOAD_FOLDER'], 'reply.mp3')})

@app.route('/updateparams', methods=['POST'])
def updateparams():
    if request.method == 'POST':
        company = request.form['company']
        field = request.form['field']
        role = request.form['role']
        insert_perplexity("Your name is Connor. Your role is an interviewer at {}. Please initiate the interview by introducing yourself, and telling the candidate the structure of the interview. Wait for the candidate confirmation before asking technical questions. The topic is {}. The candidate is interviewing for job {}. Do not apologize unnecessarily!".format(company, field, role))
        print(company, flush=True)
        return render_template('index.html', company=company, field=field, role=role)
    return redirect(url_for('home'))

# @app.route('/audio/<filename>')
# def serve_audio(filename):
#     return send_from_directory(AUDIO_DIRECTORY, filename, as_attachment=True, mimetype='audio/mp3')

# Additional route example
@app.route('/about')
def about():
    return render_template('about.html')

# The main function to run the app
if __name__ == '__main__':
    app.run(debug=False)



# import os
# import logging
# from flask import Flask, render_template, request, jsonify
# from tester import ask_perplexity
# import speech_recognition as sr
# from pydub import AudioSegment

# app = Flask(__name__)
# app.config['UPLOAD_FOLDER'] = 'static/audio'
# ALLOWED_EXTENSIONS = {'wav', 'mp3'}

# def allowed_file(filename):
#     return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# def transcribe_audio(file_path):
#     recognizer = sr.Recognizer()
#     # Convert MP3 to WAV for compatibility with speech_recognition
#     if file_path.endswith('.mp3'):
#         sound = AudioSegment.from_mp3(file_path)
#         file_path = file_path.replace('.mp3', '.wav')
#         sound.export(file_path, format='wav')

#     with sr.AudioFile(file_path) as source:
#         audio = recognizer.record(source)
#         try:
#             text = recognizer.recognize_google(audio)
#             return text
#         except sr.UnknownValueError:
#             return "Could not understand audio"
#         except sr.RequestError as e:
#             return f"Could not request results; {e}"

# @app.route('
[truncated — 1044 more characters]
```

### static/js/main.js

```javascript
// document.addEventListener("DOMContentLoaded", function() {
//     const recordButton = document.getElementById('record-btn');
//     const responseList = document.getElementById('response-list');
//     let mediaRecorder;
//     let isRecording = false;
//     let audioChunks = [];

//     recordButton.addEventListener('click', function() {
//         if (!isRecording) {
//             startRecording();
//         } else {
//             stopRecording();
//         }
//     });

//     function startRecording() {
//         navigator.mediaDevices.getUserMedia({ audio: true })
//             .then((stream) => {
//                 mediaRecorder = new MediaRecorder(stream);
//                 mediaRecorder.start();
//                 recordButton.textContent = 'Stop Recording';
//                 isRecording = true;

//                 audioChunks = [];

//                 mediaRecorder.addEventListener("dataavailable", event => {
//                     audioChunks.push(event.data);
//                 });

//                 mediaRecorder.addEventListener("stop", () => {
//                     const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
//                     convertAndSendToServer(audioBlob);
//                 });
//             })
//             .catch(error => console.error('Error accessing media devices.', error));
//     }

//     function stopRecording() {
//         mediaRecorder.stop();
//         recordButton.textContent = 'Start Recording';
//         isRecording = false;
//     }

//     function convertAndSendToServer(audioBlob) {
//         const reader = new FileReader();
//         reader.onload = function(event) {
//             const arrayBuffer = event.target.result;
//             const wav = lamejs.WavHeader.readHeader(new DataView(arrayBuffer));
//             const samples = new Int16Array(arrayBuffer, wav.dataOffset, wav.dataLen / 2); 

//             const mp3Encoder = new lamejs.Mp3Encoder(1, wav.sampleRate, 128);
//             const mp3Buffer = [];
//             let remaining = samples.length;
//             const maxSamples = 1152;

//             for (let i = 0; remaining >= maxSamples; i += maxSamples) {
//                 const monoChunk = samples.subarray(i, i + maxSamples);
//                 const mp3buf = mp3Encoder.encodeBuffer(monoChunk);
//                 if (mp3buf.length > 0) {
//                     mp3Buffer.push(new Int8Array(mp3buf));
//                 }
//                 remaining -= maxSamples;
//             }

//             const d = mp3Encoder.flush();
//             if (d.length > 0) {
//                 mp3Buffer.push(new Int8Array(d));
//             }

//             const mp3Blob = new Blob(mp3Buffer, { type: 'audio/mpeg-3' });
//             sendDataToServer(mp3Blob);
//         };

//         reader.readAsArrayBuffer(audioBlob);
//     }

//     function sendDataToServer(audioBlob) {
//         const formData = new FormData();
//         formData.append('file', audioBlob, 'audio.mp3');

//         fetch('/ask', {
//             method: 'POST',
//             body: formData
//         })
//         .then(response => response.json())
//         .then(data => {
//             const li = document.createElement('li');
//             li.textContent = data.response;
//             responseList.appendChild(li);
//         })
//         .catch((error) => {
//             console.error('Error:', error);
//         });
//     }
// });







document.addEventListener("DOMContentLoaded", function() {
    const recordButton = document.getElementById('record-btn');
    const submitButton = document.getElementById('start-convo');
    const responseList = document.getElementById('response-list');
    let mediaRecorder;
    let isRecording = false;

    recordButton.addEventListener('click', function() {
        if (!isRecording) {
            startRecording();
        } else {
            stopRecording();
        }
    });

    function startRecording() {
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then((stream) => {
                mediaRecorder = new MediaRecorder(stream);
                mediaRecorder.start();
                recordButton.textContent = 'Stop Recording';
                isRecording = true;

                const audioChunks = [];

                mediaRecorder.addEventListener("dataavailable", event => {
                    audioChunks.push(event.data);
                });

                mediaRecorder.addEventListener("stop", () => {
                    const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                    // uncomment to use wav format instead
                    const audioUrl = URL.createObjectURL(audioBlob);
                    const audio = new Audio(audioUrl);
                    // audio.play();
                    sendDataToServer(audioBlob);
                    // convertAndSendToServer(audioBlob);
                });
            })
            .catch(error => console.error('Error accessing media devices.', error));
    }

    function stopRecording() {
        console.log('recording stopped');
        mediaRecorder.stop();
        recordButton.textContent = 'Start Recording';
        isRecording = false;
    }

    // submitButton.addEventListener('submit', function(event) {
    //     event.preventDefault();
    //     const company = document.getElementById('company').value;
    //     const jobfield = document.getElementById('job-field').value;
    //     const jobrole = document.getElementById('job-role').value;
    //     if (!jobfield || !jobrole) return;
        
    //     fetch('/updateparams', {
    //         method: 'POST',
    //         headers: {
    //             'Content-Type': 'application/json'
    //         },
    //         body: JSON.stringify({ field: jobfield, name: jobrole, company: company })
    //     })
    //     .then(response => response.json())
    //     .then(data => {})
    //     .catch((error) 
[truncated — 3934 more characters]
```

### eltest.py

```python
from elevenlabs import generate, set_api_key

set_api_key('sk_e5f53a150de75465187a3fa312e6cbca028f3454447133ca')

audio = generate(
    text="Hello World!",
    voice="Bella"
)

with open("out.wav", "wb") as fp:
    fp.write(audio)
```

### perplexitytest.py

```python
from openai import OpenAI

YOUR_API_KEY = "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"

messages = [
    {
        "role": "system",
        "content": (
            "You are an artificial intelligence assistant and you need to "
            "engage in a helpful, detailed, polite conversation with a user."
        ),
    },
    {   
        "role": "user",
        "content": (
            "How many stars are in the universe?"
        ),
    },
]

client = OpenAI(api_key=YOUR_API_KEY, base_url="https://api.perplexity.ai")

# chat completion without streaming
response = client.chat.completions.create(
    model="sonar-pro",
    messages=messages,
)
print(response)

# chat completion with streaming
response_stream = client.chat.completions.create(
    model="sonar-pro",
    messages=messages,
    stream=True,
)
for response in response_stream:
    print(response)


```

### texttosspeech.py

```python
from dotenv import load_dotenv
from elevenlabs.client import ElevenLabs
from elevenlabs import play, stream, save

# ELEVENLABS_API_KEY = 'sk_e5f53a150de75465187a3fa312e6cbca028f3454447133ca'
ELEVENLABS_API_KEY = 'sk_a1af276616b5ec2671ea1dfd2f5345b2a1135569160964bf'
load_dotenv()
client = ElevenLabs(api_key = ELEVENLABS_API_KEY)
voiceID = 'EkK5I93UQWFDigLMpZcX'
# voiceID = 'TX3LPaxmHKxFdv7VOQHJ'

def tts(saytext):
    audio = client.text_to_speech.convert(
        text=saytext,
        voice_id=voiceID,
        model_id="eleven_multilingual_v2",
        output_format="mp3_44100_128",
    )
    return audio

def save_audio(audio, title="reply.mp3"):
    save(audio, title)

def play_audio(audio):
    play(audio)

# audio_stream = client.text_to_speech.convert_as_stream(

#     text="This is a test",

#     voice_id="JBFqnCBsd6RMkjVDRZzb",

#     model_id="eleven_multilingual_v2"

# )

# # option 1: play the streamed audio locally

# stream(audio_stream)
```

### lumatest.py

```python
import os
import requests
import time
from lumaai import LumaAI, AsyncLumaAI
os.environ["LUMAAI_API_KEY"] = "luma-1c2a2983-47d5-4305-a3e6-7b10596fba7e-3fe1e6aa-bc71-4aa8-8723-bd0215c17360"

client = LumaAI(
    auth_token=os.environ.get("LUMAAI_API_KEY"),
)

print(client)

generation = client.generations.create(
#   prompt="Filmed from point of view of a candidate getting interviewed by a manager for a job position. Manager is looking at the camera directly speaking to it. Background is a serious office. Make him say 'hello'.",
prompt="A serious character in a suit is talking to the viewer. They are talking and making exaggerated expressions. Their mouth is open sometimes.",
)

completed = False
while not completed:
  generation = client.generations.get(id=generation.id)
  if generation.state == "completed":
    completed = True
  elif generation.state == "failed":
    raise RuntimeError(f"Generation failed: {generation.failure_reason}")
  print("Dreaming")
  time.sleep(3)

video_url = generation.assets.video

# download the video
response = requests.get(video_url, stream=True)
with open(f'{generation.id}.mp4', 'wb') as file:
    file.write(response.content)
print(f"File downloaded as {generation.id}.mp4")
```

### speechrecog.py

```python
import speech_recognition as sr
import pyttsx3 

# Initialize the recognizer 
r = sr.Recognizer()

# Function to convert text to
# speech
def speakText(command):
    # Initialize the engine
    engine = pyttsx3.init()
    engine.say(command) 
    engine.runAndWait()
    
    
# Loop infinitely for user to
# speak

def get_speech():
    MyText = ''
    r = sr.Recognizer()
    r.energy_threshold = 386
    # r.dynamic_energy_threshold = False
    try:
        with sr.Microphone() as source2:
                    
            # wait for a second to let the recognizer
            # adjust the energy threshold based on
            # the surrounding noise level 
            # TODO: make this adjustable for slower speakers who are more hesitant.
            r.pause_threshold = 2.0
            r.adjust_for_ambient_noise(source2, duration=0.5)
            
            #listens for the user's input 
            audio2 = r.listen(source2, timeout=None)
            
            # Using google to recognize audio
            MyText = r.recognize_google(audio2)
            MyText = MyText.lower()
    except sr.RequestError as e:
        print('Could not request results {0}'.format(e))
        
    except sr.UnknownValueError:
        print('unknown error occurred')
    return MyText

# while(1):    
    
#     # Exception handling to handle
#     # exceptions at the runtime
#     try:
        
#         # use the microphone as source for input.
#         with sr.Microphone() as source2:
            
#             # wait for a second to let the recognizer
#             # adjust the energy threshold based on
#             # the surrounding noise level 
#             r.adjust_for_ambient_noise(source2, duration=0.2)
            
#             #listens for the user's input 
#             audio2 = r.listen(source2)
            
#             # Using google to recognize audio
#             MyText = r.recognize_google(audio2)
#             MyText = MyText.lower()

#             print('Did you say', MyText)
#             SpeakText(MyText)
            
#     except sr.RequestError as e:
#         print('Could not request results {0}'.format(e))
        
#     except sr.UnknownValueError:
#         print('unknown error occurred')
```

### tester.py

```python
"""
Implements the tester back-end using perplexity. 
Feature Requirements: 
    - Capability of mistake-catching 
    - Capability of suggesting industry standard solutions #TODO: need to confirm
    - Follow up questions 
    - Design questions
    - Citations
    - Memory in the conversation
    - Fast responses 
    - Streamed responses

Features Implemented/Guaranteed: 
    - Output response 
    - Capability of mistake-catching 
    - Citations
    - Memory
    - Follow up questions

Date: 02/15/25
Author: Andrea Miramontes Serrano

"""
import requests
import json

# Andrea's key
API_KEY = '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'
URL = "https://api.perplexity.ai/chat/completions"

CONV_HIST = [
        {
            "role": "system",
            "content": "You are an interviewer conducting a job interview. Please keep responses short and conversational. To candidate's responses ask follow up, technical questions. If the user makes a factual mistake, ask them about that. Questions are industry-standard. If candidate keeps making mistakes, address their underlying misconception. If the conversation drifts away from the interview topic, do guide the interviewee back."
        # You are an interviewer conducting a job interview.
        # Keep responses short and conversational.
        # Catch mistakes, provide sources and always ask a follow up, technical question.
        # If the user makes a mistake, ask them about that. Every once in a while, ask design questions and/or industry-standard questions.
        # If there are mistakes that keep happening, detect when the user has an underlying misconception.
        # """
        }
    ]

def insert_perplexity(admin_prompt: str):
    CONV_HIST.append({"role": "system", "content": admin_prompt})
    # initial_content = CONV_HIST[0]["content"]
    # output_content = initial_content + admin_prompt
    # CONV_HIST[0]["content"] = output_content
    # response = requests.post(URL, headers=headers, data=json.dumps(data))

    # if response.status_code == 200:
    #     result = response.json()
    #     text_response, citations = result["choices"][0]["message"]["content"], result["citations"]

    #     # Add AI response to memory
    #     CONV_HIST.append({"role": "assistant", "content": text_response})
    #     return text_response, citations
    # else:
    #     return f"Error: {response.status_code}, {response.text}"

def ask_perplexity(prompt: str, role:str = "user") -> str:
    """
    Initializes a perplexity API call: 
    - Asks for interview context #TODO: expand feature set to also social anxiety context 
    - enables user to choose prompt #TODO: expand to have this be a voice input. 
    - Capability of mistake-catching 
    - Capability of suggesting industry standard solutions #TODO: need to confirm
    - Follow up questions 
    - Design questions
    - Citations
    - Memory in the conversation
    - Fast responses 
    - Streamed responses

    """
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }

    CONV_HIST.append({"role": role, "content": prompt})

    data = {
        "model": "sonar-pro",# Use best available model
        "messages": CONV_HIST,  # Maintain conversation memory
        "max_tokens": 300,
        "temperature": 0.7,
        "top_p": 0.9,
        "top_k": 0,
        "stream": False,
        "presence_penalty": 0,
        "frequency_penalty": 0.1  # Set to a valid value (>0)
    }



    response = requests.post(URL, headers=headers, data=json.dumps(data))

    if response.status_code == 200:
        result = response.json()
        text_response, citations = result["choices"][0]["message"]["content"], result.get("citations")

        # Add AI response to memory
        CONV_HIST.append({"role": "assistant", "content": text_response})
        return text_response, citations
    else:
        return f"Error: {response.status_code}, {response.text}"
    
        #TODO: clean up for streamed responses - this bit chunks the response. 
        # full_response = ""
        # print("\n📢 AI is responding...\n")
        # for line in response.iter_lines():
        #     if line:
        #         decoded_line = json.loads(line.decode("utf-8"))
        #         text_chunk = decoded_line["choices"][0]["message"]["content"]
        #         print(text_chunk, end="", flush=True)  # Show response progressively
        #         full_response += text_chunk

        

        # Another way of extracting citations [doesn't work as well]:
        # references = decoded_line["choices"][0]["message"].get("references", [])
        # if references:
        #     print("\n\n🔗 **Citations:**")
        #     for ref in references:
        #         print(f"- {ref['title']} ({ref['url']})")
    

# ----------------- USAGE -----------------
# import sys #TODO: remove - only here for debugging purposes
# if len(sys.argv) > 1:
#     user_input = sys.argv[1]  # Takes input from launch.json
# else:
#     user_input = input("Enter your prompt: ")  # Fallback manual input

# company = 'Big Tech Co.'
# interview_topic = 'Electrical Engineering'
# job = 'Junior VLSI designer.'
# n_rounds = 2
# iteration = 0
# insert_perplexity("Your role is an interviewer at {}. Please initiate the interview by introducing yourself, and telling the candidate the structure of the interview. Wait for the candidate confirmation before asking technical questions. The topic is {}. The candidate is interviewing for job {}. Do not apologize unnecessarily! After {} rounds of technical questions, move on to behavioral questions in the interview.".format(company, interview_topic, job, n_rounds))
# print(CONV_HIST[0])
# while (True):
#     response = ask_perplexity(user_input)
#     ask_perplexity(user_input)
#     print("\nPerplexity AI Response:\n", response[0])
#     user_input = input("Enter your prompt: ")

```

### templates/about.html

```html
<!DOCTYPE html>
<html>

<head>
    <title>About Page</title>
</head>

<body>
    <h1>About Us</h1>
    <p>This is the about page of the web app.</p>
    <a href="/">Home</a>
</body>

</html>
```

[2 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]