# Project export: ArticuAIte

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: ArticulAIte automates first-round interviews with AI-powered VR, generating structured questions, evaluating candidates transparently, and providing real-time hiring insights.
- Devpost: https://devpost.com/software/articuaite
- GitHub: https://github.com/MohamedHAbdelnaby/ArticulAIte
- Video: https://www.youtube.com/embed/lWi4Qel63Fk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Mohamed Abdelnaby (4 commits), hbikoko (1 commits)

## Devpost submission (written by the team)

### Inspiration

Interviewing is one of the biggest bottlenecks in hiring. Companies spend countless hours conducting first-round interviews, often struggling with standardizing evaluations and reducing bias. Candidates—especially neurodivergent ones—face challenges navigating interviews due to inconsistencies in assessment criteria. Our team, many of whom have experienced these issues firsthand, wanted to build a solution that streamlines early-stage hiring while ensuring a structured, transparent, and adaptable experience for candidates. That’s why we created ArticulAIte, a VR-enabled AI interview automation platform that allows companies to generate, deploy, and assess structured interviews using AI-powered avatars. Recruiters input a prompt, and our system automatically generates interview questions, evaluation criteria, and assessment metrics to ensure consistent and efficient hiring decisions.

### What it does

ArticulAIte automates first-round interviews by enabling companies to create AI-powered interviewers that dynamically assess candidates based on customizable prompts. The platform provides: Automated Interview Generation – Recruiters input a prompt (e.g., “Assess technical skills for a backend developer. Ask about algorithms and data structures.”), and ArticulAIte generates a structured interview with relevant questions. Customizable Evaluation Criteria – The AI generates a list of assessment metrics based on the prompt and questions, ensuring standardized evaluations across candidates. AI-Driven Interview Deployment – Interviews can be conducted in VR or on a web-based interface, where candidates interact with an AI avatar interviewer. Post-Interview Analysis & Justification – ArticulAIte analyzes responses, extracts key metrics, and cites specific reasoning for each score, providing transparent, data-driven feedback. This allows companies to efficiently scale hiring, reduce recruiter workload, and ensure objective candidate assessments while giving candidates clear insights into their interview performance.

### How we built it

Building ArticulAIte required integrating AI-driven interview generation, automated evaluation, and immersive VR interactivity into a scalable hiring solution. Our system dynamically generates customized interview questions, conducts AI-powered interviews, and analyzes candidate responses to provide structured, data-driven insights for recruiters. We leveraged OpenAI’s API and LangChain for AI-powered question generation and answer evaluation, ensuring context-aware, industry-specific interviews. NVIDIA Omniverse and NVIDIA Cloud (USDCode) enabled real-time AI model processing, optimizing natural language understanding (NLU) and multimodal assessments. Hume AI was integrated for conducting technical phone interviews, allowing for nuanced analysis of speech and nonverbal cues. We deployed a VR-based solution on Meta Quest 3, aligning with Meta’s Most Technically Complex Hack track. Convai powered conversational AI capabilities to enhance real-time interactions with AI avatars. Unity was used to develop immersive and interactive VR interview experiences. Vercel was used for deploying and managing our web-based interface, ensuring scalability and performance. By leveraging these advanced technologies, we built ArticulAIte into a comprehensive AI-driven interview automation platform that streamlines hiring efficiency, ensures structured candidate evaluations, and enhances fairness in recruitment.

### Challenges we ran into

We had trouble with Omniverse generating a scene, which required additional optimization and fine-tuning. Additionally, we faced issues with APIs and extensive troubleshooting, ensuring seamless integration between various AI models and systems.

### Accomplishments we're proud of

We successfully built a scalable, fully automated interview platform that allows companies to generate, conduct, and assess AI-driven interviews in minutes. Our citation-based evaluation sets us apart, providing transparent, data-backed reasoning for each assessment. Seeing our AI conduct structured, real-time interviews and generate meaningful hiring insights was a huge validation of our work.

### What we learned

This project taught us how to bridge AI-driven automation with human-centric hiring needs. We learned how to: Fine-tune LLM-powered question generation for interview scenarios. Optimize VR interactivity for professional settings. Design assessment models that prioritize both efficiency and fairness. Reimagine hiring processes to be more structured, scalable, and accessible.

### What's next

We see ArticulAIte becoming an essential tool for hiring teams, with potential expansions including: More advanced assessment models incorporating real-time behavioral analysis. Industry-specific interview templates for specialized roles. Candidate performance tracking over multiple interview rounds. Seamless ATS (Applicant Tracking System) integration for streamlined hiring workflows.

## README (from the GitHub repository)

