From 4e3285c94914d7f6ca1953f5a48deb54e9061f0c Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:02:00 -0500 Subject: [PATCH 01/33] Added CLAUDE.md file. --- .env.example | 2 -- CLAUDE.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) delete mode 100644 .env.example create mode 100644 CLAUDE.md diff --git a/.env.example b/.env.example deleted file mode 100644 index 18b34cb7..00000000 --- a/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Copy this file to .env and add your actual API key -ANTHROPIC_API_KEY=your-anthropic-api-key-here \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..0bc2e5de --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,81 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Installation & Setup +```bash +# Install dependencies using uv package manager +uv sync + +# Create .env file with required API key +echo "ANTHROPIC_API_KEY=your_key_here" > .env +``` + +### Running the Application +```bash +# Quick start with provided script +chmod +x run.sh +./run.sh + +# Manual start (preferred for development) +cd backend && uv run uvicorn app:app --reload --port 8000 +``` + +### Development Workflow +- **Frontend**: Static files served from `frontend/` directory (HTML/CSS/JS) +- **Backend**: FastAPI server with auto-reload during development +- **Database**: ChromaDB persisted locally in `backend/chroma_db/` + +## Architecture Overview + +This is a **Retrieval-Augmented Generation (RAG) system** for course materials with the following architecture: + +### Core Components +- **`RAGSystem`** (`rag_system.py`): Main orchestrator that coordinates all components +- **`VectorStore`** (`vector_store.py`): ChromaDB-based vector storage with two collections: + - `course_catalog`: Course metadata (titles, instructors, links) + - `course_content`: Chunked course content for semantic search +- **`AIGenerator`** (`ai_generator.py`): Anthropic Claude API integration with tool support +- **`DocumentProcessor`** (`document_processor.py`): Processes course documents into chunks +- **`SessionManager`** (`session_manager.py`): Manages conversation history per session + +### Key Design Patterns +- **Tool-based AI Search**: Uses Claude's function calling to search course content +- **Two-tier Vector Storage**: Separate collections for metadata vs content search +- **Session-aware Conversations**: Maintains conversation history per user session +- **Chunked Content Processing**: Documents split into 800-character chunks with 100-char overlap + +### Configuration +All settings centralized in `config.py` with defaults: +- Chunk size: 800 characters (overlap: 100) +- Max search results: 5 +- Embedding model: `all-MiniLM-L6-v2` +- Claude model: `claude-sonnet-4-20250514` + +### API Endpoints +- `POST /api/query`: Process user queries with RAG +- `GET /api/courses`: Get course analytics +- `/`: Serves frontend static files + +### Document Processing Flow +1. Documents from `docs/` folder are processed on startup +2. Each document becomes a `Course` with multiple `Lesson` objects +3. Content is chunked into `CourseChunk` objects for vector search +4. Both metadata and content chunks are stored separately for optimal retrieval + +## Dependencies + +The project uses **uv** as the package manager with core dependencies: +- `chromadb==1.0.15`: Vector database +- `anthropic==0.58.2`: Claude API integration +- `sentence-transformers==5.0.0`: Embedding generation +- `fastapi==0.116.1`: Web framework +- `uvicorn==0.35.0`: ASGI server + +## File Structure Notes +- `docs/`: Course materials (TXT files processed on startup) +- `backend/chroma_db/`: Persisted vector database +- `frontend/`: Static web interface files +- `main.py`: Entry point (unused in favor of FastAPI app) \ No newline at end of file From 36ccefe54823ed996d86f254324fab629734f38a Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:09:24 -0500 Subject: [PATCH 02/33] Claude-generated workspace settings. --- dl-ragchatbot.code-workspace | 31 +++++++++++++++ extensions.json | 15 ++++++++ launch.json | 52 +++++++++++++++++++++++++ settings.json | 25 ++++++++++++ tasks.json | 74 ++++++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 dl-ragchatbot.code-workspace create mode 100644 extensions.json create mode 100644 launch.json create mode 100644 settings.json create mode 100644 tasks.json diff --git a/dl-ragchatbot.code-workspace b/dl-ragchatbot.code-workspace new file mode 100644 index 00000000..217cbc06 --- /dev/null +++ b/dl-ragchatbot.code-workspace @@ -0,0 +1,31 @@ +{ + "folders": [ + { + "name": "RAG Chatbot", + "path": "." + } + ], + "settings": { + "python.defaultInterpreterPath": "./.venv/Scripts/python.exe", + "python.terminal.activateEnvironment": true, + "terminal.integrated.cwd": "${workspaceFolder}", + "files.associations": { + "*.txt": "plaintext" + }, + "search.exclude": { + "**/chroma_db/**": true, + "**/.uv/**": true, + "**/uv.lock": true + } + }, + "extensions": { + "recommendations": [ + "ms-python.python", + "ms-python.flake8", + "ms-python.black-formatter", + "ms-python.isort", + "ms-toolsai.jupyter", + "humao.rest-client" + ] + } +} \ No newline at end of file diff --git a/extensions.json b/extensions.json new file mode 100644 index 00000000..bbfc673b --- /dev/null +++ b/extensions.json @@ -0,0 +1,15 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-python.flake8", + "ms-python.black-formatter", + "ms-python.isort", + "ms-toolsai.jupyter", + "ms-vscode.vscode-json", + "bradlc.vscode-tailwindcss", + "esbenp.prettier-vscode", + "ms-vscode.live-server", + "humao.rest-client", + "ms-python.debugpy" + ] +} \ No newline at end of file diff --git a/launch.json b/launch.json new file mode 100644 index 00000000..75176c95 --- /dev/null +++ b/launch.json @@ -0,0 +1,52 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug FastAPI Backend", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": [ + "backend.app:app", + "--host", + "0.0.0.0", + "--port", + "8000", + "--reload" + ], + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "console": "integratedTerminal", + "justMyCode": false, + "envFile": "${workspaceFolder}/.env" + }, + { + "name": "Debug Current Python File", + "type": "python", + "request": "launch", + "program": "${file}", + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "console": "integratedTerminal", + "justMyCode": true, + "envFile": "${workspaceFolder}/.env" + }, + { + "name": "Debug RAG System", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/main.py", + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "console": "integratedTerminal", + "justMyCode": false, + "envFile": "${workspaceFolder}/.env" + } + ] +} \ No newline at end of file diff --git a/settings.json b/settings.json new file mode 100644 index 00000000..3d55bf3f --- /dev/null +++ b/settings.json @@ -0,0 +1,25 @@ +{ + "python.defaultInterpreterPath": "./.venv/Scripts/python.exe", + "python.terminal.activateEnvironment": true, + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.formatting.provider": "black", + "python.sortImports.args": ["--profile", "black"], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "files.exclude": { + "**/__pycache__": true, + "**/*.pyc": true, + "**/chroma_db": true, + "**/.uv": true + }, + "python.analysis.typeCheckingMode": "basic", + "python.analysis.autoImportCompletions": true, + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "python.testing.unittestArgs": ["-v", "-s", "./tests", "-p", "test_*.py"], + "python.envFile": "${workspaceFolder}/.env" +} diff --git a/tasks.json b/tasks.json new file mode 100644 index 00000000..02f8c35c --- /dev/null +++ b/tasks.json @@ -0,0 +1,74 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Install Dependencies", + "type": "shell", + "command": "uv", + "args": ["sync"], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Start FastAPI Server", + "type": "shell", + "command": "uv", + "args": [ + "run", + "uvicorn", + "backend.app:app", + "--reload", + "--port", + "8000" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Run Tests", + "type": "shell", + "command": "uv", + "args": ["run", "python", "-m", "unittest", "discover", "-s", "tests"], + "group": "test", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Format Code", + "type": "shell", + "command": "uv", + "args": ["run", "black", "."], + "group": "build", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file From 6a95613065eee43df4a5277ccfb3d5bcdf20f85b Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:14:35 -0500 Subject: [PATCH 03/33] Update CLAUDE.md with uv package manager instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added reminders to always use uv for running server and managing dependencies instead of pip. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0bc2e5de..fb639140 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,4 +78,6 @@ The project uses **uv** as the package manager with core dependencies: - `docs/`: Course materials (TXT files processed on startup) - `backend/chroma_db/`: Persisted vector database - `frontend/`: Static web interface files -- `main.py`: Entry point (unused in favor of FastAPI app) \ No newline at end of file +- `main.py`: Entry point (unused in favor of FastAPI app) +- Always use uv to run the server and do not run pip directly +- Always use uv to manage all dependencies \ No newline at end of file From 1bb456b807a0dda0360e5a50210101d67909d0e4 Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:35:00 -0500 Subject: [PATCH 04/33] Claude git permissions --- .claude/settings.local.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..e0f9f82d --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(git add:*)", + "Bash(git commit:*)" + ], + "deny": [] + } +} \ No newline at end of file From a745f0537767a5e1679ea6e7efd5ca021da239f2 Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:52:34 -0500 Subject: [PATCH 05/33] Claude code first attempt (failed) to fix bad source links. --- .chat-content/bad-links.png | Bin 0 -> 63865 bytes backend/app.py | 4 ++-- backend/document_processor.py | 2 ++ backend/models.py | 1 + backend/search_tools.py | 10 +++++++--- backend/vector_store.py | 1 + frontend/script.js | 24 +++++++++++++++++++++++- frontend/style.css | 19 +++++++++++++++++++ 8 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 .chat-content/bad-links.png diff --git a/.chat-content/bad-links.png b/.chat-content/bad-links.png new file mode 100644 index 0000000000000000000000000000000000000000..51f9fdfad61fdb5ced76ccac7761fb698e1f34ce GIT binary patch literal 63865 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 bC>u0x_l4K8{;DAd@VR1WZcuR9@yY)IFJ{qz literal 0 HcmV?d00001 diff --git a/backend/app.py b/backend/app.py index 5a69d741..03072ba1 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,7 +6,7 @@ from fastapi.staticfiles import StaticFiles from fastapi.middleware.trustedhost import TrustedHostMiddleware from pydantic import BaseModel -from typing import List, Optional +from typing import List, Optional, Dict, Any, Union import os from config import config @@ -43,7 +43,7 @@ class QueryRequest(BaseModel): class QueryResponse(BaseModel): """Response model for course queries""" answer: str - sources: List[str] + sources: List[Union[str, Dict[str, Any]]] # Support both string and object formats session_id: str class CourseStats(BaseModel): diff --git a/backend/document_processor.py b/backend/document_processor.py index 266e8590..fdf1c44d 100644 --- a/backend/document_processor.py +++ b/backend/document_processor.py @@ -191,6 +191,7 @@ def process_course_document(self, file_path: str) -> Tuple[Course, List[CourseCh content=chunk_with_context, course_title=course.title, lesson_number=current_lesson, + lesson_link=lesson_link, chunk_index=chunk_counter ) course_chunks.append(course_chunk) @@ -237,6 +238,7 @@ def process_course_document(self, file_path: str) -> Tuple[Course, List[CourseCh content=chunk_with_context, course_title=course.title, lesson_number=current_lesson, + lesson_link=lesson_link, chunk_index=chunk_counter ) course_chunks.append(course_chunk) diff --git a/backend/models.py b/backend/models.py index 7f7126fa..c44f5e3b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -19,4 +19,5 @@ class CourseChunk(BaseModel): content: str # The actual text content course_title: str # Which course this chunk belongs to lesson_number: Optional[int] = None # Which lesson this chunk is from + lesson_link: Optional[str] = None # URL link to the lesson video chunk_index: int # Position of this chunk in the document \ No newline at end of file diff --git a/backend/search_tools.py b/backend/search_tools.py index adfe8235..41f5316b 100644 --- a/backend/search_tools.py +++ b/backend/search_tools.py @@ -93,6 +93,7 @@ def _format_results(self, results: SearchResults) -> str: for doc, meta in zip(results.documents, results.metadata): course_title = meta.get('course_title', 'unknown') lesson_num = meta.get('lesson_number') + lesson_link = meta.get('lesson_link') # Build context header header = f"[{course_title}" @@ -100,10 +101,13 @@ def _format_results(self, results: SearchResults) -> str: header += f" - Lesson {lesson_num}" header += "]" - # Track source for the UI - source = course_title + # Track source for the UI with lesson link if available + source = { + "display_text": course_title, + "lesson_link": lesson_link + } if lesson_num is not None: - source += f" - Lesson {lesson_num}" + source["display_text"] += f" - Lesson {lesson_num}" sources.append(source) formatted.append(f"{header}\n{doc}") diff --git a/backend/vector_store.py b/backend/vector_store.py index 390abe71..d9f1da06 100644 --- a/backend/vector_store.py +++ b/backend/vector_store.py @@ -168,6 +168,7 @@ def add_course_content(self, chunks: List[CourseChunk]): metadatas = [{ "course_title": chunk.course_title, "lesson_number": chunk.lesson_number, + "lesson_link": chunk.lesson_link, "chunk_index": chunk.chunk_index } for chunk in chunks] # Use title with chunk index for unique IDs diff --git a/frontend/script.js b/frontend/script.js index 562a8a36..1ab6999a 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -122,10 +122,32 @@ function addMessage(content, type, sources = null, isWelcome = false) { let html = `
${displayContent}
`; if (sources && sources.length > 0) { + console.log('Sources received:', sources); // Debug log + const sourceElements = sources.map(source => { + console.log('Processing source:', source, 'Type:', typeof source); // Debug log + + // Handle both string and object sources + if (typeof source === 'string') { + return escapeHtml(source); + } else if (source && typeof source === 'object') { + // Object source with potential lesson link + if (source.lesson_link && source.display_text) { + return `${escapeHtml(source.display_text)}`; + } else if (source.display_text) { + return escapeHtml(source.display_text); + } else { + // Fallback for objects without display_text + return escapeHtml(JSON.stringify(source)); + } + } + // Fallback for any other type + return escapeHtml(String(source)); + }).filter(el => el); + html += `
Sources -
${sources.join(', ')}
+
${sourceElements.join(', ')}
`; } diff --git a/frontend/style.css b/frontend/style.css index 825d0367..726aef92 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -245,6 +245,25 @@ header h1 { color: var(--text-secondary); } +/* Source link styling */ +.source-link { + color: var(--primary-color); + text-decoration: none; + border-bottom: 1px solid transparent; + transition: all 0.2s ease; +} + +.source-link:hover { + color: var(--primary-hover); + border-bottom-color: var(--primary-hover); +} + +.source-link:focus { + outline: 2px solid var(--primary-color); + outline-offset: 2px; + border-radius: 2px; +} + /* Markdown formatting styles */ .message-content h1, .message-content h2, From 6270b0ab4f748062b05bfa57356ded8767bb25bd Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 18:54:18 -0500 Subject: [PATCH 06/33] Gemini markdown --- GEMINI.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 GEMINI.md diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..6a12c256 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,61 @@ +## Project Overview + +This project is a full-stack Retrieval-Augmented Generation (RAG) application. It provides a web-based chat interface for users to ask questions about a collection of course materials. The backend is built with Python using FastAPI, and the frontend is a simple HTML, CSS, and JavaScript application. + +### Key Features + +- **RAG System**: The core of the application is a RAG system that uses a vector store (ChromaDB) to find relevant course material and an AI model (Anthropic's Claude) to generate answers. +- **Web Interface**: A user-friendly chat interface allows users to interact with the RAG system. +- **Course Management**: The application can ingest course materials from text files, process them into chunks, and store them in the vector store. +- **Semantic Search**: The system uses sentence transformers to create embeddings for semantic search, allowing users to find relevant content even if their query doesn't exactly match the text. +- **Tool-based Search**: The AI model uses a "search" tool to query the vector store, which allows for more complex and targeted searches. +- **Conversation History**: The application maintains a conversation history for each user session, allowing the AI to have context for follow-up questions. + +## Project Structure + +The project is divided into two main parts: a `backend` and a `frontend`. + +### Backend + +The backend is a Python application built with FastAPI. It's responsible for the following: + +- **`app.py`**: The main FastAPI application. It defines the API endpoints, handles CORS, and serves the frontend. +- **`rag_system.py`**: The main orchestrator for the RAG system. It integrates the document processor, vector store, and AI generator. +- **`document_processor.py`**: This module is responsible for reading course documents, splitting them into chunks, and extracting metadata. +- **`vector_store.py`**: This module manages the ChromaDB vector store. It handles adding, and searching for course content. +- **`ai_generator.py`**: This module interacts with Anthropic's Claude API to generate AI responses. +- **`search_tools.py`**: This module defines the "search" tool that the AI model uses to query the vector store. +- **`session_manager.py`**: This module manages user sessions and conversation history. +- **`config.py`**: This file contains the configuration for the application, such as API keys and model names. +- **`models.py`**: This file defines the Pydantic models used in the application. + +### Frontend + +The frontend is a simple HTML, CSS, and JavaScript application. + +- **`index.html`**: The main HTML file for the application. +- **`style.css`**: The CSS file for styling the application. +- **`script.js`**: The JavaScript file that handles user interactions, sends requests to the backend, and displays the results. + +## How it Works + +1. **Initialization**: When the application starts, it loads the course materials from the `docs` directory, processes them, and stores them in the ChromaDB vector store. +2. **User Query**: A user enters a query in the chat interface. +3. **API Request**: The frontend sends the query to the backend's `/api/query` endpoint. +4. **RAG Process**: + - The `RAGSystem` receives the query. + - The `AIGenerator` sends the query to the Claude API, along with the definition of the `search_course_content` tool. + - Claude determines that it needs to use the search tool and returns a "tool use" response. + - The `ToolManager` executes the `search_course_content` tool, which queries the `VectorStore`. + - The `VectorStore` performs a semantic search to find the most relevant course chunks. + - The search results are returned to the `AIGenerator`. + - The `AIGenerator` sends the search results back to Claude, which then generates a final answer. +5. **Response**: The backend sends the answer and the sources back to the frontend. +6. **Display**: The frontend displays the answer and the sources in the chat interface. + +## How to Run + +1. Install the dependencies: `uv sync` +2. Set up the environment variables in a `.env` file. +3. Run the application: `./run.sh` or `cd backend && uv run uvicorn app:app --reload --port 8000` +4. Open a browser to `http://localhost:8000`. From 638fda182c94bf8bdec9efee5ddad69e940f1d5e Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 19:02:42 -0500 Subject: [PATCH 07/33] Revert "Gemini markdown" This reverts commit 6270b0ab4f748062b05bfa57356ded8767bb25bd. --- GEMINI.md | 61 ------------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 GEMINI.md diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index 6a12c256..00000000 --- a/GEMINI.md +++ /dev/null @@ -1,61 +0,0 @@ -## Project Overview - -This project is a full-stack Retrieval-Augmented Generation (RAG) application. It provides a web-based chat interface for users to ask questions about a collection of course materials. The backend is built with Python using FastAPI, and the frontend is a simple HTML, CSS, and JavaScript application. - -### Key Features - -- **RAG System**: The core of the application is a RAG system that uses a vector store (ChromaDB) to find relevant course material and an AI model (Anthropic's Claude) to generate answers. -- **Web Interface**: A user-friendly chat interface allows users to interact with the RAG system. -- **Course Management**: The application can ingest course materials from text files, process them into chunks, and store them in the vector store. -- **Semantic Search**: The system uses sentence transformers to create embeddings for semantic search, allowing users to find relevant content even if their query doesn't exactly match the text. -- **Tool-based Search**: The AI model uses a "search" tool to query the vector store, which allows for more complex and targeted searches. -- **Conversation History**: The application maintains a conversation history for each user session, allowing the AI to have context for follow-up questions. - -## Project Structure - -The project is divided into two main parts: a `backend` and a `frontend`. - -### Backend - -The backend is a Python application built with FastAPI. It's responsible for the following: - -- **`app.py`**: The main FastAPI application. It defines the API endpoints, handles CORS, and serves the frontend. -- **`rag_system.py`**: The main orchestrator for the RAG system. It integrates the document processor, vector store, and AI generator. -- **`document_processor.py`**: This module is responsible for reading course documents, splitting them into chunks, and extracting metadata. -- **`vector_store.py`**: This module manages the ChromaDB vector store. It handles adding, and searching for course content. -- **`ai_generator.py`**: This module interacts with Anthropic's Claude API to generate AI responses. -- **`search_tools.py`**: This module defines the "search" tool that the AI model uses to query the vector store. -- **`session_manager.py`**: This module manages user sessions and conversation history. -- **`config.py`**: This file contains the configuration for the application, such as API keys and model names. -- **`models.py`**: This file defines the Pydantic models used in the application. - -### Frontend - -The frontend is a simple HTML, CSS, and JavaScript application. - -- **`index.html`**: The main HTML file for the application. -- **`style.css`**: The CSS file for styling the application. -- **`script.js`**: The JavaScript file that handles user interactions, sends requests to the backend, and displays the results. - -## How it Works - -1. **Initialization**: When the application starts, it loads the course materials from the `docs` directory, processes them, and stores them in the ChromaDB vector store. -2. **User Query**: A user enters a query in the chat interface. -3. **API Request**: The frontend sends the query to the backend's `/api/query` endpoint. -4. **RAG Process**: - - The `RAGSystem` receives the query. - - The `AIGenerator` sends the query to the Claude API, along with the definition of the `search_course_content` tool. - - Claude determines that it needs to use the search tool and returns a "tool use" response. - - The `ToolManager` executes the `search_course_content` tool, which queries the `VectorStore`. - - The `VectorStore` performs a semantic search to find the most relevant course chunks. - - The search results are returned to the `AIGenerator`. - - The `AIGenerator` sends the search results back to Claude, which then generates a final answer. -5. **Response**: The backend sends the answer and the sources back to the frontend. -6. **Display**: The frontend displays the answer and the sources in the chat interface. - -## How to Run - -1. Install the dependencies: `uv sync` -2. Set up the environment variables in a `.env` file. -3. Run the application: `./run.sh` or `cd backend && uv run uvicorn app:app --reload --port 8000` -4. Open a browser to `http://localhost:8000`. From 227398193d1a3f0adc72e90ab5de2e49073c3181 Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 19:03:25 -0500 Subject: [PATCH 08/33] Revert "Claude code first attempt (failed) to fix bad source links." This reverts commit a745f0537767a5e1679ea6e7efd5ca021da239f2. --- .chat-content/bad-links.png | Bin 63865 -> 0 bytes backend/app.py | 4 ++-- backend/document_processor.py | 2 -- backend/models.py | 1 - backend/search_tools.py | 10 +++------- backend/vector_store.py | 1 - frontend/script.js | 24 +----------------------- frontend/style.css | 19 ------------------- 8 files changed, 6 insertions(+), 55 deletions(-) delete mode 100644 .chat-content/bad-links.png diff --git a/.chat-content/bad-links.png b/.chat-content/bad-links.png deleted file mode 100644 index 51f9fdfad61fdb5ced76ccac7761fb698e1f34ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63865 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 bC>u0x_l4K8{;DAd@VR1WZcuR9@yY)IFJ{qz diff --git a/backend/app.py b/backend/app.py index 03072ba1..5a69d741 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,7 +6,7 @@ from fastapi.staticfiles import StaticFiles from fastapi.middleware.trustedhost import TrustedHostMiddleware from pydantic import BaseModel -from typing import List, Optional, Dict, Any, Union +from typing import List, Optional import os from config import config @@ -43,7 +43,7 @@ class QueryRequest(BaseModel): class QueryResponse(BaseModel): """Response model for course queries""" answer: str - sources: List[Union[str, Dict[str, Any]]] # Support both string and object formats + sources: List[str] session_id: str class CourseStats(BaseModel): diff --git a/backend/document_processor.py b/backend/document_processor.py index fdf1c44d..266e8590 100644 --- a/backend/document_processor.py +++ b/backend/document_processor.py @@ -191,7 +191,6 @@ def process_course_document(self, file_path: str) -> Tuple[Course, List[CourseCh content=chunk_with_context, course_title=course.title, lesson_number=current_lesson, - lesson_link=lesson_link, chunk_index=chunk_counter ) course_chunks.append(course_chunk) @@ -238,7 +237,6 @@ def process_course_document(self, file_path: str) -> Tuple[Course, List[CourseCh content=chunk_with_context, course_title=course.title, lesson_number=current_lesson, - lesson_link=lesson_link, chunk_index=chunk_counter ) course_chunks.append(course_chunk) diff --git a/backend/models.py b/backend/models.py index c44f5e3b..7f7126fa 100644 --- a/backend/models.py +++ b/backend/models.py @@ -19,5 +19,4 @@ class CourseChunk(BaseModel): content: str # The actual text content course_title: str # Which course this chunk belongs to lesson_number: Optional[int] = None # Which lesson this chunk is from - lesson_link: Optional[str] = None # URL link to the lesson video chunk_index: int # Position of this chunk in the document \ No newline at end of file diff --git a/backend/search_tools.py b/backend/search_tools.py index 41f5316b..adfe8235 100644 --- a/backend/search_tools.py +++ b/backend/search_tools.py @@ -93,7 +93,6 @@ def _format_results(self, results: SearchResults) -> str: for doc, meta in zip(results.documents, results.metadata): course_title = meta.get('course_title', 'unknown') lesson_num = meta.get('lesson_number') - lesson_link = meta.get('lesson_link') # Build context header header = f"[{course_title}" @@ -101,13 +100,10 @@ def _format_results(self, results: SearchResults) -> str: header += f" - Lesson {lesson_num}" header += "]" - # Track source for the UI with lesson link if available - source = { - "display_text": course_title, - "lesson_link": lesson_link - } + # Track source for the UI + source = course_title if lesson_num is not None: - source["display_text"] += f" - Lesson {lesson_num}" + source += f" - Lesson {lesson_num}" sources.append(source) formatted.append(f"{header}\n{doc}") diff --git a/backend/vector_store.py b/backend/vector_store.py index d9f1da06..390abe71 100644 --- a/backend/vector_store.py +++ b/backend/vector_store.py @@ -168,7 +168,6 @@ def add_course_content(self, chunks: List[CourseChunk]): metadatas = [{ "course_title": chunk.course_title, "lesson_number": chunk.lesson_number, - "lesson_link": chunk.lesson_link, "chunk_index": chunk.chunk_index } for chunk in chunks] # Use title with chunk index for unique IDs diff --git a/frontend/script.js b/frontend/script.js index 1ab6999a..562a8a36 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -122,32 +122,10 @@ function addMessage(content, type, sources = null, isWelcome = false) { let html = `
${displayContent}
`; if (sources && sources.length > 0) { - console.log('Sources received:', sources); // Debug log - const sourceElements = sources.map(source => { - console.log('Processing source:', source, 'Type:', typeof source); // Debug log - - // Handle both string and object sources - if (typeof source === 'string') { - return escapeHtml(source); - } else if (source && typeof source === 'object') { - // Object source with potential lesson link - if (source.lesson_link && source.display_text) { - return `${escapeHtml(source.display_text)}`; - } else if (source.display_text) { - return escapeHtml(source.display_text); - } else { - // Fallback for objects without display_text - return escapeHtml(JSON.stringify(source)); - } - } - // Fallback for any other type - return escapeHtml(String(source)); - }).filter(el => el); - html += `
Sources -
${sourceElements.join(', ')}
+
${sources.join(', ')}
`; } diff --git a/frontend/style.css b/frontend/style.css index 726aef92..825d0367 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -245,25 +245,6 @@ header h1 { color: var(--text-secondary); } -/* Source link styling */ -.source-link { - color: var(--primary-color); - text-decoration: none; - border-bottom: 1px solid transparent; - transition: all 0.2s ease; -} - -.source-link:hover { - color: var(--primary-hover); - border-bottom-color: var(--primary-hover); -} - -.source-link:focus { - outline: 2px solid var(--primary-color); - outline-offset: 2px; - border-radius: 2px; -} - /* Markdown formatting styles */ .message-content h1, .message-content h2, From dc68352cf2f7f730bc313b6c402e925b5a9360d0 Mon Sep 17 00:00:00 2001 From: Quoc Pham Date: Thu, 7 Aug 2025 19:07:59 -0500 Subject: [PATCH 09/33] Add new chat button. --- backend/app.py | 16 ++++++++++++++++ frontend/index.html | 5 +++++ frontend/script.js | 29 ++++++++++++++++++++++++++++- frontend/style.css | 21 +++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index 5a69d741..49bb441b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -53,6 +53,22 @@ class CourseStats(BaseModel): # API Endpoints + +# New endpoint to create a new chat session +from fastapi.responses import JSONResponse + +@app.post("/api/session/new") +async def create_new_session(prev_session_id: Optional[str] = None): + """Create a new session and clear previous session if provided""" + try: + if prev_session_id: + rag_system.session_manager.clear_session(prev_session_id) + session_id = rag_system.session_manager.create_session() + return JSONResponse(content={"session_id": session_id}) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @app.post("/api/query", response_model=QueryResponse) async def query_documents(request: QueryRequest): """Process a query and return response with sources""" diff --git a/frontend/index.html b/frontend/index.html index f8e25a62..ca70a157 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -17,8 +17,13 @@

Course Materials Assistant

+