# Project export: llmao.ai

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: UC Berkeley AI Hackathon 2025
- Tagline: LLM-Assisted Overview: Auto-generate project docs!
- Devpost: https://devpost.com/software/llmao-tx5j8p
- GitHub: https://github.com/ananya0996/llmao.ai
- Team: 4 GitHub contributor(s) — Ananya Pandey (10 commits), Aman Dwivedi (2 commits), Himanshu Nimonkar (1 commits), dhairye (1 commits)

## Devpost submission (written by the team)

### Inspiration

We were inspired by a simple but painful truth: developers hate writing documentation. It's often the last thing anyone wants to do, and even when it gets done, it quickly becomes outdated. Static documentation rarely keeps up with evolving codebases, making it hard for teams to maintain internal knowledge. This becomes a real problem when developers leave and no one understands how the system works. Onboarding new team members becomes time-consuming and frustrating. Even worse, when software products are shipped with great features but poor documentation, it leads to low adoption and support overhead. We built llmao.ai to solve this problem once and for all - by automating documentation in a way that's both accurate and useful.

### What it does

Companies can internally host an instance of llmao.ai to automate their documentation workflows. Implementation Documentation (internal to a company): The platform semantically analyzes a codebase (such as a GitHub-hosted repository), identifies relationships between modules, and captures their functionality as well as the overall system architecture. API/SDK References: It extracts the purpose, input, and output parameters of public APIs (e.g., those available through SDKs) from the repository. Developers have the option to review the generated documentation before it is published to internal knowledge bases or public platforms. Moreover, llmao.ai allows developers to interact with the documentation through AI assistants, enabling them to get specific answers to queries without reading the entire document.

### How we built it

Our application is powered by Letta Cloud, Google Gemini, v0 by Vercel and GitHub MCP Server.

### Challenges we ran into

This was all of our first time working with Letta Cloud, but it was a challenging yet rewarding experience.

### Accomplishments we're proud of

We’re proud that llmao.ai can take any GitHub repository and generate clean, readable, and technically sound documentation - both proprietary and public. The dual-documentation flow using specialized agents was an ambitious idea, and we made it work. We also built a real-time AI chat experience that can answer technical questions with context-aware precision. Finally, we designed the system to be scalable and secure, with support for self-hosting in companies - making it a practical solution for real-world development teams.

### What's next

We’re already working on several next steps. These include automatically triggering doc updates when the main branch is updated, and supporting multi-repo systems that span across several services. We also plan to generate release notes directly from pull requests, making changelogs easier to manage. We’re building integrations with popular platforms like Confluence, Notion, and Slack, so teams can push docs directly into their workflows.

## README (from the GitHub repository)

# llmao.ai
LLM-Aided Overview (LLMAO.ai) understands the semantics of your code and generates a comprehensive, up-to-date documentation for it.


## Detected evidence (automated analysis)