Inspiration
Interviewing is one of the biggest bottlenecks in hiring. Companies spend countless hours conducting first-round interviews, often struggling with standardizing evaluations and reducing bias. Candidates—especially neurodivergent ones—face challenges navigating interviews due to inconsistencies in assessment criteria. Our team, many of whom have experienced these issues firsthand, wanted to build a solution that streamlines early-stage hiring while ensuring a structured, transparent, and adaptable experience for candidates.

That’s why we created ArticulAIte, a VR-enabled AI interview automation platform that allows companies to generate, deploy, and assess structured interviews using AI-powered avatars. Recruiters input a prompt, and our system automatically generates interview questions, evaluation criteria, and assessment metrics to ensure consistent and efficient hiring decisions.

What it does
ArticulAIte automates first-round interviews by enabling companies to create AI-powered interviewers that dynamically assess candidates based on customizable prompts. The platform provides:

Automated Interview Generation – Recruiters input a prompt (e.g., “Assess technical skills for a backend developer. Ask about algorithms and data structures.”), and ArticulAIte generates a structured interview with relevant questions.

Customizable Evaluation Criteria – The AI generates a list of assessment metrics based on the prompt and questions, ensuring standardized evaluations across candidates.

AI-Driven Interview Deployment – Interviews can be conducted in VR or on a web-based interface, where candidates interact with an AI avatar interviewer.

Post-Interview Analysis & Justification – ArticulAIte analyzes responses, extracts key metrics, and cites specific reasoning for each score, providing transparent, data-driven feedback.

This allows companies to efficiently scale hiring, reduce recruiter workload, and ensure objective candidate assessments while giving candidates clear insights into their interview performance.

How we built it
Building ArticulAIte required integrating AI-driven interview generation, automated evaluation, and immersive VR interactivity into a scalable hiring solution. Our system dynamically generates customized interview questions, conducts AI-powered interviews, and analyzes candidate responses to provide structured, data-driven insights for recruiters.

We leveraged OpenAI’s API and LangChain for AI-powered question generation and answer evaluation, ensuring context-aware, industry-specific interviews.

NVIDIA Omniverse and NVIDIA Cloud (USDCode) enabled real-time AI model processing, optimizing natural language understanding (NLU) and multimodal assessments.

Hume AI was integrated for conducting technical phone interviews, allowing for nuanced analysis of speech and nonverbal cues.

We deployed a VR-based solution on Meta Quest 3, aligning with Meta’s Most Technically Complex Hack track.

Convai powered conversational AI capabilities to enhance real-time interactions with AI avatars.

Unity was used to develop immersive and interactive VR interview experiences.

Vercel was used for deploying and managing our web-based interface, ensuring scalability and performance.

By leveraging these advanced technologies, we built ArticulAIte into a comprehensive AI-driven interview automation platform that streamlines hiring efficiency, ensures structured candidate evaluations, and enhances fairness in recruitment.

Challenges we ran into
We had trouble with Omniverse generating a scene, which required additional optimization and fine-tuning. Additionally, we faced issues with APIs and extensive troubleshooting, ensuring seamless integration between various AI models and systems.

Accomplishments that we're proud of
We successfully built a scalable, fully automated interview platform that allows companies to generate, conduct, and assess AI-driven interviews in minutes. Our citation-based evaluation sets us apart, providing transparent, data-backed reasoning for each assessment. Seeing our AI conduct structured, real-time interviews and generate meaningful hiring insights was a huge validation of our work.

What we learned
This project taught us how to bridge AI-driven automation with human-centric hiring needs. We learned how to:

Fine-tune LLM-powered question generation for interview scenarios.

Optimize VR interactivity for professional settings.

Design assessment models that prioritize both efficiency and fairness.

Reimagine hiring processes to be more structured, scalable, and accessible.

What's next for ArticuAIte
We see ArticulAIte becoming an essential tool for hiring teams, with potential expansions including:

More advanced assessment models incorporating real-time behavioral analysis.

Industry-specific interview templates for specialized roles.

Candidate performance tracking over multiple interview rounds.

Seamless ATS (Applicant Tracking System) integration for streamlined hiring workflows.


## Detected evidence (automated analysis)

