diff --git a/DEV-README.md b/DEV-README.md new file mode 100644 index 0000000..ecbcb2a --- /dev/null +++ b/DEV-README.md @@ -0,0 +1,110 @@ +# Development Environment Setup + +This guide explains how to run the application in development mode with hot reload enabled. + +## Quick Start + +### Development Mode (Hot Reload) + +```bash +# Start all services in development mode +docker-compose -f docker-compose.dev.yml up --build + +# Or run in detached mode +docker-compose -f docker-compose.dev.yml up -d --build +``` + +### Production Mode + +```bash +# Start production services +docker-compose up --build +``` + +## What's Different in Dev Mode? + +### Backend (Quart/Flask) +- **Hot Reload**: Python code changes are automatically detected and the server restarts +- **Source Mounted**: Your local `services/raggr` directory is mounted as a volume +- **Debug Mode**: Flask runs with `debug=True` for better error messages +- **Environment**: `FLASK_ENV=development` and `PYTHONUNBUFFERED=1` for immediate log output + +### Frontend (React + rsbuild) +- **Auto Rebuild**: Frontend automatically rebuilds when files change +- **Watch Mode**: rsbuild runs in watch mode, rebuilding to `dist/` on save +- **Source Mounted**: Your local `services/raggr/raggr-frontend` directory is mounted as a volume +- **Served by Backend**: Built files are served by the backend, no separate dev server + +## Ports + +- **Application**: 8080 (accessible at `http://localhost:8080` or `http://YOUR_IP:8080`) + +The backend serves both the API and the auto-rebuilt frontend, making it accessible from other machines on your network. + +## Useful Commands + +```bash +# View logs +docker-compose -f docker-compose.dev.yml logs -f + +# View logs for specific service +docker-compose -f docker-compose.dev.yml logs -f raggr-backend +docker-compose -f docker-compose.dev.yml logs -f raggr-frontend + +# Rebuild after dependency changes +docker-compose -f docker-compose.dev.yml up --build + +# Stop all services +docker-compose -f docker-compose.dev.yml down + +# Stop and remove volumes (fresh start) +docker-compose -f docker-compose.dev.yml down -v +``` + +## Making Changes + +### Backend Changes +1. Edit any Python file in `services/raggr/` +2. Save the file +3. The Quart server will automatically restart +4. Check logs to confirm reload + +### Frontend Changes +1. Edit any file in `services/raggr/raggr-frontend/src/` +2. Save the file +3. The browser will automatically refresh (Hot Module Replacement) +4. No need to rebuild + +### Dependency Changes + +**Backend** (pyproject.toml): +```bash +# Rebuild the backend service +docker-compose -f docker-compose.dev.yml up --build raggr-backend +``` + +**Frontend** (package.json): +```bash +# Rebuild the frontend service +docker-compose -f docker-compose.dev.yml up --build raggr-frontend +``` + +## Troubleshooting + +### Port Already in Use +If you see port binding errors, make sure no other services are running on ports 8080 or 3000. + +### Changes Not Reflected +1. Check if the file is properly mounted (check docker-compose.dev.yml volumes) +2. Verify the file isn't in an excluded directory (node_modules, __pycache__) +3. Check container logs for errors + +### Frontend Not Connecting to Backend +Make sure your frontend API calls point to the correct backend URL. If accessing from the same machine, use `http://localhost:8080`. If accessing from another device on the network, use `http://YOUR_IP:8080`. + +## Notes + +- Both services bind to `0.0.0.0` and expose ports, making them accessible on your network +- Node modules and Python cache are excluded from volume mounts to use container versions +- Database and ChromaDB data persist in Docker volumes across restarts +- Access the app from any device on your network using your host machine's IP address diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f2aa20e --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,45 @@ +version: "3.8" + +services: + raggr-backend: + build: + context: ./services/raggr + dockerfile: Dockerfile.dev + image: torrtle/simbarag:dev + ports: + - "8080:8080" + environment: + - PAPERLESS_TOKEN=${PAPERLESS_TOKEN} + - BASE_URL=${BASE_URL} + - OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434} + - CHROMADB_PATH=/app/chromadb + - OPENAI_API_KEY=${OPENAI_API_KEY} + - FLASK_ENV=development + - PYTHONUNBUFFERED=1 + volumes: + # Mount source code for hot reload + - ./services/raggr:/app + # Exclude node_modules and Python cache + - /app/raggr-frontend/node_modules + - /app/__pycache__ + # Persist data + - chromadb_data:/app/chromadb + - database_data:/app/database + command: sh -c "chmod +x /app/startup-dev.sh && /app/startup-dev.sh" + + raggr-frontend: + build: + context: ./services/raggr/raggr-frontend + dockerfile: Dockerfile.dev + environment: + - NODE_ENV=development + volumes: + # Mount source code for hot reload + - ./services/raggr/raggr-frontend:/app + # Exclude node_modules to use container's version + - /app/node_modules + command: sh -c "yarn build && yarn watch:build" + +volumes: + chromadb_data: + database_data: diff --git a/services/raggr/Dockerfile.dev b/services/raggr/Dockerfile.dev new file mode 100644 index 0000000..d5719a4 --- /dev/null +++ b/services/raggr/Dockerfile.dev @@ -0,0 +1,33 @@ +FROM python:3.13-slim + +WORKDIR /app + +# Install system dependencies and uv +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + && rm -rf /var/lib/apt/lists/* \ + && curl -LsSf https://astral.sh/uv/install.sh | sh + +# Add uv to PATH +ENV PATH="/root/.local/bin:$PATH" + +# Copy dependency files +COPY pyproject.toml ./ + +# Install Python dependencies using uv +RUN uv pip install --system -e . + +# Create ChromaDB and database directories +RUN mkdir -p /app/chromadb /app/database + +# Expose port +EXPOSE 8080 + +# Set environment variables +ENV PYTHONPATH=/app +ENV CHROMADB_PATH=/app/chromadb +ENV PYTHONUNBUFFERED=1 + +# The actual source code will be mounted as a volume +# No CMD here - will be specified in docker-compose diff --git a/services/raggr/raggr-frontend/.dockerignore b/services/raggr/raggr-frontend/.dockerignore new file mode 100644 index 0000000..ab174f0 --- /dev/null +++ b/services/raggr/raggr-frontend/.dockerignore @@ -0,0 +1,9 @@ +.git +.gitignore +README.md +.DS_Store +node_modules +dist +.cache +coverage +*.log diff --git a/services/raggr/raggr-frontend/.gitignore b/services/raggr/raggr-frontend/.gitignore index 6f3092c..2534bb9 100644 --- a/services/raggr/raggr-frontend/.gitignore +++ b/services/raggr/raggr-frontend/.gitignore @@ -6,6 +6,7 @@ # Dist node_modules dist/ +.yarn # Profile .rspack-profile-*/ diff --git a/services/raggr/raggr-frontend/.yarnrc.yml b/services/raggr/raggr-frontend/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/services/raggr/raggr-frontend/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/services/raggr/raggr-frontend/Dockerfile.dev b/services/raggr/raggr-frontend/Dockerfile.dev new file mode 100644 index 0000000..37ad44b --- /dev/null +++ b/services/raggr/raggr-frontend/Dockerfile.dev @@ -0,0 +1,15 @@ +FROM node:20-slim + +WORKDIR /app + +# Copy package files +COPY package.json yarn.lock* ./ + +# Install dependencies +RUN yarn install + +# Expose rsbuild dev server port (default 3000) +EXPOSE 3000 + +# The actual source code will be mounted as a volume +# CMD will be specified in docker-compose diff --git a/services/raggr/raggr-frontend/package.json b/services/raggr/raggr-frontend/package.json index 13c3143..2ff56a9 100644 --- a/services/raggr/raggr-frontend/package.json +++ b/services/raggr/raggr-frontend/package.json @@ -20,6 +20,7 @@ "watch": "^1.0.2" }, "devDependencies": { + "@biomejs/biome": "2.3.10", "@rsbuild/core": "^1.5.6", "@rsbuild/plugin-react": "^1.4.0", "@tailwindcss/postcss": "^4.0.0", diff --git a/services/raggr/raggr-frontend/src/App.css b/services/raggr/raggr-frontend/src/App.css index 9cad0ff..bfe41ef 100644 --- a/services/raggr/raggr-frontend/src/App.css +++ b/services/raggr/raggr-frontend/src/App.css @@ -3,4 +3,5 @@ body { margin: 0; font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + background-color: #F9F5EB; } diff --git a/services/raggr/raggr-frontend/src/assets/cat.png b/services/raggr/raggr-frontend/src/assets/cat.png new file mode 100644 index 0000000..a718a59 Binary files /dev/null and b/services/raggr/raggr-frontend/src/assets/cat.png differ diff --git a/services/raggr/raggr-frontend/src/components/AnswerBubble.tsx b/services/raggr/raggr-frontend/src/components/AnswerBubble.tsx index dc42541..1433a98 100644 --- a/services/raggr/raggr-frontend/src/components/AnswerBubble.tsx +++ b/services/raggr/raggr-frontend/src/components/AnswerBubble.tsx @@ -7,7 +7,7 @@ type AnswerBubbleProps = { export const AnswerBubble = ({ text, loading }: AnswerBubbleProps) => { return ( -
+
{loading ? (
@@ -20,8 +20,8 @@ export const AnswerBubble = ({ text, loading }: AnswerBubbleProps) => {
) : ( -
- +
+ {"🐈: " + text}
diff --git a/services/raggr/raggr-frontend/src/components/ChatScreen.tsx b/services/raggr/raggr-frontend/src/components/ChatScreen.tsx index 85fc947..92e6e44 100644 --- a/services/raggr/raggr-frontend/src/components/ChatScreen.tsx +++ b/services/raggr/raggr-frontend/src/components/ChatScreen.tsx @@ -2,8 +2,9 @@ import { useEffect, useState, useRef } from "react"; import { conversationService } from "../api/conversationService"; import { QuestionBubble } from "./QuestionBubble"; import { AnswerBubble } from "./AnswerBubble"; +import { MessageInput } from "./MessageInput"; import { ConversationList } from "./ConversationList"; -import { parse } from "node:path/win32"; +import catIcon from "../assets/cat.png"; type Message = { text: string; @@ -38,6 +39,7 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => { const [showConversations, setShowConversations] = useState(false); const [selectedConversation, setSelectedConversation] = useState(null); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const messagesEndRef = useRef(null); const simbaAnswers = ["meow.", "hiss...", "purrrrrr", "yowOWROWWowowr"]; @@ -176,37 +178,81 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => { }; return ( -
-
-
-
-
-
-

ask simba!

-
-
- - -
-
- {showConversations && ( - + {/* Sidebar - Expanded */} + + + {/* Main chat area */} +
+ {/* Mobile header */} +
+
+ Simba +

asksimba!

+
+
+ + +
+
+ + {/* Messages area */} +
+
+ {showConversations && ( +
+ +
)} {messages.map((msg, index) => { if (msg.speaker === "simba") { @@ -215,37 +261,21 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => { return ; })}
-