# Project export: Mockly — The Best Way to Prep For Interviews

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: Cal Hacks 12.0
- Tagline: Mockly: a voice‑enabled AI for coding interviews, you can talk or text with a realistic interviewer in a live IDE, run code across languages, and get structured feedback; built to grow.
- Devpost: https://devpost.com/software/mockly-the-best-way-to-prep-for-interviews
- GitHub: https://github.com/to-ke/mockly
- Video: https://www.youtube.com/embed/C1YFaO8PRQ8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Aditya Mangalampalli (18 commits), to-ke (17 commits), Dhruv Kesavarap (10 commits), Fengyi Huang (3 commits)

## Devpost submission (written by the team)

### Inspiration

We wanted interview prep that felt human, but safe. We pictured "AI in a bubble": a friendly face you can speak to, not just a chat window. For classmates (and ourselves) who are introverted or anxious, voice practice lowers the barrier to start, repeat, and improve. Mockly began as a way to put a virtual face to a name, and grew into a path for low-pressure conversational practice.

### What it does

Mockly is a voice-enabled AI for coding interviews. A realistic interviewer presents a problem, listens to your reasoning, chats back, and runs your code in a live IDE (Python, JS/TS, C/C++, Java, Go, C#, Kotlin, Ruby, Perl). After a session, it summarizes performance across code cleanliness, communication, and efficiency.

### How we built it

Frontend React + Vite + TypeScript, Monaco Editor for code, Zustand for state A "talking head" avatar via @met4citizen/talkinghead with real-time lipsync WebRTC mic streaming (user gesture to enable), and a resilient WS client for voice events Direct backend calls (bypassing dev proxy) to stabilize requests in Docker Lightweight Markdown renderer for assistant messages Backend FastAPI with CORS, Dockerized Anthropic Claude for interview logic and feedback text Deepgram for speech: prerecorded STT (Listen) and low-latency streaming TTS (Speak) Code execution service: subprocess compile/run for multiple languages Question management: YAML questions, examples, and per-language starter code WebRTC via aiortc for mic capture; sentence-chunking of model tokens → TTS frames for responsive audio

### Challenges we ran into

Voice/TTS auth and streaming Deepgram Speak 401s surfaced only after the first WS write; added diagnostics, safe fallbacks, and a browser SpeechSynthesis fallback for silent turns. Voice/TTS auth and streaming Deepgram Speak 401s surfaced only after the first WS write; added diagnostics, safe fallbacks, and a browser SpeechSynthesis fallback for silent turns. WS handshake churn (localhost vs 127.0.0.1) in Docker/Windows; added multi-candidate WS URLs and backoff. WS handshake churn (localhost vs 127.0.0.1) in Docker/Windows; added multi-candidate WS URLs and backoff. Browser interaction rules getUserMedia without a click left the mic "busy" and the button disabled; we deferred mic warmup until user intent. Browser interaction rules getUserMedia without a click left the mic "busy" and the button disabled; we deferred mic warmup until user intent. Dev proxy vs direct origin Vite restarts caused intermittent 404/connection refused; we switched the client to call the backend origin directly. Dev proxy vs direct origin Vite restarts caused intermittent 404/connection refused; we switched the client to call the backend origin directly. Frontend gotchas JS automatic semicolon insertion (IIFE after state call) broke sending; fixed with explicit semicolons. Markdown showed raw asterisks; added a small, escaped renderer. Frontend gotchas JS automatic semicolon insertion (IIFE after state call) broke sending; fixed with explicit semicolons. Markdown showed raw asterisks; added a small, escaped renderer. Starter code and UX papercuts C++ examples missing headers (vector); Java lacking a Main entry; updated YAML for out-of-box runs. Starter code and UX papercuts C++ examples missing headers (vector); Java lacking a Main entry; updated YAML for out-of-box runs.

### Accomplishments we're proud of

A cohesive voice + avatar + IDE loop that feels personal, not robotic A multi-language runner that lets candidates practice in their preferred stack Real-time token chunking → TTS streaming for responsive, conversational delivery Cleaner DX: robust WS reconnection, direct backend routing, and safer markdown

### What we learned

Voice UX matters: short, sentence-aware streaming is miles better than long, monolithic replies WebRTC and WS in containers need pragmatic fallbacks (origin resolution, candidate lists) Getting a robust frontend-backend integration and communication with continuous, rigorous testing and validation Aligning the displayed question with the interviewer's prompt is critical for trust

### What's next

Today, Mockly focuses on technical interviews with voice and live code execution. The same stack is well-suited to expand thoughtfully: Short-term: richer transcripts, rubric tuning, exportable reports, and typed-reply TTS Medium-term: scenario packs (behavioral rounds), pacing controls, and structured follow-ups Long-term: a supportive practice space for broader conversations that's designed for students, the socially anxious, the introverted, so confidence grows one conversation at a time

## README (from the GitHub repository)

# Mockly

Mockly is a full-stack coding-interview practice experience. The Vite/React frontend presents interview flows (landing → live editor → feedback), while the FastAPI backend powers problem distribution, structured feedback, and WebRTC signaling. This repo is a mono workspace that keeps both apps in sync.

## Architecture

- **Frontend (`mockly-frontend`)** – Vite + React + TypeScript UI with Tailwind and Zustand state. Calls `src/services/api.ts` for `/api/*` endpoints, manages the Monaco-like editor, and renders problems, execution results, and feedback.
- **Backend (`mockly-backend`)** – FastAPI service exposing:
  - `POST /api/questions` to fetch prompts (with embedded examples) backed by `questions.yaml`.
  - `GET /api/feedback` for canned structured feedback.
  - `POST /api/webrtc/offer`, `POST /api/webrtc/candidate`, `DELETE /api/webrtc/session/:id` for lightweight WebRTC signaling.
  - `POST /api/execute` to compile/run Python, JavaScript, TypeScript, C++, and Java snippets inside an isolated temp workspace.
- **Shared data** – `questions.yaml` stores multi-difficulty prompts consumed at startup by the backend.
- **Local proxying** – The Vite dev server proxies `/api` to `localhost:8000`, so browser calls reach FastAPI without manual CORS fiddling.

```
frontend (Vite dev server) --/api--> FastAPI -- question/feedback store
```

## Repository layout

```
mockly/
├── mockly-frontend/       # React client
├── mockly-backend/        # FastAPI app (questions, feedback, WebRTC)
├── questions.yaml         # Source of truth for prompts
├── environment.yml        # Optional Python env descriptor
└── README.md              # You are here
```

## Quick start

### Requirements
- Node 20+
- Python 3.11+
- (optional) Poetry for backend dependency management

### One-command stack (Docker)
```bash
docker compose up --build
```
This builds the backend runner image (with Node, ts-node, g++, and the JDK installed) plus the frontend dev-server image, then exposes the apps on `http://localhost:8000` (API) and `http://localhost:5173` (Vite). Use this path if you want the code-execution endpoint to work without manually installing extra toolchains.

### Backend
```bash
cd mockly-backend
poetry install         # or pip install -r <generated>
poetry run uvicorn app.main:app --reload
```
This exposes FastAPI on `http://localhost:8000`.
> **Heads up:** `/api/execute` shells out to `python3`, `node`, `ts-node`, `g++`, and `javac`. Install those locally or run the backend via `docker compose up backend` so the containerized toolchain handles execution for you.

### Frontend
```bash
cd mockly-frontend
npm install            # or pnpm/yarn
npm run dev
```
The Vite dev server runs on `http://localhost:5173` and proxies `/api` to the backend.

## API surface (summary)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/questions` | POST | Retrieve a random prompt for a given difficulty, including example IO. |
| `/api/feedback` | GET | Fetch static structured interview feedback. |
| `/api/execute` | POST | Run Python/JS/TS/C++/Java against optional stdin and return stdout/stderr/exit code. |
| `/api/webrtc/offer` | POST | Create a signaling session (placeholder echo implementation). |
| `/api/webrtc/candidate` | POST | Push ICE candidates into the session store. |
| `/api/webrtc/session/{id}` | DELETE | Close an in-memory signaling session. |
| `/api/webrtc/session/{id}` | GET | Inspect connection stats (audio frame count, last activity). |

## Development workflow
1. Start the FastAPI server (`uvicorn app.main:app --reload`).
2. Start `npm run dev` in `mockly-frontend`.
3. The frontend issues relative `/api` requests which Vite forwards to FastAPI. Watch backend logs for request traces while verifying UI behavior.

See the per-app READMEs for deeper stack/command details.


## Detected evidence (automated analysis)

Indexed codebase: 80 recognized source files, 372 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (100 of 100)

```
.gitignore
docker-compose.yml
environment.yml
mockly-backend/.env.example
mockly-backend/.gitignore
mockly-backend/app/__init__.py
mockly-backend/app/main.py
mockly-backend/app/models.py
mockly-backend/app/routes/__init__.py
mockly-backend/app/routes/routes_audio.py
mockly-backend/app/routes/routes_execute.py
mockly-backend/app/routes/routes_feedback.py
mockly-backend/app/routes/routes_questions.py
mockly-backend/app/routes/routes_webrtc.py
mockly-backend/app/services/chatbot/__init__.py
mockly-backend/app/services/chatbot/agent.py
mockly-backend/app/services/chatbot/claude_client.py
mockly-backend/app/services/chatbot/prompts.py
mockly-backend/app/services/chatbot/tts_adapter.py
mockly-backend/app/services/webrtc_manager.py
mockly-backend/app/services/workflow/__init__.py
mockly-backend/app/services/workflow/.env.example
mockly-backend/app/services/workflow/.gitignore
mockly-backend/app/services/workflow/captions.py
mockly-backend/app/services/workflow/claude.py
mockly-backend/app/services/workflow/clients.py
mockly-backend/app/services/workflow/config.py
mockly-backend/app/services/workflow/evaluation.py
mockly-backend/app/services/workflow/example_output.json
mockly-backend/app/services/workflow/example.env
mockly-backend/app/services/workflow/frontend_example.html
mockly-backend/app/services/workflow/INTEGRATION_GUIDE.md
mockly-backend/app/services/workflow/live_transcription.py
mockly-backend/app/services/workflow/prompts.py
mockly-backend/app/services/workflow/questions.py
mockly-backend/app/services/workflow/request.json
mockly-backend/app/services/workflow/router.py
mockly-backend/app/services/workflow/speech.py
mockly-backend/app/services/workflow/SYSTEM_FLOW.txt
mockly-backend/app/services/workflow/test_live_transcription.py
mockly-backend/app/services/workflow/TRANSCRIPTION_README.md
mockly-backend/app/services/workflow/transcription.py
mockly-backend/app/services/workflow/tts.py
mockly-backend/Dockerfile
mockly-backend/poetry.lock
mockly-backend/pyproject.toml
mockly-backend/questions.yaml
mockly-backend/README.md
mockly-backend/test_questions.yaml
mockly-backend/workflow/README.md
mockly-frontend/.gitignore
mockly-frontend/Dockerfile
mockly-frontend/eslint.config.js
mockly-frontend/index.html
mockly-frontend/package.json
mockly-frontend/postcss.config.js
mockly-frontend/README.md
mockly-frontend/src/App.css
mockly-frontend/src/App.tsx
mockly-frontend/src/assets/avatar.glb
mockly-frontend/src/components/Button.tsx
mockly-frontend/src/components/ConsolePane.tsx
mockly-frontend/src/components/DifficultyDropdown.tsx
mockly-frontend/src/components/EditorPane.tsx
mockly-frontend/src/components/FeedbackView.tsx
mockly-frontend/src/components/FloatingPane.tsx
mockly-frontend/src/components/Header.tsx
mockly-frontend/src/components/Landing.tsx
mockly-frontend/src/components/LanguageDropdown.tsx
mockly-frontend/src/components/LiveTranscript.tsx
mockly-frontend/src/components/QuestionPane.tsx
mockly-frontend/src/components/Resizable.tsx
mockly-frontend/src/components/TalkingHead.tsx
mockly-frontend/src/components/TalkingHeadSync.tsx
mockly-frontend/src/hooks/useInterviewIntro.ts
mockly-frontend/src/hooks/usePushToTalk.ts
mockly-frontend/src/index.css
mockly-frontend/src/lib/audioOptimizations.ts
mockly-frontend/src/lib/cn.ts
mockly-frontend/src/lib/lipsyncController.ts
mockly-frontend/src/lib/markdown.ts
mockly-frontend/src/lib/talkingHeadPreload.ts
mockly-frontend/src/main.tsx
mockly-frontend/src/services/api.ts
mockly-frontend/src/services/audioStreamer.ts
mockly-frontend/src/services/pcmAudioPlayer.ts
mockly-frontend/src/services/voiceService.ts
mockly-frontend/src/stores/app.ts
mockly-frontend/src/stores/session.ts
mockly-frontend/src/stores/theme.ts
mockly-frontend/src/stores/voice.ts
mockly-frontend/src/types/api.ts
mockly-frontend/src/types/talkinghead.d.ts
mockly-frontend/tailwind.config.js
mockly-frontend/tsconfig.app.json
mockly-frontend/tsconfig.json
mockly-frontend/tsconfig.node.json
mockly-frontend/tsconfig.tsbuildinfo
mockly-frontend/vite.config.ts
README.md
```

### Dependencies

- mockly-backend/pyproject.toml: aiortc@^1.14.0, anthropic@^0.71.0, deepgram-sdk@^5.2.0, fastapi@^0.120.0, python-dotenv@^1.1.1, pyyaml@^6.0.3, uvicorn@^0.38.0
- mockly-frontend/package.json: @eslint/js@^9.36.0, @met4citizen/talkinghead@^1.6.0, @monaco-editor/react@^4.7.0, @radix-ui/react-icons@^1.3.2, @radix-ui/react-slot@^1.2.3, @types/node@^24.9.1, @types/react@^19.1.16, @types/react-dom@^19.1.9, @vitejs/plugin-react@^5.0.4, autoprefixer@^10.4.21, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9.36.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.22, globals@^16.4.0, lucide-react@^0.546.0, monaco-editor@^0.54.0, postcss@^8.5.6, react@^19.1.1, react-dom@^19.1.1, react-resizable@^3.0.5, tailwind-merge@^3.3.1, tailwindcss@^3.4.18, tailwindcss-animate@^1.0.7, typescript@~5.9.3, typescript-eslint@^8.45.0, vite@npm:rolldown-vite@7.1.14, zustand@^5.0.8

### Recent commits (newest first)

- Merge pull request #5 from to-ke/fix_transcript
- optimized prompt for better response, adjusted lipsync features -toke
- still attempting to fix live transcript -toke
- trying to fix live_transcription still -toke
- removed .md files
- feat:reviews code and scores based on 3 criteria -toke
- Merge pull request #4 from to-ke/api_workflow
- removed .md files -toke
- feat: basically everything (talks now and backend connected to frontend)
- still fixing interpreter issues
- fixing interpreter issues
- fixed git ignore
- Merge branch 'feat/chatbot' into api_workflow
- Merge branch 'api_workflow' of https://github.com/to-ke/mockly into api_workflow
- Merge branch 'api_workflow' of https://github.com/to-ke/mockly into feat/chatbot
- added chatbot logic and frontend-backend integration
- Live JSON for timestamp transcription of Deepgram TTS - toke
- feat(chatbot): initial chatbot service
- Chatbot -toke
- Refactored for easy integration

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

### mockly-backend/app/services/workflow/INTEGRATION_GUIDE.md

```markdown
# Live Transcription - Quick Integration Guide

## Quick Start (5 minutes)

### 1. Configure Environment Variables

Add to your `.env` file:

```bash
# Required: Your Deepgram API key
DEEPGRAM_API_KEY=your_key_here

# Enable live transcription
LIVE_TRANSCRIPTION_PATH=live_transcription.json

# Optional: Adjust update frequency (default: 2.0 seconds)
LIVE_TRANSCRIPTION_UPDATE_INTERVAL=2.0
```

### 2. Start Your Backend Server

```bash
cd mockly-backend
python -m uvicorn app.main:app --reload
```

### 3. Test the System

Send a request to any TTS endpoint:

```bash
curl -X POST http://localhost:8000/workflow/type/stream \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello, this is a test of live transcription."}' \
  --output audio.raw
```

### 4. Monitor the Output

While the audio is streaming, watch the JSON file update:

```bash
# Linux/Mac
watch -n 0.5 cat live_transcription.json

# Windows PowerShell
while($true) { Clear-Host; Get-Content live_transcription.json; Start-Sleep -Seconds 0.5 }
```

You should see something like:

```json
{
  "transcription": [
    {"word": "hello", "start_time": 0.0, "end_time": 0.28},
    {"word": "this", "start_time": 0.32, "end_time": 0.48}
  ],
  "last_updated": "2025-10-26T15:42:33.891234+00:00",
  "word_count": 2
}
```

## Integration with Existing Endpoints

The system **automatically integrates** with these endpoints when `LIVE_TRANSCRIPTION_PATH` is configured:

### `/workflow/type/stream`

Streams TTS audio with Claude text generation.

**Request:**
```bash
curl -X POST http://localhost:8000/workflow/type/stream \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Explain what a linked list is",
    "difficulty": "medium"
  }' \
  --output response.raw