Indexed codebase: 141 recognized source files, 1141 KB.
- C (language) — detected in the code
- C# (language) — detected in the code
- Python (language) — detected in the code
- LangChain (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 (120 of 1141)

```
.gitignore
.plastic/plastic.selector
.plastic/plastic.wktree
.plastic/plastic.workspace
.vsconfig
Assets/Convai.meta
Assets/Convai/Art.meta
Assets/Convai/Art/Animations.meta
Assets/Convai/Art/Animations/Crouch.anim
Assets/Convai/Art/Animations/Crouch.anim.meta
Assets/Convai/Art/Animations/Dance.anim
Assets/Convai/Art/Animations/Dance.anim.meta
Assets/Convai/Art/Animations/idle.anim
Assets/Convai/Art/Animations/idle.anim.meta
Assets/Convai/Art/Animations/Jumping.anim
Assets/Convai/Art/Animations/Jumping.anim.meta
Assets/Convai/Art/Animations/Picking Up.anim
Assets/Convai/Art/Animations/Picking Up.anim.meta
Assets/Convai/Art/Animations/Talking.anim
Assets/Convai/Art/Animations/Talking.anim.meta
Assets/Convai/Art/Animations/Walking.anim
Assets/Convai/Art/Animations/Walking.anim.meta
Assets/Convai/Art/Animators.meta
Assets/Convai/Art/Animators/FeedbackThumbs.controller
Assets/Convai/Art/Animators/FeedbackThumbs.controller.meta
Assets/Convai/Art/Resources.meta
Assets/Convai/Art/Resources/Feminine NPC Animator.controller
Assets/Convai/Art/Resources/Feminine NPC Animator.controller.meta
Assets/Convai/Art/Resources/Masculine NPC Animator.controller
Assets/Convai/Art/Resources/Masculine NPC Animator.controller.meta
Assets/Convai/Art/UI.meta
Assets/Convai/Art/UI/Custom Fonts.meta
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Bold SDF.asset
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Bold SDF.asset.meta
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Light SDF.asset
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Light SDF.asset.meta
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Medium SDF.asset
Assets/Convai/Art/UI/Custom Fonts/Inter_18pt-Medium SDF.asset.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/OFL.txt
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/OFL.txt.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/README.txt
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/README.txt.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/SpaceGrotesk-VariableFont_wght.ttf.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static/SpaceGrotesk-Bold.ttf.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static/SpaceGrotesk-Light.ttf.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static/SpaceGrotesk-Medium.ttf.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static/SpaceGrotesk-Regular.ttf.meta
Assets/Convai/Art/UI/Custom Fonts/Space_Grotesk/static/SpaceGrotesk-SemiBold.ttf.meta
Assets/Convai/Art/UI/Editor.meta
Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uss
Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uss.meta
Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uxml
Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uxml.meta
Assets/Convai/Art/UI/Feedback System.meta
Assets/Convai/Art/UI/Feedback System/ThumbsDownFill.png.meta
Assets/Convai/Art/UI/Feedback System/ThumbsDownOutline.png.meta
Assets/Convai/Art/UI/Feedback System/ThumbsUpFill.png.meta
Assets/Convai/Art/UI/Feedback System/ThumbsUpOutline.png.meta
Assets/Convai/Art/UI/Logos.meta
Assets/Convai/Art/UI/Logos/App Icon.png.meta
Assets/Convai/Art/UI/Logos/Convai Logo Full.png.meta
Assets/Convai/Art/UI/Logos/convai logo with frame.png.meta
Assets/Convai/Art/UI/Logos/Convai Logo.png.meta
Assets/Convai/Art/UI/Notification System.meta
Assets/Convai/Art/UI/Notification System/IconBackground.png.meta
Assets/Convai/Art/UI/Notification System/NotificationBackground.png.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons/APIKeyNotFound.png.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons/MicrophoneIssue.png.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons/NetworkIssue.png.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons/NoActiveNPC.png.meta
Assets/Convai/Art/UI/Notification System/NotificationIcons/TalkButtonReleasedEarly.png.meta
Assets/Convai/Art/UI/Settings Panel.meta
Assets/Convai/Art/UI/Settings Panel/RectangleDropdown.png.meta
Assets/Convai/Art/UI/Settings Panel/SettingsPanel Background_1.png.meta
Assets/Convai/Art/UI/Settings Panel/SettingsPanel Background_2.png.meta
Assets/Convai/Art/UI/Transcript UI.meta
Assets/Convai/Art/UI/Transcript UI/IndicatorBackground.png.meta
Assets/Convai/Art/UI/Transcript UI/MessageBoxBackground.png.meta
Assets/Convai/Art/UI/Transcript UI/MicrophoneIcon.png.meta
Assets/Convai/Art/UI/Transcript UI/SettingsIcon.png.meta
Assets/Convai/Art/UI/Transcript UI/UIChatBackground.png.meta
Assets/Convai/Art/UI/Transcript UI/UIChatInputField.png.meta
Assets/Convai/Art/UI/Transcript UI/UIQABackground.png.meta
Assets/Convai/Art/UI/Transcript UI/UISubtitleBackground.png.meta
Assets/Convai/Art/UI/Transcript UI/WhiteBackground_HighRounded_1024x256px.png.meta
Assets/Convai/Characters.meta
Assets/Convai/Characters/Mesh Data.meta
Assets/Convai/Characters/Mesh Data/Alex.meta
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33.meta
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/0dee43dbc6728947b5e4ff476717ca6f.meta
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/0dee43dbc6728947b5e4ff476717ca6f/63f8976b96cb00e4ba34fe33.glb
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/0dee43dbc6728947b5e4ff476717ca6f/63f8976b96cb00e4ba34fe33.glb.meta
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/0dee43dbc6728947b5e4ff476717ca6f/63f8976b96cb00e4ba34fe33.prefab
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/0dee43dbc6728947b5e4ff476717ca6f/63f8976b96cb00e4ba34fe33.prefab.meta
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/63f8976b96cb00e4ba34fe33.json
Assets/Convai/Characters/Mesh Data/Alex/63f8976b96cb00e4ba34fe33/63f8976b96cb00e4ba34fe33.json.meta
Assets/Convai/Characters/Mesh Data/Alexi.meta
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f.meta
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f.meta
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.glb
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.glb.meta
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.prefab
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.prefab.meta
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/63f885c10b1a740a88c4a19f.json
Assets/Convai/Characters/Mesh Data/Alexi/63f885c10b1a740a88c4a19f/63f885c10b1a740a88c4a19f.json.meta
Assets/Convai/Characters/Mesh Data/Brandon.meta
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f.meta
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f.meta
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.glb
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.glb.meta
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.prefab
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/0dee43dbc6728947b5e4ff476717ca6f/63f885c10b1a740a88c4a19f.prefab.meta
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/63f885c10b1a740a88c4a19f.json
Assets/Convai/Characters/Mesh Data/Brandon/63f885c10b1a740a88c4a19f/63f885c10b1a740a88c4a19f.json.meta
Assets/Convai/Characters/Mesh Data/README.md
Assets/Convai/Characters/Mesh Data/README.md.meta
Assets/Convai/Characters/Prefabs.meta
[1021 more files omitted for size]
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Pushing AI
- Update README.md
- Latest working prototype
- Adding unity gitignore file
- Initial commit

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

### Assets/Convai/package.json

```
{
    "samples": [
        {
            "displayName": "Action System",
            "description": "Contains a sample scene where how to integrate Action System in your game is explained",
            "path": "Samples~/Actions"
        },
        {
            "displayName": "Narrative Design",
            "description": "Contains a sample scene where how to integrate Narrative Design in your game is explained",
            "path": "Samples~/Narrative Design"
        },
        {
            "displayName": "NPC to NPC Conversation",
            "description": "Contains a sample scene where how to integrate NPC to NPC Conversation in your game is explained",
            "path": "Samples~/NPC to NPC Conversation"
        }
    ]
}
```

### backend_server.py

```python
from flask import Flask, request, jsonify
import traceback
from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage
import openai


app = Flask(__name__)
app.debug = True  


# =============================
# 2. Initialize the GPT-4 model via LangChain
# =============================
llm = ChatOpenAI(
    openai_api_key=OPENAI_API_KEY,
    model_name="gpt-4",
    temperature=0.5,
    max_tokens=512
)

# =============================
# 3. Initialize the NVIDIA USD-code client
# =============================
nvidia_client = openai.OpenAI(
    base_url="https://integrate.api.nvidia.com/v1",
    api_key=NVIDIA_API_KEY
)

# =============================
# 4. Define an endpoint to generate an office scene
# =============================
@app.route("/generate_office_scene", methods=["POST"])
def generate_office_scene():
    try:
        data = request.get_json() or {}
        prompt = data.get(
            "prompt",
            (
                "Generate a detailed 3D scene description for a modern office environment. "
                "Include photorealistic textures, realistic lighting, advanced materials, and "
                "functional furniture suitable for a virtual technical job interview setting."
            )
        )
        
        # Step 1: Generate the scene description using GPT-4.
        messages = [
            SystemMessage(content="You are an expert in generating detailed 3D scene descriptions for virtual environments."),
            HumanMessage(content=prompt)
        ]
        scene_description_obj = llm(messages)
        scene_description = str(scene_description_obj)
        
        # Step 2: Prepare a prompt for NVIDIA’s USD-code model.
        usd_prompt = (
            "Generate USD-Python code that creates a 3D scene in NVIDIA Omniverse for the following office scene description:\n\n"
            f"{scene_description}\n\n"
            "The code should include creation of assets, lighting, materials, and camera settings appropriate for a modern office environment."
        )
        
        # Call NVIDIA’s USD-code model with streaming response.
        completion = nvidia_client.chat.completions.create(
            model="nvidia/usdcode-llama-3.1-70b-instruct",
            messages=[{"role": "user", "content": usd_prompt}],
            temperature=0.1,
            top_p=1,
            max_tokens=1024,
            extra_body={"expert_type": "auto"},
            stream=True
        )
        
        usd_code = ""
        for chunk in completion:
            delta = chunk.choices[0].delta
            # Access the content attribute directly instead of using .get()
            if delta and delta.content is not None:
                usd_code += delta.content
        
        # Return both the scene description and the USD code.
        return jsonify({
            "scene_description": scene_description,
            "usd_code": usd_code
        })
    except Exception as e:
        print("Error occurred:\n", traceback.format_exc())
        return jsonify({"error": "Internal server error", "message": str(e)}), 500

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
```

### Assets/Plugins/Android/AndroidManifest.xml

```xml
﻿<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto">
  <application android:label="@string/app_name" android:icon="@mipmap/app_icon" android:allowBackup="false">
    <activity android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" android:configChanges="locale|fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" android:launchMode="singleTask" android:name="com.unity3d.player.UnityPlayerActivity" android:excludeFromRecents="true" android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="com.oculus.intent.category.VR" />
      </intent-filter>
      <meta-data android:name="com.oculus.vr.focusaware" android:value="true" />
    </activity>
    <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="false" />
    <meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only" />
    <meta-data android:name="com.oculus.ossplash.background" android:value="black" />
    <meta-data android:name="com.oculus.telemetry.project_guid" android:value="50670e5a-5647-473e-931b-1254019a23e5" />
    <meta-data android:name="com.oculus.supportedDevices" android:value="quest|quest2|questpro|quest3|quest3s" tools:replace="android:value" />
  </application>
  <uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="true" />
</manifest>
```

### Assets/Convai/Scripts/UASVSA/VSAttribution.cs

```c#
﻿using System;
using UnityEngine.Analytics;

namespace UnityEditor.VSAttribution
{
    public static class VSAttribution
    {
        private const int k_VersionId = 4;
        private const int k_MaxEventsPerHour = 10;
        private const int k_MaxNumberOfElements = 1000;

        private const string k_VendorKey = "unity.vsp-attribution";
        private const string k_EventName = "vspAttribution";

        private static bool RegisterEvent()
        {
            AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour,
                k_MaxNumberOfElements, k_VendorKey, k_VersionId);

            bool isResultOk = result == AnalyticsResult.Ok;
            return isResultOk;
        }

        /// <summary>
        ///     Registers and attempts to send a Verified Solutions Attribution event.
        /// </summary>
        /// <param name="actionName">Name of the action, identifying a place this event was called from.</param>
        /// <param name="partnerName">Identifiable Verified Solutions Partner's name.</param>
        /// <param name="customerUid">Unique identifier of the customer using Partner's Verified Solution.</param>
        public static AnalyticsResult SendAttributionEvent(string actionName, string partnerName, string customerUid)
        {
            try
            {
                // Are Editor Analytics enabled ? (Preferences)
                if (!EditorAnalytics.enabled)
                    return AnalyticsResult.AnalyticsDisabled;

                if (!RegisterEvent())
                    return AnalyticsResult.InvalidData;

                // Create an expected data object
                VSAttributionData eventData = new()
                {
                    actionName = actionName,
                    partnerName = partnerName,
                    customerUid = customerUid,
                    extra = "{}"
                };

                return EditorAnalytics.SendEventWithLimit(k_EventName, eventData, k_VersionId);
            }
            catch
            {
                // Fail silently
                return AnalyticsResult.AnalyticsDisabled;
            }
        }

        #region Nested type: VSAttributionData

        [Serializable]
        private struct VSAttributionData
        {
            public string actionName;
            public string partnerName;
            public string customerUid;
            public string extra;
        }

        #endregion
    }
}
```

### Assets/Convai/ConvaiXR/Scripts/XRFixedUIPositionHandler.cs

```c#
using UnityEngine;

