From f5e2d68cd29f0b6a1dfbf18638158bffa0944bf0 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 24 Dec 2025 17:12:56 -0800 Subject: [PATCH] Making UI changes --- DEV-README.md | 110 + docker-compose.dev.yml | 45 + services/raggr/Dockerfile.dev | 33 + services/raggr/raggr-frontend/.dockerignore | 9 + services/raggr/raggr-frontend/.gitignore | 1 + services/raggr/raggr-frontend/.yarnrc.yml | 1 + services/raggr/raggr-frontend/Dockerfile.dev | 15 + services/raggr/raggr-frontend/package.json | 1 + services/raggr/raggr-frontend/src/App.css | 1 + .../raggr/raggr-frontend/src/assets/cat.png | Bin 0 -> 5947 bytes .../src/components/AnswerBubble.tsx | 6 +- .../src/components/ChatScreen.tsx | 150 +- .../src/components/ConversationList.tsx | 2 +- .../src/components/MessageInput.tsx | 43 + .../src/components/QuestionBubble.tsx | 2 +- services/raggr/raggr-frontend/yarn.lock | 4559 +++++++++++------ services/raggr/startup-dev.sh | 31 + 17 files changed, 3262 insertions(+), 1747 deletions(-) create mode 100644 DEV-README.md create mode 100644 docker-compose.dev.yml create mode 100644 services/raggr/Dockerfile.dev create mode 100644 services/raggr/raggr-frontend/.dockerignore create mode 100644 services/raggr/raggr-frontend/.yarnrc.yml create mode 100644 services/raggr/raggr-frontend/Dockerfile.dev create mode 100644 services/raggr/raggr-frontend/src/assets/cat.png create mode 100644 services/raggr/raggr-frontend/src/components/MessageInput.tsx create mode 100755 services/raggr/startup-dev.sh 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 0000000000000000000000000000000000000000..a718a596bf6e9b8acfa92644097fd0af8aea4cb0 GIT binary patch literal 5947 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 { 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 ; })}
-