```

**Result:** 
- Audio streams to the client
- `live_transcription.json` updates every N seconds with word timestamps

### `/workflow/input/stream`

Handles both text and voice input with TTS response.

**Request:**
```bash
curl -X POST http://localhost:8000/workflow/input/stream \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "text",
    "text": "What is recursion?",
    "difficulty": "easy"
  }' \
  --output response.raw
```

**Result:**
- Audio streams to the client
- Live transcription file updates automatically

### `/workflow/debug/tts`

Debug endpoint for testing TTS without Claude.

**Request:**
```bash
curl http://localhost:8000/workflow/debug/tts \
  --output test.raw
```

**Result:**
- Plays test audio
- Updates transcription file with test sentences

## Frontend Integration

### Option 1: Simple Polling (Recommended)

```javascript
// Poll the JSON file every 500ms
async function pollTranscription() {
  try {
    const response = await fetch('/live_transcription.json');
    const data = await response.json();
    displayTranscription(data.transcription);
  } catch (error) {
    console.error('Failed to fetch transcription:', error);
  }
}

// Start polling
setInterval(pollTranscription, 500);
```

### Opti
[truncated — 7236 more characters]
```

### mockly-backend/app/services/workflow/TRANSCRIPTION_README.md

```markdown
# Live Timestamped Transcription System

## Overview

This system provides real-time word-level timestamps for TTS-generated audio, creating a continuously updated JSON file that the frontend can poll for synchronized captions and subtitles.

## How It Works

```
┌─────────────────┐
│  Claude Text    │
│   Generation    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Deepgram TTS   │
│  (Text → Audio) │
└────────┬────────┘
         │
         ▼