/// <summary>
/// Manages the UI element's position and rotation in a XR environment
/// so that it maintains a fixed position relative to the player camera.
/// </summary>
public class XRFixedUIPositionHandler : MonoBehaviour
{
    // Offset from the camera's position
    [SerializeField] private Vector3 _offset;
    // Speed of the interpolation for smooth movement
    [SerializeField] private float lerpSpeed;
    // Whether the UI is allowed to move along the X axis
    [SerializeField] private bool allowXMovement = true;
    // Whether the UI is allowed to move along the Y axis
    [SerializeField] private bool allowYMovement = true;
    // Whether the UI is allowed to move along the Z axis
    [SerializeField] private bool allowZMovement = true;

    // Reference to the main camera
    private Camera playerCamera;

    /// <summary>
    /// Initializes the reference to the main camera.
    /// </summary>
    private void Awake()
    {
        playerCamera = Camera.main;
    }

    /// <summary>
    /// Updates the UI's position and rotation each frame.
    /// </summary>
    private void LateUpdate()
    {
        UpdateUIPosition();
    }

    /// <summary>
    /// Calculates the target position for the UI element and smoothly moves it to that position.
    /// </summary>
    private void UpdateUIPosition()
    {
        Vector3 targetPosition = CalculateTargetPosition();
        transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * lerpSpeed);
        UpdateUIRotation();
    }

    /// <summary>
    /// Calculates the target position of the UI element relative to the player's camera.
    /// </summary>
    /// <returns>The calculated target position.</returns>
    private Vector3 CalculateTargetPosition()
    {
        Vector3 targetOffset = CalculateTargetOffset();
        Vector3 targetPosition = playerCamera.transform.position + targetOffset;

        // Apply fixed offsets if movement is not allowed
        if (!allowXMovement) targetPosition.x = _offset.x;
        if (!allowYMovement) targetPosition.y = _offset.y;
        if (!allowZMovement) targetPosition.z = _offset.z;

        return targetPosition;
    }

    /// <summary>
    /// Calculates the offset vector based on the allowed movement directions.
    /// </summary>
    /// <returns>The calculated offset vector.</returns>
    private Vector3 CalculateTargetOffset()
    {
        Vector3 targetOffset = Vector3.zero;

        if (allowXMovement)
            targetOffset += _offset.x * playerCamera.transform.right;

        if (allowYMovement)
            targetOffset += _offset.y * playerCamera.transform.up;

        if (allowZMovement)
            targetOffset += _offset.z * playerCamera.transform.forward;

        return targetOffset;
    }

    /// <summary>
    /// Updates the rotation of the UI element to face the camera.
    /// </summary>
    private void UpdateUIRotation()
    {
        Vector3 direction = transform.position - playerCamera.transform.position;
        transform.rotation = Quaternion.LookRotation(direction);
    }
}
```

### Assets/Convai/Demo/Scripts/NPCBehaviorController.cs

```c#
using System;
using Convai.Scripts.Runtime.Core;
using UnityEngine;

