# Project export: RightsNow

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: CruzHacks 2025
- Tagline: We'll handle your legal concerns. Right here, RightsNow.
- Devpost: https://devpost.com/software/rightsnow
- GitHub: https://github.com/ashwinprabou/RightsNow
- Team: 2 GitHub contributor(s) — Ashwin Prabou (12 commits), Atharv Gupta (8 commits)

## Devpost submission (written by the team)

### Inspiration

Our journey began with personal experiences of uncertainty following car accidents—moments when neither we nor our friends knew how to proceed legally. Recognizing the anxiety and potential for even worse consequences in such situations, we set out to create a solution that brings calm and clarity when it’s needed most. What It Does RightsNow empowers you to quickly and easily get legal advice during stressful moments. Simply submit your legal inquiry, and our AI Legal Advisor, Madz, delivers clear, high-level guidance on your rights and the best actions to take. Whether you’re dealing with immediate concerns or seeking preventative insight, RightsNow is designed to help you navigate tough situations with confidence. How We Built It The backbone of RightsNow is a robust Python backend that leverages the power of OpenAI Whisper and the Gemini API model to process voice memos and transform them into actionable legal insights. On the frontend, a modern React framework ensures a seamless and engaging user experience. This integration of advanced voice transcription and user-friendly design allows you to focus on your situation without worrying about the technology behind it. Challenges We Faced Integrating multiple components—from voice transcription to delivering reliable AI responses—presented significant challenges. Coordinating between the frontend and backend meant that even small components required rigorous QA testing to ensure consistency across the entire platform. These challenges, however, only strengthened our commitment to creating a reliable and intuitive service. Accomplishments We’re Proud Of We’re especially proud of the intuitive UI/UX design of RightsNow. The interface is clean, straightforward, and user-friendly, ensuring that users of any background can easily navigate the platform and understand the instructions provided. The positive feedback on our design reaffirms our belief that simplicity and clarity are key to effective communication. What We Learned Throughout the development process, we deepened our expertise in integrating voice memo functionalities with the Gemini API. Testing the system not only enhanced our technical capabilities but also provided us with valuable insights into handling real-life legal emergencies. These lessons have prepared us to continuously improve and adapt our service in the face of new challenges. What’s Next for RightsNow Our vision for the future is to enhance accessibility and immediacy. We are working towards integrating advanced voice-to-text capabilities so that you can interact with RightsNow as naturally as speaking to an advisor on speed-dial. This next step is part of our ongoing commitment to ensure you have immediate access to legal guidance, precisely when you need it most.

## README (from the GitHub repository)

## Inspiration
Our journey began with personal experiences of uncertainty following car accidents—moments when neither we nor our friends knew how to proceed legally. Recognizing the anxiety and potential for even worse consequences in such situations, we set out to create a solution that brings calm and clarity when it’s needed most.

## What It Does
RightsNow empowers you to quickly and easily get legal advice during stressful moments. Simply submit your legal inquiry, and our AI Legal Advisor, Madz, delivers clear, high-level guidance on your rights and the best actions to take. Whether you’re dealing with immediate concerns or seeking preventative insight, RightsNow is designed to help you navigate tough situations with confidence.

## How We Built It
The backbone of RightsNow is a robust Python backend that leverages the power of OpenAI Whisper and the Gemini API model to process voice memos and transform them into actionable legal insights. On the frontend, a modern React framework ensures a seamless and engaging user experience. This integration of advanced voice transcription and user-friendly design allows you to focus on your situation without worrying about the technology behind it.

## Challenges We Faced
Integrating multiple components—from voice transcription to delivering reliable AI responses—presented significant challenges. Coordinating between the frontend and backend meant that even small components required rigorous QA testing to ensure consistency across the entire platform. These challenges, however, only strengthened our commitment to creating a reliable and intuitive service.