Indexed codebase: 79 recognized source files, 276 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- Next.js (technology) — 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
- Vercel AI SDK (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (89 of 89)

```
.gitignore
backend/.dockerignore
backend/agent_initializer.py
backend/app.py
backend/Dockerfile
backend/pyproject.toml
backend/README.md
backend/requirements.txt
README.md
ui/app/api/auth/[...nextauth]/route.ts
ui/app/api/internal-chat/route.ts
ui/app/api/public-chat/route.ts
ui/app/chat/page.tsx
ui/app/globals.css
ui/app/internal/chat/loading.tsx
ui/app/internal/chat/page.tsx
ui/app/internal/page.tsx
ui/app/layout.tsx
ui/app/page.tsx
ui/components.json
ui/components/auth-provider.tsx
ui/components/chat-interface.tsx
ui/components/documentation-panel.tsx
ui/components/keyboard-shortcuts.tsx
ui/components/search-shortcuts.tsx
ui/components/split-screen-chat.tsx
ui/components/theme-provider.tsx
ui/components/theme-toggle.tsx
ui/components/ui/accordion.tsx
ui/components/ui/alert-dialog.tsx
ui/components/ui/alert.tsx
ui/components/ui/aspect-ratio.tsx
ui/components/ui/avatar.tsx
ui/components/ui/badge.tsx
ui/components/ui/breadcrumb.tsx
ui/components/ui/button.tsx
ui/components/ui/calendar.tsx
ui/components/ui/card.tsx
ui/components/ui/carousel.tsx
ui/components/ui/chart.tsx
ui/components/ui/checkbox.tsx
ui/components/ui/collapsible.tsx
ui/components/ui/command.tsx
ui/components/ui/context-menu.tsx
ui/components/ui/dialog.tsx
ui/components/ui/drawer.tsx
ui/components/ui/dropdown-menu.tsx
ui/components/ui/form.tsx
ui/components/ui/hover-card.tsx
ui/components/ui/input-otp.tsx
ui/components/ui/input.tsx
ui/components/ui/label.tsx
ui/components/ui/menubar.tsx
ui/components/ui/navigation-menu.tsx
ui/components/ui/pagination.tsx
ui/components/ui/popover.tsx
ui/components/ui/progress.tsx
ui/components/ui/radio-group.tsx
ui/components/ui/resizable.tsx
ui/components/ui/scroll-area.tsx
ui/components/ui/select.tsx
ui/components/ui/separator.tsx
ui/components/ui/sheet.tsx
ui/components/ui/sidebar.tsx
ui/components/ui/skeleton.tsx
ui/components/ui/slider.tsx
ui/components/ui/sonner.tsx
ui/components/ui/switch.tsx
ui/components/ui/table.tsx
ui/components/ui/tabs.tsx
ui/components/ui/textarea.tsx
ui/components/ui/toast.tsx
ui/components/ui/toaster.tsx
ui/components/ui/toggle-group.tsx
ui/components/ui/toggle.tsx
ui/components/ui/tooltip.tsx
ui/components/ui/use-mobile.tsx
ui/components/ui/use-toast.ts
ui/hooks/use-mobile.tsx
ui/hooks/use-toast.ts
ui/lib/pdf-export.ts
ui/lib/utils.ts
ui/middleware.ts
ui/next.config.mjs
ui/package.json
ui/postcss.config.mjs
ui/styles/globals.css
ui/tailwind.config.ts
ui/tsconfig.json
```

### Dependencies

- backend/requirements.txt: flask@>=3.0, flask-cors@>=4.0, python-dotenv@>=0.21.0, requests@>=2.25.1
- ui/package.json: @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@1.1.1, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@latest, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @types/node@^22, @types/react@^19, @types/react-dom@^19, ai@latest, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@4.1.0, embla-carousel-react@8.5.1, input-otp@1.4.1, jspdf@latest, lucide-react@^0.454.0, next@15.2.4, next-themes@latest, postcss@^8.5, react@^19, react-day-picker@8.10.1, react-dom@^19, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@2.15.0, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5, vaul@^0.9.6, zod@^3.24.1

### Recent commits (newest first)

- Cleanup
- Merge pull request #7 from ananya0996/ui-fix
- Fixed small UI glitch
- Merge pull request #6 from ananya0996/backend-docbot-agent
- Added AI Conversational Assistant for Internal Documentation
- Merge pull request #5 from ananya0996/backend-setup
- Integrated UI, Flask and Letta Doc Generator
- Merge pull request #4 from ananya0996/backend-setup
- [Backend] Docker setup + GitHub URL -> Flask
- Merge pull request #3 from ananya0996/frontend-separate
- Reorganizing ui
- Merge pull request #2 from ananya0996/frontend-integration
- Updated frontend to connect to backend
- Merge pull request #1 from ananya0996/ui-skeleton
- [Frontend] Created the UI skeleton for llmao.ai web app
- Initial commit

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

### backend/requirements.txt

```
flask>=3.0
flask-cors>=4.0
requests>=2.25.1
python-dotenv>=0.21.0

```

### backend/Dockerfile

```
# Use slim Python image
FROM python:3.10-slim

# Set workdir
WORKDIR /app

# Copy and install dependencies
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

# Copy the rest of the app
COPY . .

# Expose the Flask port
EXPOSE 5000

# Run the app
CMD ["python", "app.py"]
```

### ui/package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "next lint",
    "start": "next start"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "1.1.1",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "latest",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "ai": "latest",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "4.1.0",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "jspdf": "latest",
    "lucide-react": "^0.454.0",
    "next": "15.2.4",
    "next-themes": "latest",
    "react": "^19",
    "react-day-picker": "8.10.1",
    "react-dom": "^19",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.6",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "^5"
  }
}
```

### backend/app.py

```python
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import os
import json