namespace Convai.Demo.Scripts
{
    public class NPCBehaviorController : MonoBehaviour
    {
        [SerializeField] private float moveSpeed;
        [SerializeField] private float targetDistanceThreshold = 0.1f;
        [SerializeField] private float rotationSpeed = 5f; // Speed at which the NPC turns
        private Animator _animator;
        private ConvaiHeadTracking _convaiHeadTracking;

        private State _currentState;
        private Transform _currentTarget;
        private Quaternion _originalRotation;

        private void Awake()
        {
            _animator = GetComponent<Animator>();
            _convaiHeadTracking = GetComponent<ConvaiHeadTracking>();
        }

        private void Start()
        {
            _currentState = State.Idle;
            _originalRotation = transform.rotation; // Store the original rotation
        }

        private void Update()
        {
            switch (_currentState)
            {
                case State.Move:
                    Move();
                    break;
                case State.Idle:
                    break;
            }
        }

        public void UpdateCurrentTarget(Transform newTarget)
        {
            _currentTarget = newTarget;
        }

        public void UpdateState(int newState)
        {
            UpdateState((State)newState);
        }

        private void UpdateState(State newState)
        {
            _currentState = newState;
            PerformPostUpdateStateActions();
        }

        private void PerformPostUpdateStateActions()
        {
            switch (_currentState)
            {
                case State.Move:
                    _animator.CrossFade(Animator.StringToHash("Walking"), 0.01f);
                    _convaiHeadTracking.SetActionRunning(true);
                    break;
                case State.Idle:
                    _animator.CrossFade(Animator.StringToHash("Idle"), 0.01f);
                    _convaiHeadTracking.SetActionRunning(false);
                    transform.rotation = Quaternion.Slerp(transform.rotation, _originalRotation, Time.deltaTime * rotationSpeed); // Rotate back to original direction
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void Move()
        {
            if (Vector3.Distance(transform.position, _currentTarget.position) < targetDistanceThreshold)
            {
                UpdateState(State.Idle);
                return;
            }

            Vector3 direction = (_currentTarget.position - transform.position).normalized;
            direction.y = 0;

            // Rotate towards the movement direction
            if (direction != Vector3.zero)
            {
                Quaternion targetRotation = Quaternion.LookRotation(direction);
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
            }

            transform.Translate(direction * moveSpeed * Time.deltaTime, Space.World);
        }

        private enum State
        {
            Move = 0,
            Idle = 1
        }
    }
}
```

### Assets/Convai/ConvaiXR/Scripts/XRNPCUIPositionHandler.cs

```c#
using Convai.Scripts.Runtime.Core;
using UnityEngine;

/// <summary>
/// Handles the UI transformation for XR interactions, adjusting the UI position based on the player's camera distance from an NPC.
/// </summary>
public class XRNPCUIPositionHandler : MonoBehaviour
{
    // Speed at which the UI lerps to its new position.
    [SerializeField] private float _lerpSpeed;
    // Offset from the NPC position.
    [SerializeField] private Vector3 _offset;
    // Distance threshold for switching offsets.
    [SerializeField] private float _cameraDistanceThreshold;
    // Reference to the main camera in the scene.
    private Camera _playerCamera;
    // Reference to the currently active NPC.
    private ConvaiNPC _currentNPC;