┌─────────────────┐      ┌──────────────────┐
│  Audio Buffer   │─────▶│  Deepgram STT    │
│  (PCM 16-bit)   │      │  (Audio → Text)  │
└─────────────────┘      │  with timestamps │
                         └────────┬─────────┘
                                  │
                                  ▼
                         ┌──────────────────┐
                         │  JSON File       │
                         │  (Overwrite)     │
                         │  - word          │
                         │  - start_time    │
                         │  - end_time      │
                         └──────────────────┘
                                  │
                                  ▼
                         ┌──────────────────┐
                         │  Frontend Polls  │
                         │  for Updates     │
                         └──────────────────┘
```

### Process Flow

1. **TTS Generation**: Claude generates text, which is sent to Deepgram TTS
2. **Audio Streaming**: Audio chunks are streamed back from Deepgram
3. **Buffer Accumulation**: Audio chunks are accumulated in a buffer
4. **Periodic Transcription**: Every `N` seconds (configurable), the buffer is:
   - Sent to Deepgram STT for transcription with word-level timestamps
   - Buffer is cleared after successful transcription
5. **JSON Update**: The transcription results are written to JSON file (full overwrite)
6. **Frontend Polling**: The frontend polls the JSON file for updates

## Configuration

Add these environment variables to your `.env` file:

```bash
# Enable live transcription (set to file path)
LIVE_TRANSCRIPTION_PATH=live_transcription.json

