AI-powered qualitative research interviews. 3-5 adaptive questions. One structured analysis.
INSIGHT conducts short, intelligent interviews on any topic using Google Gemini. It dynamically adjusts interview depth, generates contextual follow-up questions based on your actual responses, and delivers a structured executive summary with sentiment analysis, key themes, and keyword extraction.
Built as a full-stack application with a clean four-layer backend architecture and a reactive single-page frontend.
- Dynamic Interview Depth - The AI evaluates topic complexity and selects 3-5 questions accordingly (simple topics get 3, deeply technical ones get 5).
- Sequential Contextual Questions - Each follow-up is generated from the full transcript so far, not from a pre-built list. The conversation adapts to what you actually say.
- Structured Analysis Output - Executive summary, key themes, sentiment (positive/neutral/negative) with rationale, notable quotes, and keyword extraction.
- Full Transcript Persistence - SQLite with WAL mode for concurrent access. Every session is stored and exportable as JSON.
- LLM Resilience - Every Gemini call has a hardcoded fallback. JSON sanitization strips markdown fences and formatting leaks. The app never crashes from an LLM failure.
- Session Deep-Linking - URL-based session hydration. Share or bookmark
/?session_id=xyzto return directly to a completed analysis. - Knowledge Archive - Full interview history with optimistic-delete UI and relative timestamps.
| # | Requirement | Status | Implementation |
|---|---|---|---|
| 1 | Start interview on a chosen topic | Done | POST /api/start with AI-generated topic suggestions |
| 2 | Generate 3-5 sequential AI questions | Done | evaluate_interview_depth() + one-at-a-time generation |
| 3 | Collect answers interactively | Done | Stateful POST /api/answer loop with index tracking |
| 4 | AI-generated summary at end | Done | POST /api/finish returns structured JSON analysis |
| 5 | Store transcript and summary | Done | SQLite transcripts table + JSON file export |
| Bonus | Sentiment scoring + keyword extraction | Done | Integrated in summary JSON schema |
[ Browser ] [ Server ]
| |
| POST /api/start {topic} |
|----------------------------------->|
| |--- interview_manager.py
| | |
| | |--- llm_client.py
| | | |--- evaluate_interview_depth(topic)
| | | |--- generate_first_question(topic)
| | | | |
| | | | [ Gemini API ]
| | | | |
| | |--- database.py
| | |--- create_session()
| | |--- save_question()
| {session_id, question, total} |
|<-----------------------------------|
| |
| POST /api/answer {answer, index} | x3-5 times
|----------------------------------->|
| |--- save_answer() + generate_next_question()
| {next_question, index, done} |
|<-----------------------------------|
| |
| POST /api/finish {session_id} |
|----------------------------------->|
| |--- generate_summary(topic, transcript)
| |--- save_summary() -> SQLite
| {summary: {themes, sentiment...}} |
|<-----------------------------------|
| File | Responsibility | Depends On |
|---|---|---|
app.py |
HTTP routing, validation, rate limiting | interview_manager, database, llm_client |
interview_manager.py |
Business orchestration, state machine | database, llm_client |
llm_client.py |
All Gemini API calls, JSON sanitization, fallbacks | prompts |
database.py |
SQLite persistence, WAL mode, typed queries | - |
prompts.py |
Pure prompt templates (zero logic) | - |
No LLM logic leaks into routes. No database calls in the LLM layer. No business logic in the API layer.
| Component | Role |
|---|---|
InterviewHub.tsx |
Main page: topic selection, Q&A flow, analysis view, summary display |
History.tsx |
Knowledge archive with optimistic delete and session navigation |
AppSidebar.tsx |
Collapsible sidebar with hover-expand animation |
AppLayout.tsx |
Shell layout with responsive sidebar integration |
State machine pattern: IDLE -> ACTIVE -> ANALYZING -> SUMMARY
The prompts in prompts.py are designed around four principles:
Every prompt explicitly states "generate EXACTLY ONE question". The LLM never sees all questions at once. Each question is generated independently with the full transcript context, preventing the common failure mode of dumping all questions in a single response.
All prompts end with a strict JSON schema (Respond ONLY with a valid JSON format). The Gemini API is configured with response_mime_type: 'application/json' to enforce structured output at the model level. Double protection.
Every prompt includes "STRICTLY NO MARKDOWN". On top of that, llm_client.py runs two sanitization passes:
_clean_json()- Strips markdown fences (```json) and extracts raw JSON via regex._strip_markdown()- Removes any leftover**,_,#,~symbols from the final text.
Every generate_* function has a try/except with a meaningful hardcoded fallback:
generate_first_question()falls back to"What sparked your interest in {topic}?"generate_next_question()falls back to a generic probing question.generate_summary()falls back to a complete default summary object with all required keys.- The app never returns an empty or broken response to the frontend.
| Layer | Technology | Version |
|---|---|---|
| Frontend | React | 18.3 |
| Build | Vite | 6.1 |
| Language | TypeScript | 5.7 |
| Styling | Tailwind CSS | 3.4 |
| Backend | Python + Flask | 3.12 / 3.x |
| LLM | Google Gemini | 2.5 Flash |
| Database | SQLite | WAL mode, 10s timeout |
| Rate Limiting | Flask-Limiter | 200/day, 50/hr |
- Python 3.10+
- Node.js 18+
- A Google Gemini API key
cd server
# Create virtual environment
python -m venv venv
.\venv\Scripts\Activate.ps1
# Install dependencies
pip install -r requirements.txt
# Configure environment
echo "GEMINI_API_KEY=your_key_here" > .env
# Start server (port 5000)
python app.pycd frontend
# Install dependencies
npm install
# Start dev server (port 5173, proxies /api to :5000)
npm run devNavigate to http://localhost:5173. Select a topic and start your interview.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/api/suggestions |
Get 4 AI-generated topic suggestions |
POST |
/api/start |
Start interview. Body: {topic: string} |
POST |
/api/answer |
Submit answer. Body: {session_id, index, answer} |
POST |
/api/finish |
Generate summary. Body: {session_id} |
GET |
/api/interviews |
List all completed interviews |
GET |
/api/interview/:id |
Get full transcript + summary |
DELETE |
/api/interview/:id |
Delete an interview (cascades) |
GET |
/api/transcript/:id |
Download transcript as JSON file |
The project includes a multi-layer test suite covering unit, integration, edge cases, and chaos scenarios.
cd server
# Run unit + integration tests
python -m pytest tests/test_api.py tests/test_edge_cases.py -v
# Run the full master suite (includes concurrency stress test)
python -m pytest tests/test_master_suite.py -v -k "not Frontend"
# Run E2E simulator against a live server
python tests/e2e_simulator.py
# Verify all endpoints with live Gemini
python tests/verify_all.py| Category | Coverage |
|---|---|
| API Routes | Health, start, answer, finish, transcript export |
| Input Validation | Empty topic, too-long topic, empty answer, 3000+ char answer |
| State Integrity | Double-submit prevention, invalid index rejection, non-existent session |
| LLM Resilience | Gemini timeout, malformed JSON response, 429 quota fallback |
| Concurrency | 20 simultaneous /api/start requests (SQLite WAL stress test) |
| E2E Flow | Full interview cycle: start -> 3-5 answers -> finish -> verify transcript |
sessions
id TEXT PRIMARY KEY
topic TEXT NOT NULL
current_index INTEGER DEFAULT 0
total_questions INTEGER DEFAULT 5
status TEXT DEFAULT 'ACTIVE' -- ACTIVE | FINISHED
started_at TEXT NOT NULL
qna_pairs
session_id TEXT NOT NULL -- FK -> sessions.id ON DELETE CASCADE
question_number INTEGER NOT NULL
question TEXT NOT NULL
answer TEXT
asked_at TEXT NOT NULL
answered_at TEXT
transcripts
session_id TEXT PRIMARY KEY -- FK -> sessions.id ON DELETE CASCADE
topic TEXT NOT NULL
date TEXT NOT NULL
summary_json TEXT NOT NULL
full_transcript TEXT NOT NULLMiniAI_Interviewer/
server/
app.py # Flask routes & rate limiting
interview_manager.py # Business logic orchestration
llm_client.py # Gemini API calls & fallbacks
prompts.py # Prompt templates (zero logic)
database.py # SQLite persistence layer
requirements.txt
tests/
conftest.py # Pytest fixtures
test_api.py # Route-level tests
test_edge_cases.py # Input validation & LLM failure tests
test_master_suite.py # Full integration + chaos tests
e2e_simulator.py # Live server E2E flow
verify_all.py # Quick smoke test script
frontend/
src/
App.tsx # Router setup
main.tsx # Entry point
pages/
Dashboard/
InterviewHub.tsx # Main interview UI
History.tsx # Knowledge archive
layout/
AppLayout.tsx # Shell with sidebar
AppSidebar.tsx # Collapsible navigation
components/
common/ # PageMeta, ScrollToTop, ComponentCard
form/input/ # InputField, TextArea
ui/button/ # Button component
context/
SidebarContext.tsx # Sidebar state management
icons/
index.ts # SVG icon components
vite.config.ts # Proxy config (/api -> :5000)
tailwind.config.js
package.json
Built for the Yonder technical assessment.