# Project export: Second Brain

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 2026
- Tagline: Save what you know, recall when you need.
- Devpost: https://devpost.com/software/second-brain-vrxyco
- GitHub: https://github.com/lenminh002/secondbrain
- Video: https://www.youtube.com/embed/D5ngqyYvPPs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Le Nguyen Minh (27 commits), aungkaungkhant866 (9 commits), Claude Opus 4.8 (6 commits)

## Devpost submission (written by the team)

### Inspiration

We learn something new every day, but like our memories, the best ideas can fade into forgotten tabs, buried bookmarks, book notes, research papers, and messy documents. It can be hard to connect insights across scattered materials. That’s why we built Second Brain: to help people save what they learn, support deeper research, and connect the dots between books, papers, and online knowledge.

### What it does

Second Brain saves knowledge from the web and lets you recall it when you need it. Users can store useful content, organize it, and retrieve relevant information quickly instead of searching from scratch again.

### How we built it

We built Second Brain as a web app that captures knowledge from online sources, books, papers, and notes, then stores it in a personal knowledge base. We focused on making information easy to save, search, and connect so users can recall what they learned and discover relationships across different materials.

### Challenges we ran into

The hardest part was turning messy online information into something useful and easy to retrieve. We also had to balance speed, simplicity, and accuracy so the product felt natural to use.

### Accomplishments we're proud of

One of the biggest challenges was turning scattered information from websites, books, papers, and notes into something organized and useful. We also ran into challenges setting up the agent workflow so it could process knowledge, support recall, and connect related ideas in a helpful way without making the experience feel complicated.

### What we learned

We learned that building a useful second brain is not just about storing information. The real value comes from helping people recall what they already know and connect ideas across different sources. On the technical side, we learned more about building agent workflows, structuring knowledge, handling retrieval, and designing a system that can turn scattered content into useful context.

### What's next

Next, we want to improve Second Brain with smarter recall, stronger connections between ideas, and better support for books, papers, and long-form research. We also want to expand the agent workflow so it can reason across saved knowledge, suggest related materials, and help users build a clearer map of what they know.

## README (from the GitHub repository)

# Second Brain

Second Brain is a personal knowledge-base assistant. It helps users save knowledge from notes, PDFs, and links, then recall it later through search, generated summaries, a knowledge graph, and an agent chat experience.

![Second Brain Memories View](photos/memories.png)
*Memory Detail View with summary, key ideas, and source file metadata.*

![Second Brain Knowledge Graph View](photos/graph.jpg)
*Interactive Knowledge Graph connecting notes, concepts, and tags.*

![Second Brain Agent Chat View](photos/chat.png)
*Librarian Agent Chat showing tool executions, retrieved graph concepts, and streaming grounded responses.*

The app uses a FastAPI backend, a React/Vite frontend, Firebase Auth, optional Firestore persistence, Anthropic Claude for enrichment and chat, and OpenAI embeddings when configured.

## Features

- Ingest notes, PDFs, and web links.
- Convert saved material into structured memories with summaries, key ideas, claims, questions, concepts, and tags.
- Build retrieval chunks and a knowledge graph that connects related ideas.
- Chat with an agent that uses saved knowledge, citations, graph context, tool traces, and streaming responses.
- Edit saved memory content and regenerate related artifacts.
- Delete saved memories and their generated artifacts.
- Archive chat sessions back into the knowledge base.
- Store original uploaded files with GitHub.
- Run locally with in-memory demo data or persist data in Firebase Firestore.

## Tech Stack

- Python, FastAPI, Uvicorn
- TypeScript, React, Vite
- Firebase Auth, Firebase Admin SDK, Firestore
- Anthropic Claude API
- OpenAI Embeddings API
- GitHub Contents API
- Tailwind CSS, Radix UI, Vaul, Lucide React
- D3 Force, React Markdown, Remark GFM
- PyPDF, Pillow, HTTPX
- Pytest

## Install

From the project root:

```bash
uv sync
cd frontend
npm install
```

## Environment

Copy the example backend environment file:

```bash
cp .env.example .env
```

The backend can run without AI keys for local development. Without `ANTHROPIC_API_KEY`, ingestion uses local fallback enrichment. Without `OPENAI_API_KEY`, embeddings use deterministic local vectors.