app = Flask(__name__)
CORS(app)

def get_agent_url(github_url):
    return 'https://api.letta.com/v1/agents/agent-64aac2ec-8a52-433d-a12e-e2f39771179a'

AGENT_URL = get_agent_url("")
LETTA_API_KEY = os.getenv("LETTA_API_KEY")
print(LETTA_API_KEY)

@app.route('/repo', methods=['POST'])
def handle_repo():
    data = request.get_json()

    # Check if required keys exist
    if not data or 'repoUrl' not in data or 'confUrl' not in data:
        return jsonify({"error": "Invalid input"}), 400

    repo_url = data['repoUrl'].strip()
    conf_url = data['confUrl'].strip()

    # Log or process the received URLs
    print(f"Received repoUrl: {repo_url}")
    print(f"Received confUrl: {conf_url}")

    # Build request to AGENT_URL/messages

    payload = {
        "messages": [
            {
                "role": "user",
                "content": f"Given this repo: https://github.com/ananya0996/vultra, create an internal documentation for this repo that can be used by the developers of this company? Focus on the implementation details and specifically on the details that would be a part of the internal documentation. A use case of this would be knowledge transfer. Give me a comprehensive, in-depth and detailed version of the documentation. I want the whole documentation in plain text only. Remember this very well as in the letta AI where Im giving this prompt, many things like copy code button, markdown and code is being generated. With the complete documentation which is being generated, I want the high level features to be generated too from the codebase, if there exists any, which form the basis of the code which explain the few core features of the codebase and also its purpose that the code serves. After the feature listing and everything is done stop then and there, do not generate the last sentence where you print the summary of the project."
            }
        ]
    }

    headers = {
        "Authorization": "Bearer sk-let-OWZkNmUyMzUtNGMxNC00NDFhLThlMmYtOTNiN2M3NzRhNTVmOmFkNzZiZDdhLTllYzMtNDg5My1iZTlmLWJjYzNmMzg0MzA1MQ==",
        "Content-Type": "application/json"
    }

    try:
        # Stream the response
        agent_response = requests.post(
            f"{AGENT_URL}/messages",
            headers=headers,
            json=payload,
            timeout=600,
            stream=True  # <-- Streaming enabled
        )
        agent_response.raise_for_status()

        # Read the streamed lines and combine them
        streamed_content = ""
        for line in agent_response.iter_lines():
            if line:
                decoded_line = line.decode('utf-8')
                streamed_content += decoded_line

        # Convert streamed content into JSON
        response_json = json.loads(streamed_content)

        # Extract assistant_message
        assistant_message = next(
            (msg["content"] for msg in response_json.get("messages", [])
             if msg.get("message_type") == "assistant_message"),
            None
        )

        if assistant_message:
            return jsonify({
                "assistantMessageContent": assistant_message,
                "repoUrl": repo_url,
                "confUrl": conf_url
            }), 200
        else:
            return jsonify({"error": "No assistant_message found in response"}), 500

    except Exception as e:
        return jsonify({"error": f"Failed to contact agent: {str(e)}"}), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)
```

### ui/app/layout.tsx

```typescript
import type React from "react"
import "./globals.css"
import { Inter } from "next/font/google"
import { AuthProvider } from "@/components/auth-provider"
import { ThemeProvider } from "@/components/theme-provider"

const inter = Inter({ subsets: ["latin"] })

export const metadata = {
  title: "LLMAO - Documentation Chat",
  description: "Chat with your documentation using AI",
    generator: 'v0.dev'
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider attribute="class" defaultTheme="light" enableSystem disableTransitionOnChange>
          <AuthProvider>{children}</AuthProvider>
        </ThemeProvider>
      </body>
    </html>
  )
}