## Accomplishments We’re Proud Of
We’re especially proud of the intuitive UI/UX design of RightsNow. The interface is clean, straightforward, and user-friendly, ensuring that users of any background can easily navigate the platform and understand the instructions provided. The positive feedback on our design reaffirms our belief that simplicity and clarity are key to effective communication.

## What We Learned
Throughout the development process, we deepened our expertise in integrating voice memo functionalities with the Gemini API. Testing the system not only enhanced our technical capabilities but also provided us with valuable insights into handling real-life legal emergencies. These lessons have prepared us to continuously improve and adapt our service in the face of new challenges.

## What’s Next for RightsNow
Our vision for the future is to enhance accessibility and immediacy. We are working towards integrating advanced voice-to-text capabilities so that you can interact with RightsNow as naturally as speaking to an advisor on speed-dial. This next step is part of our ongoing commitment to ensure you have immediate access to legal guidance, precisely when you need it most.


## Detected evidence (automated analysis)

Indexed codebase: 30 recognized source files, 77 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (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 (40 of 40)

```
.gitignore
gemini_wrapper/gemini_prompt.py
gemini_wrapper/main.py
gemini_wrapper/record.py
gemini_wrapper/requirements_ios.txt
gemini_wrapper/requirements.txt
gemini_wrapper/transcribe.py
j-league/App.tsx
j-league/app/app.css
j-league/app/global.css
j-league/app/index.css
j-league/app/root.tsx
j-league/app/routes.ts
j-league/app/routes/App.tsx
j-league/components-css/ActionWizard.css
j-league/components-css/ChatInterface.css
j-league/components-css/LocalResources.css
j-league/components-css/main.css
j-league/components-css/Summary.css
j-league/components-css/TopicSelector.css
j-league/components/ActionWizard.tsx
j-league/components/ChatInterface.tsx
j-league/components/LocalResources.tsx
j-league/components/main.tsx
j-league/components/Summary.tsx
j-league/components/TopicSelector.tsx
j-league/components/vite-env.d.ts
j-league/Dockerfile
j-league/package.json
j-league/react-router.config.ts
j-league/README.md
j-league/tailwind.config.js
j-league/tsconfig.app.json
j-league/tsconfig.json
j-league/tsconfig.node.json
j-league/tsconfigs.json
j-league/vite copy.config.ts
j-league/vite.config.ts
README.md
vercel.json
```

### Dependencies

- gemini_wrapper/requirements.txt: fastapi, google-generativeai, openai-whisper, pydantic, pydub, python-dotenv, scipy, sounddevice, torch, uvicorn
- j-league/package.json: @heroui/react@^2.7.6, @react-router/dev@^7.5.0, @react-router/node@^7.5.0, @react-router/serve@^7.5.0, @tailwindcss/vite@^4.0.0, @types/jspdf@^2.0.0, @types/node@^20, @types/react@^19.0.1, @types/react-dom@^19.0.1, @vitejs/plugin-react@^4.3.4, framer-motion@^12.6.5, isbot@^5.1.17, jspdf@^3.0.1, jspdf-autotable@^5.0.2, lucide-react@^0.487.0, react@^19.0.0, react-dom@^19.0.0, react-markdown@^10.1.0, react-router@^7.5.0, react-router-devtools@^1.1.0, react-router-dom@^7.5.0, tailwindcss@^4.0.0, typescript@^5.7.2, vite@^5.4.11, vite-tsconfig-paths@^5.1.4

### Recent commits (newest first)

- Disclaimer
- Summary Demo RightsNow
- madz commit
- vercel.json commit
- Create README.md
- MADZ
- group suicide delayed
- dsafdas
- rightsnoww!woaw
- functionality edits
- SUII
- kobev
- Merge pull request #1 from ashwinprabou/update-frontend
- Merge branch 'main' into update-frontend
- ui mostly done
- Added topic api calls
- ChatInterface fixed
- Co-authored-by: Atharv Gupta <AtharvG-124@users.noreply.github.com>
- CONNECTED!!
- init commit

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

### gemini_wrapper/requirements.txt

```
openai-whisper
sounddevice
scipy
pydub
python-dotenv
google-generativeai
torch
fastapi
uvicorn
pydantic
```

### j-league/Dockerfile

```
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci

FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev

FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build

FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]
```

### j-league/package.json

```
{
  "name": "j-league",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "react-router build",
    "dev": "react-router dev",
    "start": "react-router-serve ./build/server/index.js",
    "typecheck": "react-router typegen && tsc"
  },
  "dependencies": {
    "@heroui/react": "^2.7.6",
    "@react-router/node": "^7.5.0",
    "@react-router/serve": "^7.5.0",
    "@types/jspdf": "^2.0.0",
    "@vitejs/plugin-react": "^4.3.4",
    "framer-motion": "^12.6.5",
    "isbot": "^5.1.17",
    "jspdf": "^3.0.1",
    "jspdf-autotable": "^5.0.2",
    "lucide-react": "^0.487.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-markdown": "^10.1.0",
    "react-router": "^7.5.0",
    "react-router-dom": "^7.5.0"
  },
  "devDependencies": {
    "@react-router/dev": "^7.5.0",
    "@tailwindcss/vite": "^4.0.0",
    "@types/node": "^20",
    "@types/react": "^19.0.1",
    "@types/react-dom": "^19.0.1",
    "react-router-devtools": "^1.1.0",
    "tailwindcss": "^4.0.0",
    "typescript": "^5.7.2",
    "vite": "^5.4.11",
    "vite-tsconfig-paths": "^5.1.4"
  }
}

```

### j-league/App.tsx

```typescript
import React, { useState } from "react";
import TopicSelector from "./components/TopicSelector";
import ChatInterface from "./components/ChatInterface";
import Summary from "./components/Summary";
import ActionWizard from "./components/ActionWizard";
import LocalResources from "./components/LocalResources";
import { CheckCircle } from "lucide-react";

interface ChatMessage {
  type: "user" | "ai";
  content: string;
  timestamp: string;
  isVoice?: boolean;
}

interface CaseDetails {
  notes: Array<{ id: string; content: string; date: string }>;
  documents: Array<{ id: string; name: string; url: string }>;
  evidence: Array<{ id: string; type: string; description: string }>;
}

const steps = [
  { id: "topic-selector", title: "Select Topic" },
  { id: "chat", title: "AI Chat" },
  { id: "local-resources", title: "Resources" },
  { id: "action-wizard", title: "Steps" },
  { id: "summary", title: "Summary" },
];

function App() {
  const [currentView, setCurrentView] = useState("topic-selector");
  const [topic, setTopic] = useState("");
  const [location, setLocation] = useState("San Francisco, CA");
  const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
  const [caseDetails, setCaseDetails] = useState<CaseDetails>({
    notes: [],
    documents: [],
    evidence: [],
  });

  const handleTopicSelect = (selectedTopic: string) => {
    setTopic(selectedTopic);
    setCurrentView("chat");
  };

  const handleChatComplete = () => {
    setCurrentView("local-resources");
  };

  const handleResourcesComplete = () => {
    setCurrentView("action-wizard");
  };

  const handleActionComplete = () => {
    setCurrentView("summary");
  };

  const handleGoHome = () => {
    setCurrentView("topic-selector");
    setTopic("");
    setChatHistory([]);
    setCaseDetails({
      notes: [],
      documents: [],
      evidence: [],
    });
  };

  const currentStepIndex = steps.findIndex(step => step.id === currentView);

  return (
    <div className="min-h-screen bg-background text-foreground overflow-hidden">
      <div className="max-w-3xl mx-auto p-4 h-screen flex flex-col overflow-hidden">
        {/* Progress Steps */}
        <div className="flex justify-center items-center gap-4 mb-6">
          {steps.map((step, index) => (
            <React.Fragment key={step.id}>
              <div className="flex flex-col items-center">
                <div
                  className={`w-8 h-8 rounded-full flex items-center justify-center ${
                    index <= currentStepIndex
                      ? "bg-primary text-white"
                      : "bg-muted text-muted-foreground"
                  }`}
                >
                  {index < currentStepIndex ? (
                    <CheckCircle className="h-4 w-4" />
                  ) : (
                    index + 1
                  )}
                </div>
                <span
                  className={`text-xs mt-1 ${
                    index <= currentStepIndex ? "text-foreground" : "text-muted"
                  }`}
                >
                  {step.title}
                </span>
              </div>
              {index < steps.length - 1 && (
                <div
                  className={`w-8 h-0.5 ${
                    index < currentStepIndex ? "bg-primary" : "bg-muted"
                  }`}
                />
              )}
            </React.Fragment>
          ))}
        </div>

        {currentView === "topic-selector" && (
          <TopicSelector
            onSelect={handleTopicSelect}
            onVoiceRecord={() => {}}
            chatHistory={chatHistory}
            setChatHistory={setChatHistory}
          />
        )}
        {currentView === "chat" && (
          <ChatInterface
            topic={topic}
            onComplete={handleChatComplete}
            chatHistory={chatHistory}
            setChatHistory={setChatHistory}
          />
        )}
        {currentView === "local-resources" && (
          <LocalResources
            topic={topic}
            location={location}
            onComplete={handleResourcesComplete}
          />
        )}
        {currentView === "action-wizard" && (
          <ActionWizard
            topic={topic}
            onComplete={handleActionComplete}
            caseDetails={caseDetails}
            setCaseDetails={setCaseDetails}
          />
        )}
        {currentView === "summary" && (
          <Summary
            topic={topic}
            chatHistory={chatHistory}
            location={location}
            caseDetails={caseDetails}
            onGoHome={handleGoHome}
          />
        )}
      </div>
    </div>
  );
}

export default App;

```

### gemini_wrapper/main.py

```python
from datetime import date
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import logging
import json
from record import record_to_wav
from transcribe import transcribe_audio
from gemini_prompt import prompt_gemini
import os
from typing import Optional

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI()

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, replace with your frontend URL
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class ChatRequest(BaseModel):
    message: str
    conversation_history: str = ""
    topicId: Optional[str] = None
    description: Optional[str] = None

class ChatResponse(BaseModel):
    response: str

@app.middleware("http")
async def log_requests(request: Request, call_next):
    logger.info(f"Received {request.method} request to {request.url}")
    try:
        body = await request.body()
        if body:
            logger.info(f"Request body: {body.decode()}")
    except Exception as e:
        logger.error(f"Error reading request body: {e}")
    response = await call_next(request)
    return response

def create_context_prompt(user_input: str, conversation_history: str = "", is_follow_up: bool = False) -> str:
    today = date.today().strftime("%B %d, %Y")
    context_prompt = f"""
    You are a professional legal assistant helping those inexperienced with the law and legal issues understand their rights.
    Explain everything calmly, clearly, and in simple terms.
    The person does not know much about the law.
    It is currently {today}.

    {conversation_history}

    Here is what the student said:
    \"\"\"{user_input}\"\"\"

    In your explanation:
    - Talk about the law, legal issues, and legal rights that the person has in simple terms.
    - Then, talk about what the person should do in this situation.
    - Then, talk about what the person shouldn't do in this situation.
    - Use bullet points (`-`) for lists and add spacing between sections for readability.
    - Limit your answer to 200 words.
    Ask any follow up questions that may help the person or you out.
    """
    return context_prompt

@app.get("/")
async def health_check():
    return {"status": "ok", "message": "Server is running"}

@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    try:
        logger.info(f"Received chat request: {request.message}")
        logger.info(f"Conversation history: {request.conversation_history}")

        # If topic fields are provided, combine them with the message.
        if request.topicId and request.description:
            # You can format the topic as you see fit. Here we concatenate them.
            topic_info = f"Topic: {request.topicId} - {request.description}"
            # If the message is empty, use the topic_info as the main message;
            # if there's already a message, append it or combine as needed.
            full_input = f"{topic_info}\nUser Query: {request.message}" if request.message.strip() else topic_info
        else:
            full_input = request.message

        # Create your context prompt using the full_input (which now may contain topic data)
        context_prompt = create_context_prompt(full_input, request.conversation_history)
        logger.info(f"Created context prompt: {context_prompt}")

        # Get Gemini's response
        logger.info("Calling Gemini API...")
        response = prompt_gemini(context_prompt)
        logger.info(f"Received Gemini response: {response}")

        return ChatResponse(response=response)
    except Exception as e:
        logger.error(f"Error in chat endpoint: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/transcribe")
async def transcribe_voice(request: Request):
    try:
        logger.info("Received audio data for transcription")
        
        # Get raw bytes from request body
        audio_data = await request.body()
        logger.info(f"Received {len(audio_data)} bytes of audio data")
        
        # Save the audio data to a file with proper WAV header
        with open("temp_audio.wav", "wb") as f:
            f.write(audio_data)
        
        # Verify the file exists and has content
        if not os.path.exists("temp_audio.wav"):
            raise HTTPException(status_code=500, detail="Failed to save audio file")
        
        file_size = os.path.getsize("temp_audio.wav")
        if file_size == 0:
            raise HTTPException(status_code=500, detail="Audio file is empty")
        
        logger.info(f"Saved audio file with size: {file_size} bytes")
        
        # Transcribe the audio
        transcription = transcribe_audio("temp_audio.wav")
        logger.info(f"Transcribed text: {transcription}")
        
        return {"transcription": transcription}
    except Exception as e:
        logger.error(f"Error in transcribe endpoint: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    import uvicorn
    logger.info("Starting server...")
    uvicorn.run(app, host="0.0.0.0", port=8000)

```

### j-league/components/main.tsx

```typescript
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from '../app/routes/App';
import './index.css';

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

```

### j-league/app/routes/App.tsx

```typescript
import React, { useState } from "react";
import { Search, BookOpen, MapPin, ArrowRight, ArrowLeft } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import TopicSelector from "../../components/TopicSelector";
import ChatInterface from "../../components/ChatInterface";
import LocalResources from "../../components/LocalResources";
import ActionWizard from "../../components/ActionWizard";
import Summary from "../../components/Summary";
import "../app.css";
import "../index.css";

interface ChatMessage {
  type: "user" | "ai";
  content: string;
  timestamp: string;
  isVoice?: boolean;
}

function App() {
  const [selectedTopic, setSelectedTopic] = useState("");
  const [currentStep, setCurrentStep] = useState("topics");
  const [userLocation, setUserLocation] = useState("");
  const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
  const [showConfirmation, setShowConfirmation] = useState(false);
  const [caseDetails, setCaseDetails] = useState({
    notes: [],
    documents: [],
    evidence: [],
  });

  const steps = {
    topics: "Select Topic",
    chat: "AI Chat",
    resources: "Resources",
    wizard: "Steps",
    summary: "Summary",
  };

  const stepOrder = Object.keys(steps);

  const handleTopicSelect = (topic: string, query?: string) => {
    setChatHistory([]);
    setSelectedTopic(topic);
    setCurrentStep("chat");
  };

  const handleBack = () => {
    if (currentStep === "chat") {
      setShowConfirmation(true);
      return;
    }
    const currentIndex = stepOrder.indexOf(currentStep);
    if (currentIndex > 0) {
      setCurrentStep(stepOrder[currentIndex - 1]);
    }
  };

  const handleConfirmBack = () => {
    setShowConfirmation(false);
    setCurrentStep("topics");
    setChatHistory([]);
  };

  const handleCancelBack = () => {
    setShowConfirmation(false);
  };

  const handleGoHome = () => {
    setCurrentStep("topics");
    setSelectedTopic("");
    setChatHistory([]);
    setCaseDetails({
      notes: [],
      documents: [],
      evidence: [],
    });
  };

  const handleVoiceRecord = (audioBlob: Blob) => {
    // Here you would typically send the audio to a speech-to-text service
    // For now, we'll simulate by adding a placeholder message
    const newMessage: ChatMessage = {
      type: "user",
      content: "[Voice Recording Transcription]",
      timestamp: new Date().toISOString(),
      isVoice: true,
    };
    setChatHistory([...chatHistory, newMessage]);
  };

  return (
    <div className="min-h-screen bg-gradient-to-b from-muted to-background">
      {/* Mobile Navigation */}
      <nav className="bg-background shadow-sm fixed top-0 left-0 right-0 z-50 bottom-border">
        <div className="px-4 py-3">
          <div className="flex items-center justify-between">
            <div className="flex items-center">
              {currentStep !== "topics" && (
                <button
                  onClick={handleBack}
                  className="nav-icon text-white mr-2"
                >
                  <ArrowLeft className="h-5 w-5" />
                </button>
              )}
              <BookOpen className="h-6 w-6 text-primary" />
              <span className="ml-2 text-lg font-semibold text-foreground ">
                RightsNow
              </span>
            </div>
          </div>
        </div>
      </nav>

      {/* Confirmation Dialog */}
      {showConfirmation && (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
          <div className="card card-hover max-w-sm w-full mx-4">
            <h3 className="section-title text-primary mb-4">Confirm Exit</h3>
            <p className="text-white  mb-4">
              Are you sure you want to go back? Your chat history will not be
              saved.
            </p>
            <div className="flex justify-end space-x-3">
              <button onClick={handleCancelBack} className="btn-neutral text-white">
                Cancel
              </button>
              <button onClick={handleConfirmBack} className="btn-exit text-white">
                Exit
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Mobile Progress Steps */}
      <div className="px-4 py-3 mt-14 overflow-x-auto">
        <div className="flex space-x-4 min-w-max justify-center">
          {Object.entries(steps).map(([key, label], index) => (
            <div
              key={key}
              className={`progress-step ${
                index < Object.keys(steps).indexOf(currentStep)
                  ? "progress-step-completed"
                  : index === Object.keys(steps).indexOf(currentStep)
                  ? "progress-step-active"
                  : "progress-step-pending"
              }`}
            >
              <span className="flex items-center justify-center h-6 w-6 rounded-full border-2 border-current text-sm">
                {index + 1}
              </span>
              <span className="ml-2 text-sm">{label}</span>
              {index < Object.keys(steps).length - 1 && (
                <ArrowRight className="mx-2 h-4 w-4" />
              )}
            </div>
          ))}
        </div>
      </div>

      {/* Mobile Content Area */}
      <main className="px-4 py-4 mb-16 overflow-hidden">
        <AnimatePresence mode="wait">
          {currentStep === "topics" && (
            <motion.div
              key="topics"
              initial={{ opacity: 0, x: -20 }}
              animate={{ opacity: 1, x: 0 }}
              exit={{ opacity: 0, x: 20 }}
              transition={{ duration: 0.3 }}
            >
              <TopicSelector
                onSelect={handleTopicSelect}
                onVoiceRecord={handleVoiceRecord}
                chatHistory={chatHistory}
                setChatHistory={setChatHistory}
              />
            </motion.div>
          )}
          {currentStep === "chat" && (
            <motion.div
             
[truncated — 2206 more characters]
```

### j-league/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

```

### gemini_wrapper/transcribe.py

```python
import whisper

model = whisper.load_model("tiny.en")

def transcribe_audio(file_path):
    print("📝 Transcribing audio...")
    result = model.transcribe(file_path)
    return result["text"]

```

### j-league/react-router.config.ts

```typescript
import type { Config } from "@react-router/dev/config";

export default {
  // Config options...
  // Server-side render by default, to enable SPA mode set this to `false`
  ssr: true,
} satisfies Config;


```

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