# Update frequency in seconds (default: 2.0)
# Lower values = more frequent updates, higher API usage
# Higher values = less frequent updates, longer delay
LIVE_TRANSCRIPTION_UPDATE_INTERVAL=2.0

# Deepgram API credentials (required)
DEEPGRAM_API_KEY=your_api_key_here

# Audio configuration (defaults shown)
DEEPGRAM_SAMPLE_RATE=48000
DEEPGRAM_STREAM_ENCODING=linear16
DEEPGRAM_STT_MODEL=nova-3
```

### Configuration Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `LIVE_TRANSCRIPTION_PATH` | `live_transcription.json` | Output file path for transcription JSON |
| `LIVE_TRANSCRIPTION_UPDATE_INTERVAL` | `2.0` | Seconds between JSON updates |
| `DEEPGRAM_SAMPLE_RATE` | `48000` | Audio sample rate in Hz |
| `DEEPGRAM_STREAM_ENCODING` | `linear16` | Audio encoding format (PCM 16-bit) |
| `DEEPGRAM_STT_MODEL` | `nova-3` | Deepgram
[truncated — 9731 more characters]
```

### docker-compose.yml

```yaml
version: "3.9"

services:
  backend:
    build:
      context: ./mockly-backend
      dockerfile: Dockerfile
    env_file:
      - ./mockly-backend/.env
    environment:
      - HOST=0.0.0.0
      - PORT=8000
    ports:
      - "8000:8000"
    # DNS configuration to resolve external APIs like Deepgram
    dns:
      - 8.8.8.8
      - 8.8.4.4
    # (removed bind mount) Use the image's copied files so the built
    # image's /app (including workflow/) is used. For development you
    # can re-enable a bind mount, but ensure the host folder contains
    # the 'workflow' directory to avoid hiding the image files.
    cap_add:
      - SYS_ADMIN
      - NET_ADMIN
      - SYS_PTRACE
    security_opt:
      - seccomp=unconfined
      - apparmor=unconfined
    tmpfs:
      - /tmp
    restart: unless-stopped

  frontend:
    build:
      context: ./mockly-frontend
      dockerfile: Dockerfile
    environment:
      - VITE_USE_MOCK=false
      - VITE_BACKEND_URL=http://backend:8000
    ports:
      - "5173:5173"
    depends_on:
      - backend
    restart: unless-stopped

```