Common backend variables:

```bash
ANTHROPIC_API_KEY="your_claude_key"
OPENAI_API_KEY="your_openai_key"
SECONDBRAIN_STORAGE_BACKEND=memory
SECONDBRAIN_SEED_MOCK_DATA=1
```

Create `frontend/.env` for the Vite app:

```bash
VITE_API_BASE_URL="http://localhost:8000"
VITE_FIREBASE_API_KEY="your-firebase-web-api-key"
VITE_FIREBASE_AUTH_DOMAIN="your-project.firebaseapp.com"
VITE_FIREBASE_PROJECT_ID="your-firebase-project-id"
VITE_FIREBASE_APP_ID="your-firebase-web-app-id"
```

Firebase Auth is used by the frontend for Google sign-in. The backend accepts Firebase ID tokens when present and falls back to the mock account for local demo mode.

## Run

Start the backend from the project root:

```bash
uv run python -m backend.api
```

Equivalent Uvicorn command:

```bash
uv run uvicorn backend.api:app --reload
```

Do not run `uv run api.py` from inside `backend/`; the backend imports expect the project root to be on Python's module path.

In another terminal, start the frontend:

```bash
cd frontend
npm run dev
```

The frontend runs on the Vite URL printed in the terminal and talks to `VITE_API_BASE_URL`, defaulting to `http://127.0.0.1:8000`.

## Storage

The backend defaults to seeded in-memory storage:

```bash
SECONDBRAIN_STORAGE_BACKEND=memory
SECONDBRAIN_SEED_MOCK_DATA=1
```

Use Firestore for persistent accounts, sources, chunks, posts, and graph data:

```bash
SECONDBRAIN_STORAGE_BACKEND=firestore
FIREBASE_PROJECT_ID="your-firebase-project-id"
FIREBASE_SERVICE_ACCOUNT_FILE="/absolute/path/to/service-account.json"
# or:
FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}'
```

For local Firestore emulator development:

```bash
SECONDBRAIN_STORAGE_BACKEND=firestore
FIREBASE_PROJECT_ID="secondbrain-local"
FIRESTORE_EMULATOR_HOST="127.0.0.1:8080"
```

Firestore collections used by the backend are `accounts`, `sources`, `chunks`, `posts`, and `graphs`. Records are scoped by `account_id`.

## Original File Storage

PDF uploads and scraped-link Markdown snapshots are stored outside the database, then linked from source metadata.

Original files are stored in GitHub:

```bash
ORIGINAL_FILE_STORAGE=github
GITHUB_TOKEN="your-github-token"
GITHUB_STORAGE_REPO="owner/repo"
GITHUB_STORAGE_BRANCH="main"
GITHUB_STORAGE_PATH_PREFIX="uploads"
```

GitHub storage requires a token with write access to `GITHUB_STORAGE_REPO`.

## API

- `GET /account`
- `GET /sources`
- `GET /sources/{source_id}`
- `POST /sources`
  - accepts JSON or multipart form data
  - fields: `type=note|pdf|link`, `title`, `text`, `source_url`, `file`
- `PATCH /sources/{source_id}`
  - JSON body: `{ "content": "updated memory content" }`
- `DELETE /sources/{source_id}`
  - deletes the memory and generated source artifacts
- `GET /posts`
- `GET /graph`
- `POST /chat`
  - JSON body: `{ "message": "...", "history": [] }`
- `POST /chat/stream`
  - streams server-sent events for text, tool calls, trace steps, and final citations

## CLI Ingestion

```bash
uv run python -m backend.ingest note --account-id "cli-user" --title "Transformers" --text "Self-attention connects tokens."
uv run python -m backend.ingest pdf --account-id "cli-user" --title "Paper" --file ./paper.pdf
uv run python -m backend.ingest link --account-id "cli-user" --title "Article" --source-url "https://example.com"
```

## Test

Backend tests:

```bash
uv run pytest
```

Frontend typecheck and build:

```bash
cd frontend
npm run build
```

## Documentation

More technical notes are in `documents/`:

- [Agent Workflow](documents/agent_workflow.md)
- [Ingestion Workflow](documents/ingestion_workflow.md)
- [System Architectures](documents/architectures.md)
- [Knowledge Graph](documents/knowledge_graph.md)