    /// <summary>
    /// Subscribes to the active NPC change event when the script is enabled.
    /// </summary>
    private void OnEnable()
    {
        if (ConvaiNPCManager.Instance != null)
        {
            ConvaiNPCManager.Instance.OnActiveNPCChanged += OnActiveNPCChanged;
            _currentNPC = ConvaiNPCManager.Instance.activeConvaiNPC;
        }
    }

    /// <summary>
    /// Unsubscribes from the active NPC change event when the script is disabled.
    /// </summary>
    private void OnDisable()
    {
        if (ConvaiNPCManager.Instance != null)
        {
            ConvaiNPCManager.Instance.OnActiveNPCChanged -= OnActiveNPCChanged;
        }
    }

    /// <summary>
    /// Gets the main camera reference.
    /// </summary>
    private void Start()
    {
        _playerCamera = Camera.main;
    }

    /// <summary>
    /// Updates the current NPC and sets the UI position when the active NPC changes.
    /// </summary>
    /// <param name="newNPC">The new active NPC.</param>
    private void OnActiveNPCChanged(ConvaiNPC newNPC)
    {
        _currentNPC = newNPC;
        if (_currentNPC != null && _playerCamera != null)
        {
            SetUIPosition();
        }
    }

    /// <summary>
    /// Updates the UI position and rotation to face the camera in each frame.
    /// </summary>
    private void LateUpdate()
    {
        if (_currentNPC != null)
        {
            UpdateUIPosition();
            FaceCamera();
        }
    }
    