```

### ui/app/page.tsx

```typescript
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { ArrowRight, Shield, Globe, Sparkles, Zap, Brain, Code } from "lucide-react"
import { ThemeToggle } from "@/components/theme-toggle"

export default function Home() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 dark:from-slate-900 dark:via-blue-900 dark:to-indigo-900">
      {/* Header */}
      <header className="border-b bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm sticky top-0 z-50">
        <div className="container mx-auto px-4 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center space-x-3">
              <div className="w-10 h-10 bg-gradient-to-r from-purple-600 via-blue-600 to-indigo-600 rounded-xl flex items-center justify-center animate-pulse">
                <Brain className="w-6 h-6 text-white" />
              </div>
              <div>
                <span className="text-2xl font-bold bg-gradient-to-r from-purple-600 via-blue-600 to-indigo-600 bg-clip-text text-transparent">
                  LLMAO
                </span>
                <p className="text-xs text-muted-foreground -mt-1">Large Language Model Aided Overview</p>
              </div>
            </div>
            <div className="flex items-center space-x-4">
              <div className="flex items-center space-x-2 text-sm text-muted-foreground">
                <Zap className="w-4 h-4 text-yellow-500" />
                <span>Powered by AI</span>
              </div>
              <ThemeToggle />
            </div>
          </div>
        </div>
      </header>

      {/* Hero Section */}
      <main className="container mx-auto px-4 py-16">
        <div className="text-center mb-16 animate-fade-in">
          <div className="inline-flex items-center space-x-2 bg-gradient-to-r from-purple-100 to-blue-100 dark:from-purple-900/30 dark:to-blue-900/30 rounded-full px-4 py-2 mb-6">
            <Sparkles className="w-4 h-4 text-purple-600 dark:text-purple-400" />
            <span className="text-sm font-medium text-purple-700 dark:text-purple-400">
              Revolutionary Documentation Experience
            </span>
          </div>

          <h1 className="text-6xl md:text-7xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-purple-900 to-blue-900 dark:from-gray-100 dark:via-purple-300 dark:to-blue-300 bg-clip-text text-transparent leading-tight">
            LLMAO
          </h1>
          <h2 className="text-2xl md:text-3xl font-semibold mb-4 text-foreground">
            Large Language Model Aided Overview
          </h2>
          <p className="text-xl text-muted-foreground mb-8 max-w-4xl mx-auto leading-relaxed">
            Experience documentation like never before. Split-screen interface with live documentation and intelligent
            AI chat. Get instant answers while browsing through your codebase and internal docs.
          </p>
        </div>

        {/* Feature Cards */}
        <div className="grid md:grid-cols-2 gap-8 max-w-5xl mx-auto mb-16">
          <Card className="group hover:shadow-2xl transition-all duration-500 hover:-translate-y-3 border-0 bg-white/70 dark:bg-slate-900/70 backdrop-blur-sm overflow-hidden">
            <div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-indigo-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
            <CardContent className="p-8 relative">
              <div className="flex items-center mb-6">
                <div className="w-14 h-14 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-2xl flex items-center justify-center mr-4 group-hover:scale-110 transition-transform duration-300 shadow-lg">
                  <Shield className="w-7 h-7 text-white" />
                </div>
                <div>
                  <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Internal Access</h3>
                  <p className="text-sm text-gray-500 dark:text-gray-400">Secure & Private</p>
                </div>
              </div>
              <p className="text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
                Secure access to private repositories and internal Confluence documentation. Split-screen interface with
                live documentation browsing and intelligent AI assistance for your development team.
              </p>
              <Link href="/internal" className="block">
                <Button className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold py-4 rounded-xl transition-all duration-300 group-hover:shadow-xl">
                  Access Internal Docs
                  <ArrowRight className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" />
                </Button>
              </Link>
            </CardContent>
          </Card>

          <Card className="group hover:shadow-2xl transition-all duration-500 hover:-translate-y-3 border-0 bg-white/70 dark:bg-slate-900/70 backdrop-blur-sm overflow-hidden">
            <div className="absolute inset-0 bg-gradient-to-br from-emerald-500/10 to-teal-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
            <CardContent className="p-8 relative">
              <div className="flex items-center mb-6">
                <div className="w-14 h-14 bg-gradient-to-r from-emerald-500 to-teal-500 rounded-2xl flex items-center justify-center mr-4 group-hover:scale-110 transition-transform duration-300 shadow-lg">
                  <Globe className="w-7 h-7 text-white" />
                </div>
                <div>
                  <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Public Documentation</h3>
                  <p className="text-sm text-gray-500 dark:text-gray-400">Open & Accessible</p>
            