## Detected evidence (automated analysis)

Indexed codebase: 113 recognized source files, 560 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Firebase (technology) — detected in the code
- HTML (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
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Cursor — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 227)

```
.claude/rules/rocketride.md
.cursor/rules/rocketride.mdc
.env.example
.gitignore
.rocketride/docs/ROCKETRIDE_COMMON_MISTAKES.md
.rocketride/docs/ROCKETRIDE_COMPONENT_REFERENCE.md
.rocketride/docs/ROCKETRIDE_OBSERVABILITY.md
.rocketride/docs/ROCKETRIDE_PIPELINE_RULES.md
.rocketride/docs/ROCKETRIDE_python_API.md
.rocketride/docs/ROCKETRIDE_QUICKSTART.md
.rocketride/docs/ROCKETRIDE_README.md
.rocketride/docs/ROCKETRIDE_typescript_API.md
.rocketride/schema/accessibility_describe.json
.rocketride/schema/agent_crewai_manager.json
.rocketride/schema/agent_crewai_subagent.json
.rocketride/schema/agent_crewai.json
.rocketride/schema/agent_deepagent_subagent.json
.rocketride/schema/agent_deepagent.json
.rocketride/schema/agent_langchain.json
.rocketride/schema/agent_rocketride.json
.rocketride/schema/anonymize_text.json
.rocketride/schema/astra_db.json
.rocketride/schema/audio_player.json
.rocketride/schema/audio_transcribe.json
.rocketride/schema/audio_tts.json
.rocketride/schema/chat.json
.rocketride/schema/chroma.json
.rocketride/schema/db_clickhouse.json
.rocketride/schema/db_mysql.json
.rocketride/schema/db_neo4j.json
.rocketride/schema/db_postgres.json
.rocketride/schema/db_supabase.json
.rocketride/schema/dictionary.json
.rocketride/schema/dropper.json
.rocketride/schema/elasticsearch.json
.rocketride/schema/embedding_image.json
.rocketride/schema/embedding_openai.json
.rocketride/schema/embedding_transformer.json
.rocketride/schema/embedding_video.json
.rocketride/schema/extract_data.json
.rocketride/schema/filesys.json
.rocketride/schema/frame_grabber.json
.rocketride/schema/guardrails.json
.rocketride/schema/hash.json
.rocketride/schema/image_cleanup.json
.rocketride/schema/image_vision_gemini.json
.rocketride/schema/image_vision_mistral.json
.rocketride/schema/image_vision_ollama.json
.rocketride/schema/image_vision_openai.json
.rocketride/schema/llamaparse.json
.rocketride/schema/llm_anthropic.json
.rocketride/schema/llm_bedrock.json
.rocketride/schema/llm_deepseek.json
.rocketride/schema/llm_gemini.json
.rocketride/schema/llm_gmi_cloud.json
.rocketride/schema/llm_minimax.json
.rocketride/schema/llm_mistral.json
.rocketride/schema/llm_nebius.json
.rocketride/schema/llm_ollama.json
.rocketride/schema/llm_openai_api.json
.rocketride/schema/llm_openai.json
.rocketride/schema/llm_perplexity.json
.rocketride/schema/llm_qwen.json
.rocketride/schema/llm_xai.json
.rocketride/schema/local-text-output.json
.rocketride/schema/mcp_client.json
.rocketride/schema/memory_internal.json
.rocketride/schema/memory_persistent.json
.rocketride/schema/milvus.json
.rocketride/schema/mongodb_srv.json
.rocketride/schema/ner.json
.rocketride/schema/ocr.json
.rocketride/schema/opensearch.json
.rocketride/schema/parse.json
.rocketride/schema/pinecone.json
.rocketride/schema/postgres.json
.rocketride/schema/preprocessor_code.json
.rocketride/schema/preprocessor_langchain.json
.rocketride/schema/preprocessor_llm.json
.rocketride/schema/prompt.json
.rocketride/schema/qdrant.json
.rocketride/schema/question.json
.rocketride/schema/reducto.json
.rocketride/schema/remote.json
.rocketride/schema/rerank_cohere.json
.rocketride/schema/response_answers.json
.rocketride/schema/response_audio.json
.rocketride/schema/response_documents.json
.rocketride/schema/response_image.json
.rocketride/schema/response_questions.json
.rocketride/schema/response_table.json
.rocketride/schema/response_text.json
.rocketride/schema/response_video.json
.rocketride/schema/search_exa.json
.rocketride/schema/summarization.json
.rocketride/schema/telegram.json
.rocketride/schema/text-output.json
.rocketride/schema/thumbnail.json
.rocketride/schema/tool_bland_ai.json
.rocketride/schema/tool_butterbase.json
.rocketride/schema/tool_chartjs.json
.rocketride/schema/tool_exa_search.json
.rocketride/schema/tool_filesystem.json
.rocketride/schema/tool_firecrawl.json
.rocketride/schema/tool_git.json
.rocketride/schema/tool_github.json
.rocketride/schema/tool_http_request.json
.rocketride/schema/tool_pipe.json
.rocketride/schema/tool_python.json
.rocketride/schema/tool_tavily.json
.rocketride/schema/tool_xtrace_memory.json
.rocketride/schema/twelvelabs.json
.rocketride/schema/weaviate.json
.rocketride/schema/webhook.json
.rocketride/services-catalog.json
backend/__init__.py
backend/.claude/rules/rocketride.md
backend/.cursor/rules/rocketride.mdc
backend/.gitignore
backend/api.py
[107 more files omitted for size]
```