### mockly-frontend/Dockerfile

```
# syntax=docker/dockerfile:1
FROM node:20-slim

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

ENV VITE_USE_MOCK=false
EXPOSE 5173

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]

```

### mockly-backend/pyproject.toml

```
[tool.poetry]
name = "calhacks12"
version = "0.1.0"
description = "Mockly API backend"
authors = ["Dhruv Kesavarap"]
license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.120.0"
uvicorn = {extras = ["standard"], version = "^0.38.0"}
pyyaml = "^6.0.3"
aiortc = "^1.14.0"
python-dotenv = "^1.1.1"
anthropic = "^0.71.0"
deepgram-sdk = "^5.2.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

```

### mockly-backend/Dockerfile

```
# syntax=docker/dockerfile:1
FROM python:3.11-slim

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    curl \
    ca-certificates \
    nodejs \
    npm \
    default-jdk-headless \
  && rm -rf /var/lib/apt/lists/*

RUN npm install -g ts-node typescript

ENV POETRY_HOME="/opt/poetry" \
    POETRY_VERSION=1.8.3 \
    PIP_NO_CACHE_DIR=1
RUN curl -sSL https://install.python-poetry.org | python3 - && \
    ln -s /opt/poetry/bin/poetry /usr/local/bin/poetry

WORKDIR /app

COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create false && \
    poetry install --no-interaction --no-ansi --only main

COPY app ./app
COPY workflow ./workflow
COPY questions.yaml ./questions.yaml
EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### mockly-frontend/package.json

```
{
  "name": "mockly-frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@monaco-editor/react": "^4.7.0",
    "@radix-ui/react-icons": "^1.3.2",
    "@radix-ui/react-slot": "^1.2.3",
    "@met4citizen/talkinghead": "^1.6.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.546.0",
    "monaco-editor": "^0.54.0",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-resizable": "^3.0.5",
    "tailwind-merge": "^3.3.1",
    "zustand": "^5.0.8"
  },
  "devDependencies": {
    "@eslint/js": "^9.36.0",
    "@types/node": "^24.9.1",
    "@types/react": "^19.1.16",
    "@types/react-dom": "^19.1.9",
    "@vitejs/plugin-react": "^5.0.4",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.36.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.22",
    "globals": "^16.4.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.18",
    "tailwindcss-animate": "^1.0.7",
    "typescript": "~5.9.3",
    "typescript-eslint": "^8.45.0",
    "vite": "npm:rolldown-vite@7.1.14"
  },
  "overrides": {
    "vite": "npm:rolldown-vite@7.1.14"
  }
}