[truncated — 3250 more characters]
```

### ui/app/chat/page.tsx

```typescript
"use client"

import { useChat } from "ai/react"
import { SplitScreenChat } from "@/components/split-screen-chat"

export default function PublicChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: "/api/public-chat",
    initialMessages: [
      {
        id: "system-1",
        role: "system",
        content:
          "You are LLMAO (Large Language Model Aided Overflow), an AI assistant for public documentation. Provide helpful, accurate responses based on available documentation.",
      },
    ],
    // Ensure messages persist properly
    keepLastMessageOnError: true,
  })

  return (
    <SplitScreenChat
      messages={messages}
      input={input}
      handleInputChange={handleInputChange}
      handleSubmit={handleSubmit}
      isLoading={isLoading}
      title="Public Documentation Chat"
      backUrl="/"
      isPublic={true}
    />
  )
}

```

### ui/app/internal/page.tsx

```typescript
"use client"

import type React from "react"
import { useState, useCallback } from "react"
import { useRouter } from "next/navigation"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { useEffect } from "react"
import {
  Lock,
  Github,
  FileText,
  ArrowRight,
  Eye,
  EyeOff,
  Brain,
  ArrowLeft,
  AlertCircle,
  CheckCircle,
} from "lucide-react"
import Link from "next/link"
import { ThemeToggle } from "@/components/theme-toggle"

interface Config {
  repo: string
  conf: string
  timestamp: number
}