### Dependencies

- frontend/package.json: @radix-ui/react-avatar@^1.2.0, @radix-ui/react-dialog@^1.1.17, @radix-ui/react-scroll-area@^1.2.12, @radix-ui/react-separator@^1.1.10, @radix-ui/react-tabs@^1.1.15, @radix-ui/react-tooltip@^1.2.10, @tailwindcss/typography@^0.5.20, @tailwindcss/vite@^4.3.1, @types/d3-force@^3.0.10, @types/react@^19.0.0, @types/react-dom@^19.0.0, @vitejs/plugin-react@^5.0.0, class-variance-authority@^0.7.1, clsx@^2.1.1, d3-force@^3.0.0, firebase@^12.15.0, lucide-react@^1.21.0, react@^19.0.0, react-dom@^19.0.0, react-markdown@^10.1.0, remark-gfm@^4.0.1, tailwind-merge@^3.6.0, tailwindcss@^4.3.1, typescript@^5.8.0, vaul@^1.1.2, vite@^7.0.0
- pyproject.toml: anthropic, fastapi, firebase-admin, google-api-python-client, google-auth, google-auth-httplib2, httpx, pillow, pypdf, python-dotenv@>=1.2.2, python-multipart, uvicorn[standard]

### Recent commits (newest first)

- update readme
- feat: delete memory
- Latest commit
- Make Memories page header scrollable instead of sticky
- Replace tab list with an icon toggle button in the Memories header, defaulting to Vault
- Filter stopwords and prioritize title words in fallback concept extraction
- Hide browser scrollbar in Memories page details view
- Hide browser scrollbar in Home page feed wrapper
- Merge Vault and AI Chat on right sidebar for Memories page, always shown, with mobile Vault drawer trigger
- Enhance timeline post body to dynamically compile summary, key ideas, concepts, and full study guide markdown
- feat: parse ingestion response using XML format to prevent JSON parsing errors on large Markdown posts
- feat: use Recall-First Full Summary Generator skill for ingested posts and render posts as Markdown in frontend
- feat: add optional thumbnail link to PDF ingest, store in db, and render in timeline posts
- style: remove Vault sidebar in Graph view and use explicit mt-6 margin spacing in NotesView sidebar
- style: add space-y-2 between Vault title and category titles in Memories sidebar
- feat: make AI generated posts highly informative, detailed and longer in enrichment prompt
- update README
- style: remove grid layout from IngestSourceDrawer to make form width full
- style: remove what happens next card and align ChatPanel items
- feat: change ingest button to bottom drawer, rename project to Second Brain, update button hovers, and clean up layouts

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

### documents/architectures.md