```

### mockly-frontend/src/main.tsx

```typescript
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'


ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

```

### mockly-frontend/src/App.tsx

```typescript
import { useState } from 'react'
import Header from '@/components/Header'
import EditorPane from '@/components/EditorPane'
import ConsolePane from '@/components/ConsolePane'
import { HorizontalResizable } from '@/components/Resizable'
import { FloatingPane } from '@/components/FloatingPane'
import { Landing } from '@/components/Landing'
import { FeedbackView } from '@/components/FeedbackView'
import { Api } from '@/services/api'
import { useSession } from '@/stores/session'
import { useAppState } from '@/stores/app'


export default function App() {
  const { stage, showFeedback, difficulty } = useAppState()
  const { language, code, setResult, running, setRunning, resetIO, lastPrompt } = useSession()
  const [ending, setEnding] = useState(false)
  const [endError, setEndError] = useState<string | null>(null)


  const run = async () => {
    if (running) return
    setRunning(true)
    resetIO()
    try {
      const res = await Api.execute({ language, source: code, timeoutMs: 4000 })
      setResult({ stdout: res.stdout, stderr: res.stderr })
    } catch (err: unknown) {
      if (err instanceof Error) {
        setResult({ stdout: '', stderr: err.message })
      } else {
        setResult({ stdout: '', stderr: String(err) })
      }
    } finally {
      setRunning(false)
    }
  }


  const stop = () => {
    // In mock mode this just cancels UI state; with FastAPI you might cancel a job id
    setRunning(false)
  }


  const endInterview = async () => {
    if (ending) return
    setEndError(null)
    setEnding(true)
    try {
      setRunning(false)
      
      // Prepare question context for evaluation
      const question = lastPrompt ? {
        prompt: lastPrompt,
        difficulty: difficulty,
      } : undefined
      
      // Fetch feedback with code, language, and question context
      const report = await Api.fetchFeedback({
        code,
        language,
        question,
      })
      showFeedback(report)
    } catch (err: unknown) {
      if (err instanceof Error) {
        setEndError(err.message)
      } else {
        setEndError(String(err))
      }
    } finally {
      setEnding(false)
    }
  }


  if (stage === 'landing') {
    return <Landing />
  }

  if (stage === 'feedback') {
    return <FeedbackView />
  }


  return (
    <div className="flex h-full flex-col">
      <Header onRun={run} onStop={stop} onEnd={endInterview} ending={ending} />
      <main className="page-transition flex-1 p-3 overflow-hidden min-h-0">
        {endError && (
          <div className="mb-3 rounded-2xl border border-destructive/40 bg-destructive/10 px-4 py-2 text-sm text-destructive">
            {endError}
          </div>
        )}
        <HorizontalResizable
          left={<EditorPane />}
          right={<ConsolePane />}
        />
      </main>
      <FloatingPane />
    </div>
  )
}

