Making UI changes
This commit is contained in:
110
DEV-README.md
Normal file
110
DEV-README.md
Normal file
@@ -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
|
||||||
45
docker-compose.dev.yml
Normal file
45
docker-compose.dev.yml
Normal file
@@ -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:
|
||||||
33
services/raggr/Dockerfile.dev
Normal file
33
services/raggr/Dockerfile.dev
Normal file
@@ -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
|
||||||
9
services/raggr/raggr-frontend/.dockerignore
Normal file
9
services/raggr/raggr-frontend/.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.cache
|
||||||
|
coverage
|
||||||
|
*.log
|
||||||
1
services/raggr/raggr-frontend/.gitignore
vendored
1
services/raggr/raggr-frontend/.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
# Dist
|
# Dist
|
||||||
node_modules
|
node_modules
|
||||||
dist/
|
dist/
|
||||||
|
.yarn
|
||||||
|
|
||||||
# Profile
|
# Profile
|
||||||
.rspack-profile-*/
|
.rspack-profile-*/
|
||||||
|
|||||||
1
services/raggr/raggr-frontend/.yarnrc.yml
Normal file
1
services/raggr/raggr-frontend/.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
15
services/raggr/raggr-frontend/Dockerfile.dev
Normal file
15
services/raggr/raggr-frontend/Dockerfile.dev
Normal file
@@ -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
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"watch": "^1.0.2"
|
"watch": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.3.10",
|
||||||
"@rsbuild/core": "^1.5.6",
|
"@rsbuild/core": "^1.5.6",
|
||||||
"@rsbuild/plugin-react": "^1.4.0",
|
"@rsbuild/plugin-react": "^1.4.0",
|
||||||
"@tailwindcss/postcss": "^4.0.0",
|
"@tailwindcss/postcss": "^4.0.0",
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
background-color: #F9F5EB;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
services/raggr/raggr-frontend/src/assets/cat.png
Normal file
BIN
services/raggr/raggr-frontend/src/assets/cat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -7,7 +7,7 @@ type AnswerBubbleProps = {
|
|||||||
|
|
||||||
export const AnswerBubble = ({ text, loading }: AnswerBubbleProps) => {
|
export const AnswerBubble = ({ text, loading }: AnswerBubbleProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md bg-orange-100 p-3 sm:p-4">
|
<div className="rounded-md bg-orange-100 p-3 sm:p-4 w-2/3">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex flex-col w-full animate-pulse gap-2">
|
<div className="flex flex-col w-full animate-pulse gap-2">
|
||||||
<div className="flex flex-row gap-2 w-full">
|
<div className="flex flex-row gap-2 w-full">
|
||||||
@@ -20,8 +20,8 @@ export const AnswerBubble = ({ text, loading }: AnswerBubbleProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col break-words overflow-wrap-anywhere">
|
<div className=" flex flex-col break-words overflow-wrap-anywhere text-sm sm:text-base [&>*]:break-words">
|
||||||
<ReactMarkdown className="text-sm sm:text-base [&>*]:break-words">
|
<ReactMarkdown>
|
||||||
{"🐈: " + text}
|
{"🐈: " + text}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { useEffect, useState, useRef } from "react";
|
|||||||
import { conversationService } from "../api/conversationService";
|
import { conversationService } from "../api/conversationService";
|
||||||
import { QuestionBubble } from "./QuestionBubble";
|
import { QuestionBubble } from "./QuestionBubble";
|
||||||
import { AnswerBubble } from "./AnswerBubble";
|
import { AnswerBubble } from "./AnswerBubble";
|
||||||
|
import { MessageInput } from "./MessageInput";
|
||||||
import { ConversationList } from "./ConversationList";
|
import { ConversationList } from "./ConversationList";
|
||||||
import { parse } from "node:path/win32";
|
import catIcon from "../assets/cat.png";
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -38,6 +39,7 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => {
|
|||||||
const [showConversations, setShowConversations] = useState<boolean>(false);
|
const [showConversations, setShowConversations] = useState<boolean>(false);
|
||||||
const [selectedConversation, setSelectedConversation] =
|
const [selectedConversation, setSelectedConversation] =
|
||||||
useState<Conversation | null>(null);
|
useState<Conversation | null>(null);
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(false);
|
||||||
|
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const simbaAnswers = ["meow.", "hiss...", "purrrrrr", "yowOWROWWowowr"];
|
const simbaAnswers = ["meow.", "hiss...", "purrrrrr", "yowOWROWWowowr"];
|
||||||
@@ -176,37 +178,81 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-opacity-20">
|
<div className="h-screen flex flex-row bg-[#F9F5EB]">
|
||||||
<div className="bg-white/85 h-screen">
|
{/* Sidebar - Expanded */}
|
||||||
<div className="flex flex-row justify-center py-4">
|
<aside className={`hidden md:flex md:flex-col bg-white border-r border-gray-200 p-4 overflow-y-auto transition-all duration-300 ${sidebarCollapsed ? 'w-20' : 'w-64'}`}>
|
||||||
<div className="flex flex-col gap-4 w-full px-4 sm:w-11/12 sm:max-w-2xl lg:max-w-4xl sm:px-0">
|
{!sidebarCollapsed ? (
|
||||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-0 sm:justify-between">
|
<>
|
||||||
<header className="flex flex-row justify-center gap-2 sticky top-0 z-10 bg-white">
|
<div className="flex flex-row items-center gap-2 mb-6">
|
||||||
<h1 className="text-2xl sm:text-3xl">ask simba!</h1>
|
<img
|
||||||
</header>
|
src={catIcon}
|
||||||
<div className="flex flex-row gap-2 justify-center sm:justify-end">
|
alt="Simba"
|
||||||
<button
|
className="cursor-pointer hover:opacity-80"
|
||||||
className="p-2 h-11 border border-green-400 bg-green-200 hover:bg-green-400 cursor-pointer rounded-md text-sm sm:text-base"
|
onClick={() => setSidebarCollapsed(true)}
|
||||||
onClick={() => setShowConversations(!showConversations)}
|
/>
|
||||||
>
|
<h2 className="text-3xl font-semibold">asksimba!</h2>
|
||||||
{showConversations
|
|
||||||
? "hide conversations"
|
|
||||||
: "show conversations"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="p-2 h-11 border border-red-400 bg-red-200 hover:bg-red-400 cursor-pointer rounded-md text-sm sm:text-base"
|
|
||||||
onClick={() => setAuthenticated(false)}
|
|
||||||
>
|
|
||||||
logout
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{showConversations && (
|
|
||||||
<ConversationList
|
<ConversationList
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
onCreateNewConversation={handleCreateNewConversation}
|
onCreateNewConversation={handleCreateNewConversation}
|
||||||
onSelectConversation={handleSelectConversation}
|
onSelectConversation={handleSelectConversation}
|
||||||
/>
|
/>
|
||||||
|
<div className="mt-auto pt-4">
|
||||||
|
<button
|
||||||
|
className="w-full p-2 border border-red-400 bg-red-200 hover:bg-red-400 cursor-pointer rounded-md text-sm"
|
||||||
|
onClick={() => setAuthenticated(false)}
|
||||||
|
>
|
||||||
|
logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<img
|
||||||
|
src={catIcon}
|
||||||
|
alt="Simba"
|
||||||
|
className="cursor-pointer hover:opacity-80"
|
||||||
|
onClick={() => setSidebarCollapsed(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Main chat area */}
|
||||||
|
<div className="flex-1 flex flex-col h-screen overflow-hidden">
|
||||||
|
{/* Mobile header */}
|
||||||
|
<header className="md:hidden flex flex-row justify-between items-center gap-3 p-4 border-b border-gray-200 bg-white">
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<img src={catIcon} alt="Simba" className="w-10 h-10" />
|
||||||
|
<h1 className="text-xl">asksimba!</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<button
|
||||||
|
className="p-2 border border-green-400 bg-green-200 hover:bg-green-400 cursor-pointer rounded-md text-sm"
|
||||||
|
onClick={() => setShowConversations(!showConversations)}
|
||||||
|
>
|
||||||
|
{showConversations ? "hide" : "show"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="p-2 border border-red-400 bg-red-200 hover:bg-red-400 cursor-pointer rounded-md text-sm"
|
||||||
|
onClick={() => setAuthenticated(false)}
|
||||||
|
>
|
||||||
|
logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Messages area */}
|
||||||
|
<div className="flex-1 overflow-y-auto px-4 py-4">
|
||||||
|
<div className="max-w-2xl mx-auto flex flex-col gap-4">
|
||||||
|
{showConversations && (
|
||||||
|
<div className="md:hidden">
|
||||||
|
<ConversationList
|
||||||
|
conversations={conversations}
|
||||||
|
onCreateNewConversation={handleCreateNewConversation}
|
||||||
|
onSelectConversation={handleSelectConversation}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{messages.map((msg, index) => {
|
{messages.map((msg, index) => {
|
||||||
if (msg.speaker === "simba") {
|
if (msg.speaker === "simba") {
|
||||||
@@ -215,38 +261,22 @@ export const ChatScreen = ({ setAuthenticated }: ChatScreenProps) => {
|
|||||||
return <QuestionBubble key={index} text={msg.text} />;
|
return <QuestionBubble key={index} text={msg.text} />;
|
||||||
})}
|
})}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
<footer className="flex flex-col gap-2 sticky bottom-0">
|
|
||||||
<div className="flex flex-row justify-between gap-2 grow">
|
|
||||||
<textarea
|
|
||||||
className="p-3 sm:p-4 border border-blue-200 rounded-md grow bg-white min-h-[44px] resize-y"
|
|
||||||
onChange={handleQueryChange}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
value={query}
|
|
||||||
rows={2}
|
|
||||||
placeholder="Type your message... (Press Enter to send, Shift+Enter for new line)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-between gap-2 grow">
|
|
||||||
<button
|
|
||||||
className="p-3 sm:p-4 min-h-[44px] border border-blue-400 bg-blue-200 hover:bg-blue-400 cursor-pointer rounded-md flex-grow text-sm sm:text-base"
|
|
||||||
onClick={() => handleQuestionSubmit()}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-center gap-2 grow items-center">
|
|
||||||
<input
|
{/* Input area */}
|
||||||
type="checkbox"
|
<footer className="p-4 bg-[#F9F5EB]">
|
||||||
onChange={(event) => setSimbaMode(event.target.checked)}
|
<div className="max-w-2xl mx-auto">
|
||||||
className="w-5 h-5 cursor-pointer"
|
<MessageInput
|
||||||
|
query={query}
|
||||||
|
handleQueryChange={handleQueryChange}
|
||||||
|
handleKeyDown={handleKeyDown}
|
||||||
|
handleQuestionSubmit={handleQuestionSubmit}
|
||||||
|
setSimbaMode={setSimbaMode}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm sm:text-base">simba mode?</p>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const ConversationList = ({
|
|||||||
className="border-blue-400 bg-indigo-300 hover:bg-indigo-200 cursor-pointer rounded-md p-3 min-h-[44px] flex items-center"
|
className="border-blue-400 bg-indigo-300 hover:bg-indigo-200 cursor-pointer rounded-md p-3 min-h-[44px] flex items-center"
|
||||||
onClick={() => onSelectConversation(conversation)}
|
onClick={() => onSelectConversation(conversation)}
|
||||||
>
|
>
|
||||||
<p className="text-sm sm:text-base break-words">
|
<p className="text-sm sm:text-base truncate w-full">
|
||||||
{conversation.title}
|
{conversation.title}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect, useState, useRef } from "react";
|
||||||
|
|
||||||
|
type MessageInputProps = {
|
||||||
|
handleQueryChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
handleKeyDown: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
handleQuestionSubmit: () => void;
|
||||||
|
setSimbaMode: (sdf: boolean) => void;
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MessageInput = ({query, handleKeyDown, handleQueryChange, handleQuestionSubmit, setSimbaMode}: MessageInputProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 sticky bottom-0 bg-[#3D763A] p-6 rounded-xl">
|
||||||
|
<div className="flex flex-row justify-between grow">
|
||||||
|
<textarea
|
||||||
|
className="p-3 sm:p-4 border border-blue-200 rounded-md grow bg-[#F9F5EB] min-h-[44px] resize-y"
|
||||||
|
onChange={handleQueryChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
value={query}
|
||||||
|
rows={2}
|
||||||
|
placeholder="Type your message... (Press Enter to send, Shift+Enter for new line)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-between gap-2 grow">
|
||||||
|
<button
|
||||||
|
className="p-3 sm:p-4 min-h-[44px] border border-blue-400 bg-[#EDA541] hover:bg-blue-400 cursor-pointer rounded-md flex-grow text-sm sm:text-base"
|
||||||
|
onClick={() => handleQuestionSubmit()}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-center gap-2 grow items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(event) => setSimbaMode(event.target.checked)}
|
||||||
|
className="w-5 h-5 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<p className="text-sm sm:text-base">simba mode?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ type QuestionBubbleProps = {
|
|||||||
|
|
||||||
export const QuestionBubble = ({ text }: QuestionBubbleProps) => {
|
export const QuestionBubble = ({ text }: QuestionBubbleProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md bg-stone-200 p-3 sm:p-4 break-words overflow-wrap-anywhere text-sm sm:text-base">
|
<div className="w-2/3 rounded-md bg-stone-200 p-3 sm:p-4 break-words overflow-wrap-anywhere text-sm sm:text-base ml-auto">
|
||||||
🤦: {text}
|
🤦: {text}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
31
services/raggr/startup-dev.sh
Executable file
31
services/raggr/startup-dev.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Initializing database directories..."
|
||||||
|
mkdir -p /app/chromadb /app/database
|
||||||
|
|
||||||
|
echo "Waiting for frontend to build..."
|
||||||
|
while [ ! -f /app/raggr-frontend/dist/index.html ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "Frontend built successfully!"
|
||||||
|
|
||||||
|
echo "Running database migrations..."
|
||||||
|
aerich upgrade
|
||||||
|
|
||||||
|
echo "Initializing visited.db with indexed_documents table..."
|
||||||
|
python3 -c "
|
||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect('database/visited.db')
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute('CREATE TABLE IF NOT EXISTS indexed_documents (id INTEGER PRIMARY KEY AUTOINCREMENT, paperless_id INTEGER)')
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print('Database initialized successfully')
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "Starting reindex process..."
|
||||||
|
python main.py "" --reindex || echo "Reindex failed, continuing anyway..."
|
||||||
|
|
||||||
|
echo "Starting Flask application in debug mode..."
|
||||||
|
python app.py
|
||||||
Reference in New Issue
Block a user