```markdown
# System Architecture

```mermaid
flowchart LR
    A[Frontend: Vite + React] <-->|REST API / SSE| B[Backend: FastAPI]
    B <--> C{Storage Backend}
    C -->|Default| D[In-Memory Mock]
    C -->|Optional| E[Firebase Firestore]
    B -->|PDF Storage| F[Google Drive]
    B <-->|Enrichment & Agent| G[Anthropic Claude API]
    B <-->|Text Embeddings| H[OpenAI API]
```

SecondBrain is built with a decoupled frontend and backend architecture, utilizing external APIs for LLM capabilities, embeddings, and optional cloud storage.

## Components

### Frontend
- **Framework**: React via Vite.
- **Role**: Provides the user interface for uploading documents, viewing the knowledge graph, and chatting with the agent.

### Backend
- **Framework**: FastAPI (Python).
- **Role**: Serves the REST API endpoints, handles ingestion pipelines, chunking, embedding generation, and LLM agent orchestration.

### AI Integration
- **LLM**: Anthropic Claude API for rich text extraction, metadata generation (summaries, concepts), and the conversational agent. (Includes local fallback).
- **Embeddings**: OpenAI API for vectorizing text chunks for semantic search, and powering the Knowledge Graph's Semantic Entity Resolution. (Includes deterministic local fallback).

### Storage Layer
- **Default**: In-memory mock data (designed for local hackathon/MVP development).
- **Cloud Backend**: Google Firebase Firestore can be configured via environment variables to persist accounts, sources, chunks, posts, and graph edges.
- **File Storage**: Google Drive is used for storing the original raw PDFs.

## Deployment Strategy
The app is designed to run locally with `uvicorn` and `npm run dev` but can easily be deployed to platforms like Render, Heroku, or Google Cloud Run, provided the appropriate environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `FIREBASE_SERVICE_ACCOUNT_JSON`, etc.) are configured.

```

### documents/ingestion_workflow.md

```markdown
# Ingestion Workflow

```mermaid
flowchart TD
    A[Raw Source: Note or PDF] --> B{Source Type?}
    B -->|Note| C[Read Text]
    B -->|PDF| D[Upload to Google Drive]
    D --> E[Extract Text from PDF]
    C --> F[Enrichment via LLM]
    E --> F
    F --> G[Generate Structured Data: Summary, Concepts, etc.]
    G --> H[Text Chunking]
    G --> P[Extract Social Post]
    H --> I[Embeddings Generation via OpenAI]
    I --> J[Store Chunks & Embeddings]
    P --> N[Publish to Timeline Feed]
    G --> K[Embed Concepts & Generate Graph]
    K --> L[Store Knowledge Graph]
```

The ingestion workflow is responsible for taking a raw source (text note or PDF document) and converting it into a rich, queryable format within the SecondBrain.

## Stages of Ingestion

1. **Validating**: Verifies the input format (note text or PDF file upload) and target account.
2. **Uploading**: If the source is a PDF, the raw file is uploaded to Google Drive. The Drive link is saved as metadata.
3. **Extracting/Reading Text**:
   - Note: The raw text is read directly.
   - PDF: The text is extracted from the uploaded file bytes.
4. **Enriching**: The raw content is sent to an LLM (Claude) to generate structured memory:
   - Summary
   - Key Ideas
   - Concepts
   - Claims
   - Questions
   - A generated social media post
5. **Embedding**: The enriched sections and raw notes are chunked into smaller pieces (e.g., max 1400 characters). These chunks are then embedded using an embedding model (e.g., OpenAI text-embedding model or local fallback) to create vector representations for semantic search.
6. **Graphing**: Extracted concepts are converted into vector embeddings. The backend then uses semantic similarity to merge them into existing concept nodes, preventing fragmentation. Finally, the source document is linked to these concepts and tags, creating a clean web of interconnected knowledge.
7. **Complete**: The source is marked as "ready" and is available for retrieval by the chat agent and viewing in the UI.

```

### pyproject.toml

```
[project]
name = "Second-Brain"
version = "0.1.0"
description = "Personal knowledge-base ingestion and assistant prototype."
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
  "anthropic",
  "fastapi",
  "firebase-admin",
  "google-api-python-client",
  "google-auth",
  "google-auth-httplib2",
  "httpx",
  "pillow",
  "pypdf",
  "python-dotenv>=1.2.2",
  "python-multipart",
  "uvicorn[standard]",
]

[dependency-groups]
dev = [
  "pytest",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]

```