```

### mockly-backend/app/main.py

```python
import os
import sys
from pathlib import Path

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

# Ensure project root is importable before pulling in app.routes.* modules.
PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from app.routes.routes_questions import router as questions_router
from app.routes.routes_feedback import router as feedback_router
from app.routes.routes_webrtc import router as webrtc_router
from app.routes.routes_execute import router as execute_router
from app.routes.routes_audio import router as audio_router
from app.services.workflow import router as workflow_router

# Try to import the optional standalone workflow app. In some runtime
# environments (e.g., the Docker image) the top-level `workflow`
# package may not be present. We import inside a try/except and only
# mount the sub-app if the import succeeded.
try:
    import workflow.api as workflow_api
    workflow_import_error = None
except Exception as _e:  # pragma: no cover - runtime diagnostic
    workflow_api = None
    workflow_import_error = str(_e)


app = FastAPI(title="Mockly", version="0.1.0")


# Allow local dev frontends; tighten in production
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


app.include_router(questions_router)
app.include_router(feedback_router)
app.include_router(webrtc_router)
app.include_router(execute_router)
app.include_router(audio_router)
app.include_router(workflow_router)

# Mount the standalone workflow FastAPI app under /assistant so its
# endpoints (claude streaming, TTS helpers, etc.) are reachable from
# the running server. This mirrors the workflow module's previous
# standalone usage but exposes it under the main app.
if workflow_api is not None and getattr(workflow_api, "app", None) is not None:
    app.mount("/assistant", workflow_api.app)