    /// <summary>
    /// Sets the UI position based on the distance from the player camera to the NPC.
    /// </summary>
    private void SetUIPosition()
    {
        Transform npcTransform = _currentNPC.transform;
        Vector3 targetPosition = CalculateTargetPosition(npcTransform);
        transform.position = targetPosition;
    }

    /// <summary>
    /// Smoothly updates the UI position using linear interpolation (Lerp).
    /// </summary>
    private void UpdateUIPosition()
    {
        Transform npcTransform = _currentNPC.transform;
        Vector3 targetPosition = CalculateTargetPosition(npcTransform);
        transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * _lerpSpeed);
    }

    /// <summary>
    /// Calculates the target position of the UI based on the NPC's position and the distance to the player camera.
    /// </summary>
    /// <param name="npcTransform">The transform of the NPC.</param>
    /// <returns>The target position for the UI.</returns>
    private Vector3 CalculateTargetPosition(Transform npcTransform)
    {
        Vector3 leftOffset = new Vector3(-_offset.x, _offset.y, _offset.z);
        Vector3 rightOffset = new Vector3(_offset.x, _offset.y, _offset.z);

        Vector3 leftOffsetPosition = npcTransform.position + npcTransform.TransformDirection(leftOffset);
        Vector3 rightOffsetPosition = npcTransform.position + npcTransform.TransformDirection(rightOffset);

        float distanceToLeftOffset = Vector3.Distance(leftOffsetPosition, _playerCamera.transform.position);
        float distanceToRightOffset = Vector3.Distance(rightOffsetPosition, _playerCamera.transform.position);

        Vector3 dynamicOffset = DetermineDynamicOffset(distanceToLeftOffset, distanceToRightOffset);

        return npcTransform.position + npcTransform.TransformDirection(dynamicOffset);
    }

    /// <summary>
    /// Determines the appropriate dynamic offset based on the distances from the camera to the left and right offsets.
    /// </summary>
    /// <param name="distanceToLeftOffset">Distance to the left offset position.</param>
    /// <param name="distanceToRightOffset">Distance to the right offset position.</param>
    /// <returns>The chosen offset vector.</returns>
    private Vector3 DetermineDynamicOffset(float distanceToLeftOffset, float distanceToRightOffset)
    {
        Vector3 leftOffset = new Vector3(-_offset.x, _offset.y, _offset.z);
        Vector3 rightOffset = new Vector3(_offset.x, _offset.y, _offset.z);

        float threshold = 0.5f;

        if (distanceToLeftOffset < _cameraDistanceThreshold && distanceToRightOffset < _cameraDistanceThreshold)
        {
            float difference = Mathf.Abs(distanceToLeftOffset - distanceToRightOffset);
            return difference > threshold
                ? (distanceToLeftOffset > distanceToRightOffset ? leftOffset : rightOffset)
                : leftOffset;
        }
        else
        {
            return distanceToLeftOffset >= _cameraDistanceThreshold ? leftOffset : rightOffset;
        }
    }