### frontend/package.json

```
{
  "name": "second-brain-frontend",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "tsc --noEmit && vite build",
    "preview": "vite preview --host 0.0.0.0"
  },
  "dependencies": {
    "@radix-ui/react-avatar": "^1.2.0",
    "@radix-ui/react-dialog": "^1.1.17",
    "@radix-ui/react-scroll-area": "^1.2.12",
    "@radix-ui/react-separator": "^1.1.10",
    "@radix-ui/react-tabs": "^1.1.15",
    "@radix-ui/react-tooltip": "^1.2.10",
    "@tailwindcss/typography": "^0.5.20",
    "@tailwindcss/vite": "^4.3.1",
    "@types/d3-force": "^3.0.10",
    "@vitejs/plugin-react": "^5.0.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "d3-force": "^3.0.0",
    "firebase": "^12.15.0",
    "lucide-react": "^1.21.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-markdown": "^10.1.0",
    "remark-gfm": "^4.0.1",
    "tailwind-merge": "^3.6.0",
    "tailwindcss": "^4.3.1",
    "vaul": "^1.1.2",
    "vite": "^7.0.0"
  },
  "devDependencies": {
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "^5.8.0"
  }
}

```

### frontend/src/main.tsx

```typescript
import React from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
import App from "./App";
import { AuthProvider } from "@/hooks/useAuth";

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element #root was not found.");
}

createRoot(rootElement).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
);

```

### frontend/src/App.tsx