export default function InternalPage() {
  const [repoUrl, setRepoUrl] = useState("")
  const [confUrl, setConfUrl] = useState("")
  const [assistantMessage, setAssistantMessage] = useState("")
  const [isAuthorized, setIsAuthorized] = useState(false)
  const [password, setPassword] = useState("")
  const [error, setError] = useState("")
  const [showPassword, setShowPassword] = useState(false)
  const [isLoading, setIsLoading] = useState(false)
  const [configError, setConfigError] = useState("")
  const [authLoading, setAuthLoading] = useState(true)
  const [repoUrlValid, setRepoUrlValid] = useState<boolean | null>(null)
  const [confUrlValid, setConfUrlValid] = useState<boolean | null>(null)
  const router = useRouter()

  // URL validation functions
  const validateGitHubUrl = useCallback((url: string): boolean => {
    if (!url.trim()) return false
    try {
      const urlObj = new URL(url)
      return urlObj.hostname === "github.com" && urlObj.pathname.split("/").length >= 3
    } catch {
      return false
    }
  }, [])

  const validateConfluenceUrl = useCallback((url: string): boolean => {
    if (!url.trim()) return false
    try {
      const urlObj = new URL(url)
      return urlObj.hostname.includes("atlassian.net") || urlObj.pathname.includes("/wiki")
    } catch {
      return false
    }
  }, [])

  // Real-time URL validation
  useEffect(() => {
    if (repoUrl) {
      setRepoUrlValid(validateGitHubUrl(repoUrl))
    } else {
      setRepoUrlValid(null)
    }
  }, [repoUrl, validateGitHubUrl])

  useEffect(() => {
    if (confUrl) {
      setConfUrlValid(validateConfluenceUrl(confUrl))
    } else {
      setConfUrlValid(null)
    }
  }, [confUrl, validateConfluenceUrl])

  const handleSubmit = useCallback(
    async (e: React.FormEvent) => {
      e.preventDefault()
      e.stopPropagation()

      setConfigError("")

      // Validate inputs
      if (!repoUrl.trim() || !confUrl.trim()) {
        setConfigError("Both GitHub repository and Confluence URLs are required")
        return
      }

      if (!validateGitHubUrl(repoUrl)) {
        setConfigError("Please enter a valid GitHub repository URL (e.g., https://github.com/username/repository)")
        return
      }

      if (!validateConfluenceUrl(confUrl)) {
        setConfigError("Please enter a valid Confluence URL (e.g., https://your-domain.atlassian.net/wiki)")
        return
      }

      setIsLoading(true)

      try {
        // Send repository data to Python server
        console.log("Where are you");
        const response = await fetch("http://127.0.0.1:5000/repo", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            repoUrl: repoUrl.trim(),
            confUrl: confUrl.trim(),
          }),
        })
        console.log("Works");

        const result = await response.json()

        if (!response.ok) {
          throw new Error(result.details || result.error || "Failed to send repository to Python server")
        }
        if (result && result.assistantMessageContent) {
          console.log("Received assistant message:", result.assistantMessageContent);
           // Display or use it in your UI
            setAssistantMessage(result.assistantMessageContent);
                 } else {
                   console.error("Assistant message missing in response", result);
                   }

        console.log("Successfully sent to Python server:", result)

        const config: Config = {
          repo: repoUrl.trim(),
          conf: confUrl.trim(),
          timestamp: Date.now(),
        }

        // Clear any existing config first
        sessionStorage.removeItem("llmao_config")

        // Set new config
        sessionStorage.setItem("llmao_config", JSON.stringify(config))

        // Verify it was saved
        const saved = sessionStorage.getItem("llmao_config")
        if (!saved) {
          throw new Error("Failed to save configuration")
        }

        // Navigate after a short delay to ensure state is saved
        setTimeout(() => {
          router.push("/internal/chat")
        }, 500)
      } catch (error) {
        console.error("Error during submission:", error)
        const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred"
        setConfigError(`Error: ${errorMessage}`)
        setIsLoading(false)
      }
    },
    [repoUrl, confUrl, validateGitHubUrl, validateConfluenceUrl, router],
  )

  const handleLogin = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault()
      e.stopPropagation()

      setError("")
      setIsLoading(true)

      setTimeout(() => {
        const adminPassword = process.env.NEXT_PUBLIC_ADMIN_PASSWORD || "admin123"

        if (password === adminPassword) {
          try {
            sessionStorage.setItem("isInternalAuthorized", "true")
            sessionStorage.setItem("authTimestamp", Date.now().toString())
            setIsAuthorized(true)
            setError("")
          } catch (error) {
            console.error("Failed to set authorization:", error)
            setError("Authorization failed. Please try again.")
          }
        } else {
          setError("Invalid password")
        }
        setIsLoading(false)
      }, 800)
    },
    [password],
  )

  useEffect(()
[truncated — 13322 more characters]
```

### ui/app/api/public-chat/route.ts

```typescript
// Allow streaming responses up to 30 seconds
export const maxDuration = 30

function textToStream(text: string, chunkDelay = 40) {
  const encoder = new TextEncoder()

  return new ReadableStream({
    start(controller) {
      const words = text.split(" ")
      let i = 0
      const push = () => {
        if (i < words.length) {
          controller.enqueue(encoder.encode(`0:"${words[i]}${i < words.length - 1 ? " " : ""}"\n`))
          i++
          setTimeout(push, chunkDelay)
        } else {
          controller.enqueue(encoder.encode("d:\n"))
          controller.close()
        }
      }
      push()
    },
  })
}

export async function POST(req: Request) {
  try {
    const { messages } = await req.json()

    const latestUserMsg =
      [...messages]
        .reverse()
        .find((m: any) => m.role === "user")
        ?.content?.trim() ?? ""

    if (!latestUserMsg) {
      return new Response("No content", { status: 400 })
    }

    // Use your actual API URL here instead of environment variables
    // Replace this with your real API endpoint
    const apiURL = "https://your-actual-api-endpoint.com/chat"
    const apiKey = "your-actual-api-key" // or leave empty if not needed

    // For demo purposes, let's simulate a response instead of calling external API
    const simulatedResponse = `Here's information about "${latestUserMsg}" from the public documentation. This is a simulated response that shows how your custom API would integrate. The response demonstrates the streaming typing effect and message persistence that you requested. You can replace this with your actual API logic when ready.`

    // Uncomment and modify this section when you have your real API ready:
    /*
    const upstream = await fetch(apiURL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        query: latestUserMsg,
        context: "public-documentation",
      }),
    })

    if (!upstream.ok) {
      throw new Error(`API error ${upstream.status}`)
    }

    const contentType = upstream.headers.get("content-type") || ""
    let answer = ""

    if (contentType.includes("application/json")) {
      const data = await upstream.json()
      answer = data.answer ?? data.response ?? data.text ?? "No response"
    } else {
      answer = await upstream.text()
    }
    */

    // For now, use the simulated response
    const answer = simulatedResponse

    return new Response(textToStream(answer), {
      headers: {
        "Content-Type": "text/plain; charset=utf-8",
        "X-Vercel-AI-Data-Stream": "v1",
      },
    })
  } catch (err) {
    console.error("Error in public chat:", err)
    return new Response(
      textToStream(
        "I'm sorry, I'm having trouble processing your request. This is a demo response while your custom API is being configured.",
      ),
      {
        headers: {
          "Content-Type": "text/plain; charset=utf-8",
          "X-Vercel-AI-Data-Stream": "v1",
        },
        status: 500,
      },
    )
  }
}

```

### ui/app/api/internal-chat/route.ts

```typescript
// Allow streaming responses up to 30 seconds
export const maxDuration = 30

function textToStream(text: string, chunkDelay = 30) {
  const encoder = new TextEncoder()
  return new ReadableStream({
    start(controller) {
      const words = text.split(" ")
      let i = 0
      const push = () => {
        if (i < words.length) {
          controller.enqueue(encoder.encode(`0:"${words[i]}${i < words.length - 1 ? " " : ""}"\n`))
          i++
          setTimeout(push, chunkDelay)
        } else {
          controller.enqueue(encoder.encode("d:\n"))
          controller.close()
        }
      }
      push()
    },
  })
}

export async function POST(req: Request) {
  try {
    const { messages, repo, conf } = await req.json()

    const latestUserMsg =
      [...messages]
        .reverse()
        .find((m: any) => m.role === "user")
        ?.content?.trim() ?? ""

    if (!latestUserMsg) {
      return new Response("No content", { status: 400 })
    }

    // Use your actual API URL here instead of environment variables
    // Replace this with your real API endpoint
    const apiURL = "https://your-actual-api-endpoint.com/chat"
    const apiKey = "your-actual-api-key" // or leave empty if not needed

    // For demo purposes, let's simulate a response instead of calling external API
    const simulatedResponse = `Based on your internal documentation for ${repo} and Confluence at ${conf}, here's what I found regarding: "${latestUserMsg}". This is a simulated response that demonstrates how your custom API integration would work. You can replace this logic with your actual API call.`

    // Uncomment and modify this section when you have your real API ready:
    /*
    const upstream = await fetch(apiURL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        query: latestUserMsg,
        context: "internal-documentation",
        repository: repo,
        confluence: conf,
      }),
    })

    if (!upstream.ok) {
      throw new Error(`API error ${upstream.status}`)
    }

    const contentType = upstream.headers.get("content-type") || ""
    let answer = ""

    if (contentType.includes("application/json")) {
      const data = await upstream.json()
      answer = data.answer ?? data.response ?? data.text ?? "No response"
    } else {
      answer = await upstream.text()
    }
    */

    // For now, use the simulated response
    const answer = simulatedResponse

    return new Response(textToStream(answer), {
      headers: {
        "Content-Type": "text/plain; charset=utf-8",
        "X-Vercel-AI-Data-Stream": "v1",
      },
    })
  } catch (err) {
    console.error("Error in internal chat:", err)
    return new Response(
      textToStream(
        "I'm sorry, I had trouble processing your request. This is a demo response while your custom API is being configured.",
      ),
      {
        headers: {
          "Content-Type": "text/plain; charset=utf-8",
          "X-Vercel-AI-Data-Stream": "v1",
        },
        status: 500,
      },
    )
  }
}

```

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