else:
    # Provide a lightweight fallback endpoint under /assistant/debug/claude/stream
    # which uses the internal ChatbotAgent. This avoids relying on the
    # optional top-level `workflow` package being present in the image.
    from fastapi import Body, Response
    from app.services.chatbot.agent import ChatbotAgent
    from app.services.chatbot.prompts import load_question_by_difficulty

    agent = ChatbotAgent()

    @app.post("/assistant/debug/claude/stream")
    async def assistant_debug_claude_stream(payload: dict = Body(...)):
        text = (payload.get("text") or "").strip()
        if not text:
            return Response(content="", media_type="text/plain; charset=utf-8")

        # Prefer an explicit question payload from the client, otherwise allow
        # a difficulty hint to load a consistent question from questions.yaml.
        question = payload.get("question")
        if not question and payload.get("difficulty"):
            try:
                question = load_question_by_difficulty(str(payload.get("difficulty")))
            except Exception:
                question = None

        try:
            # Use the agent to get a full (non-streaming) reply for the frontend.
            reply = agent.get_text(text, question=question)
            return Response(content=reply, media_type="text/plain; charset=utf-8")
        except Exception as e:
            return Response(content=f"Error: {e}", media_type="text/plain; charset=utf-8", status_code=500)


@app.get("/assistant/_info")
def assistant_info():
    """Diagnostic endpoint to confirm the mounted workflow app and list its routes.

    This helps debug 404s by returning whether the workflow module was
    imported correctly and which paths it exposes.
    """
    try:
        subapp = getattr(workflow_api, "app", None)
        if subapp is None:
            return {"mounted": False, "reason": "workflow.api.app not found"}
        routes = []
        for r in getattr(subapp, "routes", []):
            try:
                routes.append({"path": getattr(r, "path", str(r)), "name": getattr(r, "name", None)})
            except Exception:
                routes.append({"repr": repr(r)})
        return {"mounted": True, "routes_count": len(routes), "routes": routes}
    except Exception as e:
        return {"mounted": False, "error": str(e)}


@app.get("/_assistant_info")
def assistant_info_root():
    """Root-level diagnostic that lists the mounted workflow app routes.

    This endpoint deliberately avoids the /assistant mount prefix so it
    is handled by the main app and not forwarded to the sub-application.
    """
    try:
        subapp = getattr(workflow_api, "app", None)
        if subapp is None:
            return {"mounted": False, "reason": "workflow.api.app not found"}
        routes = []
        for r in getattr(subapp, "routes", []):
            try:
                methods = []
                if hasattr(r, "methods") and r.methods:
                    methods = sorted(list(r.methods))
                routes.append({
                    "path": getattr(r, "path", None) or str(r),
                    "name": getattr(r, "name", None),
                    "methods": methods,
                })
            except Exception:
                routes.append({"repr": repr(r)})
        return {"mounted": True, "routes_count": len(routes), "routes": routes}
    except Exception as e:
        return {"mounted": False, "error": str(e)}


@app.get("/")
def root():
    return {"ok": True, "service": "Mockly"}


@app.get("/live_transcription.json")
async def get_live_transcription():
    """
    Serve the live transcription JSON file generated by the workflow system.
    """
    from fastapi.responses import FileResponse
    from pathlib import Path
    
    # Get the transcription path from workflow config
    try:
        from app.services.workflow.config import LIVE_TRANSCRIPTION_PATH
        if not LIVE_TRANSCRIPTION_PA
[truncated — 1018 more characters]
```

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