```typescript
import { useMemo, useState } from "react";
import { Bot, FileText } from "lucide-react";

import { MobileNav, SidebarNav, TopBar } from "@/components/navigation";
import { AddSelectionToChat } from "@/components/AddSelectionToChat";
import { ChatPanel } from "@/components/ChatPanel";
import { IngestSourceDrawer } from "@/components/IngestSourceDrawer";
import { HomeAside, HomeView } from "@/components/HomeView";
import { NotesView } from "@/components/NotesView";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { StatusBadge } from "@/components/StatusBadge";
import { cn } from "@/lib/utils";
import { formatDate } from "@/lib/format";
import type { ActiveView, NotesMode, SourceRecord, SourceType } from "@/types";

import { useKnowledgeBase } from "@/hooks/useKnowledgeBase";
import { useSourceIngestion } from "@/hooks/useSourceIngestion";
import { useChatSession } from "@/hooks/useChatSession";
import { useAuth } from "@/hooks/useAuth";
import { LoginView } from "@/components/LoginView";

export default function App() {
  const { user, loading, loginWithGoogle } = useAuth();

  if (loading) {
    return (
      <div className="flex h-screen w-screen items-center justify-center bg-[#0d0f14]">
        <div className="h-10 w-10 animate-spin rounded-full border-4 border-indigo-500 border-t-transparent"></div>
      </div>
    );
  }

  if (!user) {
    return <LoginView onLogin={loginWithGoogle} />;
  }

  return <AuthenticatedApp />;
}

function AuthenticatedApp() {
  const [activeView, setActiveView] = useState<ActiveView>("home");
  const [notesMode, setNotesMode] = useState<NotesMode>("note");
  const [isIngestOpen, setIsIngestOpen] = useState(false);
  const isSidebarMinimized = false;
  const [memoriesSidebarTab, setMemoriesSidebarTab] = useState<"chat" | "vault">("vault");

  const {
    account,
    sources,
    posts,
    graph,
    selectedSourceId,
    setSelectedSourceId,
    selectedSourceDetail,
    setSelectedSourceDetail,
    notice,
    setNotice,
    refresh,
    refreshWithNotice,
    isLoading,
  } = useKnowledgeBase();

  const {
    activeType,
    setActiveType,
    title,
    setTitle,
    noteText,
    setNoteText,
    pdfFile,
    setPdfFile,
    thumbnailUrl,
    setThumbnailUrl,
    isSubmitting,
    ingestProgress,
    submitSource,
  } = useSourceIngestion({
    refresh,
    setSelectedSourceId,
    setActiveView,
    setNotesMode,
    setNotice,
    onSuccess: () => setIsIngestOpen(false),
  });

  const {
    chatInput,
    setChatInput,
    chatLog,
    isChatting,
    isChatMinimized,
    setIsChatMinimized,
    submitChat,
    clearChatHistory,
    archiveAndClearChatHistory,
    isArchivingChat,
    chatArchiveError,
  } = useChatSession({ onArchiveComplete: refresh });

  const sourcesByType = useMemo(() => {
    return sources.reduce<Record<SourceType, SourceRecord[]>>(
      (groups, source) => {
        groups[source.type].push(source);
        return groups;
      },
      { note: [], pdf: [] },
    );
  }, [sources]);

  const readyCount = sources.filter((source) => source.status === "ready").length;
  const conceptCount = graph.nodes.filter((node) => node.type === "concept").length;
  const accountPosts = useMemo(() => {
    return account ? posts.filter((post) => post.account_id === account.id) : posts;
  }, [account, posts]);

  const chatPanel = (
    <ChatPanel
      chatInput={chatInput}
      chatLog={chatLog}
      isChatting={isChatting}
      setChatInput={setChatInput}
      submitChat={submitChat}
      clearChatHistory={clearChatHistory}
      archiveChatHistory={archiveAndClearChatHistory}
      isArchivingChat={isArchivingChat}
      chatArchiveError={chatArchiveError}
      isMinimized={activeView === "notes" ? false : isChatMinimized}
      toggleMinimize={activeView === "notes" ? undefined : () => setIsChatMinimized((v) => !v)}
    />
  );

  function addSelectionToChat(quotedText: string) {
    setChatInput((current) => current.trim() ? `${current.trim()}\n\n${quotedText}` : quotedText);
    setIsChatMinimized(false);
    if (activeView === "notes") {
      setMemoriesSidebarTab("chat");
    } else {
      setActiveView("chat");
    }
  }

  return (
    <TooltipProvider>
      <div className="app-frame pb-20 lg:pb-0">
        <AddSelectionToChat onAdd={addSelectionToChat} />
        <TopBar account={account} />
        <div
          className={activeView === "home" ? "social-grid" : activeView === "ingest" ? "ingest-grid" : activeView === "chat" ? "chat-grid" : "notes-grid"}
          style={{
            ["--sidebar-width" as string]: isSidebarMinimized ? "72px" : "260px",
            ["--chat-width" as string]: (activeView === "notes" ? false : isChatMinimized) ? "48px" : "360px"
          }}
        >
          <SidebarNav
            account={account}
            activeView={activeView}
            notesMode={notesMode}
            setActiveView={setActiveView}
            setNotesMode={setNotesMode}
            isMinimized={isSidebarMinimized}
            onIngestClick={() => setIsIngestOpen(true)}
          />

          {activeView === "home" ? (
            <HomeView
              account={account}
              notice={notice}
              posts={accountPosts}
              refresh={refreshWithNotice}
              setActiveView={setActiveView}
              onIngestClick={() => setIsIngestOpen(true)}
              isLoading={isLoading}
            />
          ) : activeView === "chat" ? (
            <div className="h-[calc(100vh-74px)]">
              <ChatPanel
                chatInput={chatInput}
                chatLog={chatLog}
                isChatting={isChatting}
                setChatInput={setChatInput}
                submitChat={submitChat}
                clearC
[truncated — 6150 more characters]
```

### frontend/src/components/navigation/index.ts

```typescript
export { Logo } from "./Logo";
export { TopBar } from "./TopBar";
export { SidebarNav } from "./SidebarNav";
export { MobileNav } from "./MobileNav";

```

### frontend/src/components/graph/index.ts

```typescript
export { GraphEmptyState } from "./GraphEmptyState";
export { GraphToolbar } from "./GraphToolbar";
export { GraphEdges } from "./GraphEdges";
export { GraphNodes } from "./GraphNodes";
export { GraphCanvas } from "./GraphCanvas";
export { NodeDetailPanel } from "./NodeDetailPanel";

```

### backend/__init__.py

```python
"""Backend package for the Second Brain API."""

```

### frontend/vite.config.ts

```typescript
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": "/src",
    },
  },
  server: {
    allowedHosts: ["second-brain.loca.lt"],
  },
});

```

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