    /// <summary>
    /// Makes the UI face the camera.
    /// </summary>
    private void FaceCamera()
    {
        Vector3 direction = transform.position - _playerCamera.transform.position;
        transform.rotation = Quaternion.LookRotation(direction);
    }
}
```

### Assets/Convai/Resources/Controls.cs

```c#
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
//     version 1.7.0
//     from Assets/Convai/Resources/Controls.inputactions
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;

public partial class @Controls: IInputActionCollection2, IDisposable
{
    public InputActionAsset asset { get; }
    public @Controls()
    {
        asset = InputActionAsset.FromJson(@"{
    ""name"": ""Controls"",
    ""maps"": [
        {
            ""name"": ""Player"",
            ""id"": ""9966ac18-db5e-4728-b610-7a4351d57f83"",
            ""actions"": [
                {
                    ""name"": ""Move"",
                    ""type"": ""Value"",
                    ""id"": ""37317921-e05d-49eb-80ba-4037dc3422e9"",
                    ""expectedControlType"": ""Vector2"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": true
                },
                {
                    ""name"": ""Talk"",
                    ""type"": ""Button"",
                    ""id"": ""7efd48a1-36f9-41dc-ba05-64f4e37985a2"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""Jump"",
                    ""type"": ""Button"",
                    ""id"": ""b5c6fdbe-4e72-4cb2-9ddf-0afea5878846"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""Look"",
                    ""type"": ""Value"",
                    ""id"": ""d4bfac40-fd90-46ab-a1f6-07c2a12d8a93"",
                    ""expectedControlType"": ""Vector2"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": true
                },
                {
                    ""name"": ""MousePress"",
                    ""type"": ""Button"",
                    ""id"": ""a4a2b620-6da1-47db-9898-5d5d9be49c3f"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""Run"",
                    ""type"": ""Button"",
                    ""id"": ""33720f42-6005-4ea1-b461-e518f7f56d7e"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""SendText"",
                    ""type"": ""Button"",
                    ""id"": ""8b3cda63-fdb6-4e88-8ab7-83b28b309345"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""ToggleChat"",
                    ""type"": ""Button"",
                    ""id"": ""35745f7d-43f1-4748-863e-5b3717499377"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""ToggleSettings"",
                    ""type"": ""Button"",
                    ""id"": ""2feff8e6-737d-4f11-892e-60a1eedbe5b8"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                },
                {
                    ""name"": ""CursorUnlock"",
                    ""type"": ""Button"",
                    ""id"": ""b140b6bf-eff7-4213-a43d-47c7c4f9f492"",
                    ""expectedControlType"": ""Button"",
                    ""processors"": """",
                    ""interactions"": """",
                    ""initialStateCheck"": false
                }
            ],
            ""bindings"": [
                {
                    ""name"": """",
                    ""id"": ""5e200eed-fa09-4181-a9e5-aa009d612ed4"",
                    ""path"": ""<Keyboard>/t"",
                    ""interactions"": """",
                    ""processors"": """",
                    ""groups"": ""Keyboard & Mouse"",
                    ""action"": ""Talk"",
                    ""isComposite"": false,
                    ""isPartOfComposite"": false
                },
                {
                    ""name"": """",
                    ""id"": ""eb6fde4e-4025-40db-9dec-67816470992d"",
                    ""path"": ""<XRController>{RightHand}/primaryButton"",
                    ""interactions"": """",
                    ""processors"": """",
                    ""groups"": """",
                    ""action"": ""Talk"",
                    ""isComposite"": false,
                    ""isPartOfComposite"": false
                },
                {
                    ""name"": """",
                    ""id"": ""eca909b4-db20-4f74-86ec-48d30a34f031"",
                    ""path"": ""<Keyboard>/space"",
    
[truncated — 20104 more characters]
```

### Assets/Convai/Scripts/Runtime/Attributes/ReadOnlyAttribute.cs

```c#
using UnityEngine;

namespace Convai.Scripts.Runtime.Attributes
{
    public class ReadOnlyAttribute : PropertyAttribute
    {
    }
}
```

### Assets/Convai/Scripts/Editor/UI/ConvaiImagesDirectory.cs

```c#
namespace Convai.Scripts.Editor.UI
{
    public struct ConvaiImagesDirectory
    {
        public const string CONVAI_LOGO_PATH = "Assets/Convai/Art/UI/Logos/Convai Logo.png";
    }
}
```

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