chore: add pnpm workspace configuration for apps and packages
This commit is contained in:
20
.devcontainer/Dockerfile
Normal file
20
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM mcr.microsoft.com/devcontainers/typescript-node:24
|
||||||
|
|
||||||
|
# Install additional tools
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
postgresql-client \
|
||||||
|
redis-tools \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set up pnpm environment
|
||||||
|
ENV PNPM_HOME=/home/node/.local/share/pnpm
|
||||||
|
ENV PATH=$PNPM_HOME:$PATH
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# Install global packages
|
||||||
|
RUN pnpm install -g @nestjs/cli@latest
|
||||||
|
|
||||||
52
.devcontainer/devcontainer.json
Normal file
52
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "DreamChat Development",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspace",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"version": "24"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||||
|
"moby": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"ms-vscode.vscode-typescript-next",
|
||||||
|
"nestjs.vscode-nestjs",
|
||||||
|
"prisma.prisma"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"typescript.preferences.importModuleSpecifier": "relative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [3000, 5173, 5432],
|
||||||
|
"portsAttributes": {
|
||||||
|
"3000": {
|
||||||
|
"label": "Backend API",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"5173": {
|
||||||
|
"label": "Frontend Dev Server",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"5432": {
|
||||||
|
"label": "PostgreSQL",
|
||||||
|
"onAutoForward": "silent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "bash .devcontainer/post-create.sh",
|
||||||
|
"remoteUser": "node",
|
||||||
|
"mounts": [
|
||||||
|
"source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume",
|
||||||
|
"source=${localWorkspaceFolderBasename}-pnpm-store,target=/home/node/.local/share/pnpm/store,type=volume"
|
||||||
|
]
|
||||||
|
}
|
||||||
47
.devcontainer/docker-compose.yml
Normal file
47
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ..:/workspace:cached
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
command: sleep infinity
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:postgres@db:5432/dreamchat
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
# Keycloak is external - configure KEYCLOAK_URL in apps/backend/.env
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: ankane/pgvector:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: dreamchat
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dreamchat-network:
|
||||||
|
driver: bridge
|
||||||
76
.devcontainer/post-create.sh
Normal file
76
.devcontainer/post-create.sh
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Setting up DreamChat monorepo development environment..."
|
||||||
|
|
||||||
|
# Install all dependencies (uses pnpm workspaces)
|
||||||
|
echo "📦 Installing dependencies..."
|
||||||
|
cd /workspace
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Build shared packages first
|
||||||
|
echo "📦 Building shared packages..."
|
||||||
|
pnpm --filter @dreamchat/shared build || echo "Shared package build skipped (may not exist yet)"
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
echo "🔧 Generating Prisma client..."
|
||||||
|
cd /workspace/apps/backend
|
||||||
|
pnpm db:generate || echo "Prisma generate skipped (may not be set up yet)"
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# Copy environment files if they don't exist
|
||||||
|
if [ ! -f /workspace/apps/backend/.env ]; then
|
||||||
|
echo "⚙️ Creating backend .env file..."
|
||||||
|
mkdir -p /workspace/apps/backend
|
||||||
|
cat > /workspace/apps/backend/.env << EOF
|
||||||
|
NODE_ENV=development
|
||||||
|
PORT=3000
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/dreamchat
|
||||||
|
JWT_SECRET=dev-jwt-secret-change-in-production
|
||||||
|
JWT_EXPIRES_IN=1h
|
||||||
|
JWT_REFRESH_EXPIRES_IN=7d
|
||||||
|
LLM_PROVIDER=openrouter
|
||||||
|
LLM_API_KEY=your-openrouter-api-key
|
||||||
|
LLM_MODEL=openai/gpt-4o
|
||||||
|
|
||||||
|
# Keycloak (external) - configure if using external Keycloak
|
||||||
|
KEYCLOAK_ENABLED=false
|
||||||
|
# KEYCLOAK_URL=http://your-keycloak-server:8080
|
||||||
|
# KEYCLOAK_REALM=dreamchat
|
||||||
|
# KEYCLOAK_CLIENT_ID=dreamchat-backend
|
||||||
|
# KEYCLOAK_CLIENT_SECRET=your_keycloak_secret
|
||||||
|
|
||||||
|
# Keycloak Authorization (optional)
|
||||||
|
# KEYCLOAK_REQUIRED_GROUP=dreamchat-users
|
||||||
|
# KEYCLOAK_REQUIRED_ROLE=dreamchat-access
|
||||||
|
# KEYCLOAK_REQUIRED_CLIENT_ROLE=user
|
||||||
|
# KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
|
||||||
|
|
||||||
|
# Keycloak Auto-Create Users
|
||||||
|
KEYCLOAK_AUTO_CREATE_USER=true
|
||||||
|
KEYCLOAK_DEFAULT_USER_ROLE=USER
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /workspace/apps/frontend/.env ]; then
|
||||||
|
echo "⚙️ Creating frontend .env file..."
|
||||||
|
mkdir -p /workspace/apps/frontend
|
||||||
|
cat > /workspace/apps/frontend/.env << EOF
|
||||||
|
VITE_API_URL=http://localhost:3000/api
|
||||||
|
VITE_WS_URL=ws://localhost:3000
|
||||||
|
# Keycloak (external) - configure if using external Keycloak
|
||||||
|
# VITE_KEYCLOAK_URL=http://your-keycloak-server:8080
|
||||||
|
# VITE_KEYCLOAK_REALM=dreamchat
|
||||||
|
# VITE_KEYCLOAK_CLIENT_ID=dreamchat-frontend
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Development environment setup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Start all apps: pnpm dev"
|
||||||
|
echo "2. Or start individually:"
|
||||||
|
echo " - Backend: pnpm --filter @dreamchat/backend dev"
|
||||||
|
echo " - Frontend: pnpm --filter @dreamchat/frontend dev"
|
||||||
|
echo ""
|
||||||
|
echo "Note: Keycloak is external. Configure KEYCLOAK_URL in apps/backend/.env if needed."
|
||||||
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
56
.env.example
Normal file
56
.env.example
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Database
|
||||||
|
POSTGRES_PASSWORD=your_secure_password_here
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET=your_jwt_secret_key_min_32_chars
|
||||||
|
JWT_EXPIRES_IN=1h
|
||||||
|
JWT_REFRESH_EXPIRES_IN=7d
|
||||||
|
|
||||||
|
# LLM Configuration
|
||||||
|
LLM_PROVIDER=openrouter
|
||||||
|
LLM_API_KEY=sk-or-v1-...
|
||||||
|
LLM_MODEL=openai/gpt-4o
|
||||||
|
|
||||||
|
# Embedding Configuration (Local HuggingFace by default)
|
||||||
|
EMBEDDING_PROVIDER=local
|
||||||
|
EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2
|
||||||
|
EMBEDDING_DIMENSION=384
|
||||||
|
EMBEDDING_DEVICE=cpu
|
||||||
|
|
||||||
|
# HuggingFace API (optional - if not using local embeddings)
|
||||||
|
# HUGGINGFACE_API_KEY=hf_...
|
||||||
|
|
||||||
|
# Keycloak (External) Configuration
|
||||||
|
# Enable Keycloak authentication
|
||||||
|
KEYCLOAK_ENABLED=false
|
||||||
|
KEYCLOAK_URL=http://your-keycloak-server:8080
|
||||||
|
KEYCLOAK_REALM=dreamchat
|
||||||
|
KEYCLOAK_CLIENT_ID=dreamchat-backend
|
||||||
|
KEYCLOAK_CLIENT_SECRET=your_keycloak_secret
|
||||||
|
|
||||||
|
# Keycloak Authorization Settings
|
||||||
|
# Require specific group/role/attribute for access
|
||||||
|
# Set at least one of these to enforce authorization checks
|
||||||
|
|
||||||
|
# Required Keycloak group (e.g., "dreamchat-users")
|
||||||
|
KEYCLOAK_REQUIRED_GROUP=
|
||||||
|
|
||||||
|
# Required Keycloak realm role (e.g., "dreamchat-access")
|
||||||
|
KEYCLOAK_REQUIRED_ROLE=
|
||||||
|
|
||||||
|
# Required Keycloak client role (e.g., "user")
|
||||||
|
KEYCLOAK_REQUIRED_CLIENT_ROLE=
|
||||||
|
|
||||||
|
# Required Keycloak user attribute (format: "attribute_name:attribute_value")
|
||||||
|
# Examples:
|
||||||
|
# KEYCLOAK_REQUIRED_ATTRIBUTE=department:engineering
|
||||||
|
# KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
|
||||||
|
KEYCLOAK_REQUIRED_ATTRIBUTE=
|
||||||
|
|
||||||
|
# Auto-create users on first Keycloak login
|
||||||
|
# If true, users will be automatically created in the database
|
||||||
|
# If false, only existing users can log in via Keycloak
|
||||||
|
KEYCLOAK_AUTO_CREATE_USER=true
|
||||||
|
|
||||||
|
# Default role for auto-created Keycloak users
|
||||||
|
KEYCLOAK_DEFAULT_USER_ROLE=USER
|
||||||
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Prisma
|
||||||
|
prisma/migrations/*/migration_lock.toml
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.cache/
|
||||||
|
temp/
|
||||||
|
tmp/
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# DevContainer
|
||||||
|
.devcontainer/.pnpm-store/
|
||||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
shamefully-hoist=true
|
||||||
|
auto-install-peers=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
193
README.md
193
README.md
@@ -1,24 +1,191 @@
|
|||||||
# DreamChat
|
# DreamChat
|
||||||
|
|
||||||
## Overview
|
A character simulation and interactive storytelling platform with AI-powered conversations.
|
||||||
|
|
||||||
This project is designed to simulate characters, enable interactive conversations with them, and generate stories based on user-provided templates and backgrounds. The platform focuses on creating immersive, personalized fan-like experiences where users can interact with their favorite simulated personas.
|
## Features
|
||||||
|
|
||||||
## Core Features
|
- **Character Simulation**: Create custom characters with personalities, attributes, and backstories
|
||||||
|
- **Interactive Dialogue**: Real-time chat with AI characters using WebSocket streaming
|
||||||
|
- **Story Generation**: Branching narratives with tree-view visualization
|
||||||
|
- **Vector Memory**: Context-aware conversations using local embeddings
|
||||||
|
- **Data Import**: Import character data from files (TXT, PDF, MD) or web sources
|
||||||
|
- **Multi-Character Chat**: Group conversations with multiple characters (Phase 3)
|
||||||
|
|
||||||
- Character Simulation: Users can define and customize characters with attributes, personalities, and backstories.
|
## Tech Stack
|
||||||
- Interactive Dialogue: The main focus is enabling users to talk directly to their simulated characters, using chat data provided by the user.
|
|
||||||
- Story Generation: The system generates narratives based on user-defined templates, backgrounds, and character interactions.
|
|
||||||
- User Data Import: Users can import their own chat histories, blogs, or other text data to enrich character simulation and personalize interactions.
|
|
||||||
|
|
||||||
## Key Requirements
|
- **Backend**: NestJS + TypeScript + Prisma + PostgreSQL (pgvector)
|
||||||
|
- **Frontend**: React + Vite + TypeScript + Tailwind CSS
|
||||||
|
- **Package Manager**: pnpm workspaces (monorepo)
|
||||||
|
- **AI/LLM**: OpenRouter with flexible provider support
|
||||||
|
- **Embeddings**: Local HuggingFace models (@xenova/transformers)
|
||||||
|
- **Auth**: Password-based (with optional external Keycloak SSO supporting group/role/attribute authorization)
|
||||||
|
- **DevOps**: Docker + DevContainer (external reverse proxy expected)
|
||||||
|
|
||||||
User-Centric Design: Prioritize ease of use, intuitive character creation, and natural conversational flow.
|
## Quick Start
|
||||||
|
|
||||||
## Target Audience
|
### Using DevContainer (Recommended)
|
||||||
|
|
||||||
- Storytellers and roleplayers seeking immersive character-driven narratives.
|
1. Open project in VS Code
|
||||||
|
2. Install [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||||
|
3. Press `F1` → "Dev Containers: Reopen in Container"
|
||||||
|
4. Wait for setup to complete
|
||||||
|
5. Start development:
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
## Goals
|
### Manual Setup
|
||||||
|
|
||||||
Deliver a platform where users can define, interact, and narrate with their characters.
|
Prerequisites:
|
||||||
|
- Node.js 20+
|
||||||
|
- pnpm 8+
|
||||||
|
- PostgreSQL 15+ with pgvector extension
|
||||||
|
- Redis (optional)
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build shared packages:
|
||||||
|
```bash
|
||||||
|
pnpm --filter @dreamchat/shared build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Setup database:
|
||||||
|
```bash
|
||||||
|
pnpm db:generate
|
||||||
|
pnpm db:migrate
|
||||||
|
pnpm db:seed
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create environment files:
|
||||||
|
```bash
|
||||||
|
cp .env.example apps/backend/.env
|
||||||
|
cp .env.example apps/frontend/.env
|
||||||
|
# Edit both files with your configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Start development:
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
dreamchat/
|
||||||
|
├── apps/
|
||||||
|
│ ├── backend/ # NestJS API
|
||||||
|
│ └── frontend/ # React + Vite SPA
|
||||||
|
├── packages/
|
||||||
|
│ └── shared/ # Shared types & WebSocket definitions
|
||||||
|
├── prisma/
|
||||||
|
│ ├── schema.prisma # Main schema (imports from models/)
|
||||||
|
│ ├── seed.ts # Seed data
|
||||||
|
│ └── models/ # Individual model files
|
||||||
|
│ ├── user.prisma
|
||||||
|
│ ├── character.prisma
|
||||||
|
│ └── ...
|
||||||
|
├── .devcontainer/ # DevContainer configuration
|
||||||
|
├── docker-compose.yml # Production Docker Compose
|
||||||
|
└── doc/ # Project documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See the `doc/` folder for comprehensive documentation:
|
||||||
|
|
||||||
|
- [architecture.md](doc/architecture.md) - System architecture and design
|
||||||
|
- [monorepo-guide.md](doc/monorepo-guide.md) - pnpm workspace setup
|
||||||
|
- [database-schema.md](doc/database-schema.md) - Database schema (Prisma)
|
||||||
|
- [api-spec.md](doc/api-spec.md) - REST API & WebSocket specifications
|
||||||
|
- [implementation-plan.md](doc/implementation-plan.md) - Phased roadmap
|
||||||
|
- [frontend-guide.md](doc/frontend-guide.md) - Frontend architecture
|
||||||
|
- [deployment.md](doc/deployment.md) - Deployment guide
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Start all apps in development mode
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# Build all packages
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pnpm test
|
||||||
|
|
||||||
|
# Database commands
|
||||||
|
pnpm db:generate # Generate Prisma client
|
||||||
|
pnpm db:migrate # Run migrations
|
||||||
|
pnpm db:studio # Open Prisma Studio
|
||||||
|
pnpm db:seed # Seed database
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
pnpm lint
|
||||||
|
|
||||||
|
# Clean
|
||||||
|
pnpm clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Backend (`apps/backend/.env`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_ENV=development
|
||||||
|
PORT=3000
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/dreamchat
|
||||||
|
JWT_SECRET=your-secret-key
|
||||||
|
LLM_PROVIDER=openrouter
|
||||||
|
LLM_API_KEY=your-api-key
|
||||||
|
LLM_MODEL=openai/gpt-4o
|
||||||
|
EMBEDDING_PROVIDER=local
|
||||||
|
EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2
|
||||||
|
|
||||||
|
# Keycloak (optional)
|
||||||
|
KEYCLOAK_ENABLED=false
|
||||||
|
KEYCLOAK_URL=http://your-keycloak-server:8080
|
||||||
|
KEYCLOAK_REALM=dreamchat
|
||||||
|
KEYCLOAK_CLIENT_ID=dreamchat-backend
|
||||||
|
KEYCLOAK_CLIENT_SECRET=your-secret
|
||||||
|
|
||||||
|
# Keycloak Authorization (optional)
|
||||||
|
KEYCLOAK_REQUIRED_GROUP=dreamchat-users
|
||||||
|
KEYCLOAK_REQUIRED_ROLE=dreamchat-access
|
||||||
|
KEYCLOAK_AUTO_CREATE_USER=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (`apps/frontend/.env`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VITE_API_URL=http://localhost:3000/api
|
||||||
|
VITE_WS_URL=ws://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
See [deployment.md](doc/deployment.md) for production deployment instructions.
|
||||||
|
|
||||||
|
Quick deployment with Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy and edit environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Build and start services
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
docker-compose exec backend pnpm db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** An external reverse proxy (nginx, Traefik, etc.) is expected for SSL termination and routing. See [deployment.md](doc/deployment.md) for configuration examples.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
57
apps/backend/Dockerfile
Normal file
57
apps/backend/Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# apps/backend/Dockerfile
|
||||||
|
FROM node:24-alpine AS base
|
||||||
|
RUN npm install -g pnpm@8
|
||||||
|
|
||||||
|
FROM base AS dependencies
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy workspace configuration
|
||||||
|
COPY pnpm-workspace.yaml package.json ./
|
||||||
|
COPY apps/backend/package.json ./apps/backend/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=dependencies /app/node_modules ./node_modules
|
||||||
|
COPY --from=dependencies /app/apps/backend/node_modules ./apps/backend/node_modules
|
||||||
|
COPY --from=dependencies /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY packages/shared ./packages/shared
|
||||||
|
COPY apps/backend ./apps/backend
|
||||||
|
COPY prisma ./prisma
|
||||||
|
|
||||||
|
# Build shared packages first
|
||||||
|
RUN pnpm --filter @dreamchat/shared build
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
RUN pnpm db:generate
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
RUN pnpm --filter @dreamchat/backend build
|
||||||
|
|
||||||
|
FROM base AS production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy only production dependencies
|
||||||
|
COPY --from=dependencies /app/node_modules ./node_modules
|
||||||
|
COPY --from=dependencies /app/apps/backend/node_modules ./apps/backend/node_modules
|
||||||
|
COPY --from=build /app/apps/backend/dist ./dist
|
||||||
|
COPY --from=build /app/packages/shared/dist ./node_modules/@dreamchat/shared/dist
|
||||||
|
COPY --from=build /app/node_modules/.pnpm/@prisma+client* ./node_modules/.pnpm/
|
||||||
|
COPY --from=build /app/node_modules/@prisma ./node_modules/@prisma
|
||||||
|
|
||||||
|
# Create logs directory
|
||||||
|
RUN mkdir -p /app/logs
|
||||||
|
|
||||||
|
# Non-root user
|
||||||
|
RUN addgroup -g 1001 -S nodejs
|
||||||
|
RUN adduser -S nodejs -u 1001
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
50
apps/backend/package.json
Normal file
50
apps/backend/package.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "@dreamchat/backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"dev": "nest start --watch",
|
||||||
|
"start": "node dist/main",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||||
|
"db:migrate": "prisma migrate deploy",
|
||||||
|
"db:generate": "prisma generate",
|
||||||
|
"db:seed": "prisma db seed",
|
||||||
|
"clean": "rm -r dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dreamchat/shared": "workspace:*",
|
||||||
|
"@nestjs/common": "^11.1.14",
|
||||||
|
"@nestjs/core": "^11.1.14",
|
||||||
|
"@nestjs/platform-express": "^11.1.14",
|
||||||
|
"@nestjs/platform-socket.io": "^11.1.14",
|
||||||
|
"@nestjs/websockets": "^11.1.14",
|
||||||
|
"@prisma/client": "^7.4.1",
|
||||||
|
"@xenova/transformers": "^2.15.0",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.0",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.0",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"puppeteer": "^24.37.5",
|
||||||
|
"reflect-metadata": "^0.2.0",
|
||||||
|
"rxjs": "^7.8.0",
|
||||||
|
"socket.io": "^4.7.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^11.0.16",
|
||||||
|
"@nestjs/testing": "^11.1.14",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/jsonwebtoken": "^9.0.0",
|
||||||
|
"@types/node": "^24.10.13",
|
||||||
|
"@types/passport-jwt": "^4.0.0",
|
||||||
|
"@types/passport-local": "^1.0.0",
|
||||||
|
"jest": "^30.2.0",
|
||||||
|
"prisma": "^7.4.1",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
apps/backend/prisma.config.ts
Normal file
13
apps/backend/prisma.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig, env } from 'prisma/config';
|
||||||
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
seed: 'tsx prisma/seed.ts',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
});
|
||||||
14
apps/backend/prisma/schema.prisma
Normal file
14
apps/backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["strictUndefinedChecks"]
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
55
apps/backend/prisma/seed.ts
Normal file
55
apps/backend/prisma/seed.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🌱 Seeding database...');
|
||||||
|
|
||||||
|
// Create default admin user
|
||||||
|
const admin = await prisma.user.upsert({
|
||||||
|
where: { email: 'admin@dreamchat.local' },
|
||||||
|
update: {},
|
||||||
|
create: {
|
||||||
|
email: 'admin@dreamchat.local',
|
||||||
|
username: 'admin',
|
||||||
|
role: 'ADMIN',
|
||||||
|
passwordHash: '$2b$10$YourHashedPasswordHere', // Replace with actual hash
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Created admin user: ${admin.email}`);
|
||||||
|
|
||||||
|
// Create a sample character
|
||||||
|
const character = await prisma.character.upsert({
|
||||||
|
where: {
|
||||||
|
id: '00000000-0000-0000-0000-000000000001'
|
||||||
|
},
|
||||||
|
update: {},
|
||||||
|
create: {
|
||||||
|
id: '00000000-0000-0000-0000-000000000001',
|
||||||
|
name: 'Alice',
|
||||||
|
personalityPrompt: 'You are Alice, a curious and adventurous explorer who loves discovering new things. You are friendly, witty, and always eager to help.',
|
||||||
|
backstory: 'Alice grew up in a small village at the edge of a vast forest. From a young age, she was fascinated by the unknown and would often venture into the woods to explore.',
|
||||||
|
attributes: {
|
||||||
|
traits: ['curious', 'brave', 'witty', 'friendly'],
|
||||||
|
age: 25,
|
||||||
|
species: 'human',
|
||||||
|
skills: ['navigation', 'survival', 'cartography'],
|
||||||
|
},
|
||||||
|
userId: admin.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Created sample character: ${character.name}`);
|
||||||
|
|
||||||
|
console.log('✅ Seeding complete!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('❌ Seeding failed:', e);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
21
apps/backend/tsconfig.json
Normal file
21
apps/backend/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "ES2021",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"forceConsistentCasingInFileNames": false,
|
||||||
|
"noFallthroughCasesInSwitch": false
|
||||||
|
}
|
||||||
|
}
|
||||||
55
apps/frontend/Dockerfile
Normal file
55
apps/frontend/Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# apps/frontend/Dockerfile
|
||||||
|
FROM node:24-alpine AS base
|
||||||
|
RUN npm install -g pnpm@8
|
||||||
|
|
||||||
|
FROM base AS dependencies
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy workspace configuration
|
||||||
|
COPY pnpm-workspace.yaml package.json ./
|
||||||
|
COPY apps/frontend/package.json ./apps/frontend/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=dependencies /app/node_modules ./node_modules
|
||||||
|
COPY --from=dependencies /app/apps/frontend/node_modules ./apps/frontend/node_modules
|
||||||
|
COPY --from=dependencies /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY packages/shared ./packages/shared
|
||||||
|
COPY apps/frontend ./apps/frontend
|
||||||
|
|
||||||
|
# Build shared packages first
|
||||||
|
RUN pnpm --filter @dreamchat/shared build
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
RUN pnpm --filter @dreamchat/frontend build
|
||||||
|
|
||||||
|
# Production stage - using serve for static files
|
||||||
|
# External reverse proxy (nginx/traefik/etc.) expected
|
||||||
|
FROM node:24-alpine AS production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install serve
|
||||||
|
RUN npm install -g serve
|
||||||
|
|
||||||
|
# Copy built assets
|
||||||
|
COPY --from=build /app/apps/frontend/dist ./dist
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 -S nodejs
|
||||||
|
RUN adduser -S nodejs -u 1001
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Serve static files
|
||||||
|
# Note: External reverse proxy should handle:
|
||||||
|
# - SSL/TLS termination
|
||||||
|
# - Path routing (/api -> backend, / -> frontend)
|
||||||
|
# - WebSocket proxying
|
||||||
|
CMD ["serve", "-s", "dist", "-l", "3000"]
|
||||||
33
apps/frontend/package.json
Normal file
33
apps/frontend/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@dreamchat/frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test": "vitest",
|
||||||
|
"lint": "eslint . --ext ts,tsx",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dreamchat/shared": "workspace:*",
|
||||||
|
"@tanstack/react-query": "^5.0.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.20.0",
|
||||||
|
"socket.io-client": "^4.7.0",
|
||||||
|
"zustand": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"autoprefixer": "^10.4.0",
|
||||||
|
"postcss": "^8.4.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"typescript": "^5.3.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vitest": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
apps/frontend/tsconfig.json
Normal file
25
apps/frontend/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
apps/frontend/tsconfig.node.json
Normal file
10
apps/frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
30
apps/frontend/vite.config.ts
Normal file
30
apps/frontend/vite.config.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
// Development proxy - external reverse proxy used in production
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/ws': {
|
||||||
|
target: 'ws://localhost:3000',
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -101,7 +101,7 @@ DreamChat is a character simulation platform built with a modular, extensible ar
|
|||||||
#### 1. Auth Module
|
#### 1. Auth Module
|
||||||
```typescript
|
```typescript
|
||||||
// Dual authentication strategy
|
// Dual authentication strategy
|
||||||
- KeycloakStrategy (OAuth2/OIDC)
|
- KeycloakStrategy (OAuth2/OIDC with group/role/attribute authorization)
|
||||||
- LocalStrategy (Password-based)
|
- LocalStrategy (Password-based)
|
||||||
- JWT Guard for stateless auth
|
- JWT Guard for stateless auth
|
||||||
- Roles: USER, ADMIN
|
- Roles: USER, ADMIN
|
||||||
@@ -110,6 +110,13 @@ DreamChat is a character simulation platform built with a modular, extensible ar
|
|||||||
- id, email, username
|
- id, email, username
|
||||||
- passwordHash, keycloakSub
|
- passwordHash, keycloakSub
|
||||||
- role, isActive
|
- role, isActive
|
||||||
|
|
||||||
|
// Keycloak Authorization
|
||||||
|
- Validates group membership (KEYCLOAK_REQUIRED_GROUP)
|
||||||
|
- Checks realm roles (KEYCLOAK_REQUIRED_ROLE)
|
||||||
|
- Checks client roles (KEYCLOAK_REQUIRED_CLIENT_ROLE)
|
||||||
|
- Validates user attributes (KEYCLOAK_REQUIRED_ATTRIBUTE)
|
||||||
|
- Auto-creates users on first login (if KEYCLOAK_AUTO_CREATE_USER=true)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Character Module
|
#### 2. Character Module
|
||||||
@@ -119,14 +126,23 @@ DreamChat is a character simulation platform built with a modular, extensible ar
|
|||||||
- CharacterRepository (Prisma)
|
- CharacterRepository (Prisma)
|
||||||
- DTOs: CreateCharacterDto, UpdateCharacterDto, CharacterResponseDto
|
- DTOs: CreateCharacterDto, UpdateCharacterDto, CharacterResponseDto
|
||||||
|
|
||||||
Entities:
|
Prisma Models:
|
||||||
- Character
|
- Character
|
||||||
- id, name, avatar
|
- id, name, avatarUrl
|
||||||
- personalityPrompt: string
|
- personalityPrompt: string
|
||||||
- attributes: JSON (complex attribute system)
|
- attributes: JSON (complex attribute system)
|
||||||
- backstory: string
|
- knowledgeSources: CharacterKnowledge[]
|
||||||
|
- vectorMemories: VectorMemory[]
|
||||||
- createdBy: User
|
- createdBy: User
|
||||||
- createdAt, updatedAt
|
|
||||||
|
- CharacterKnowledge
|
||||||
|
- id, name, sourceType (file/url/manual)
|
||||||
|
- sourceName, mimeType, fileSize
|
||||||
|
- rawContent: string
|
||||||
|
- status (pending/processing/completed/failed)
|
||||||
|
- processingInfo: JSON
|
||||||
|
- vectorMemories: VectorMemory[]
|
||||||
|
- characterId: Character
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. Chat Module (MVP)
|
#### 3. Chat Module (MVP)
|
||||||
@@ -169,21 +185,32 @@ WebSocket Events:
|
|||||||
- HuggingFaceAPIProvider: Uses HuggingFace Inference API
|
- HuggingFaceAPIProvider: Uses HuggingFace Inference API
|
||||||
|
|
||||||
- VectorStoreService (uses Prisma with pgvector extension)
|
- VectorStoreService (uses Prisma with pgvector extension)
|
||||||
- addDocument(conversationId, content, metadata)
|
- addDocument(targetId, content, metadata, memoryType)
|
||||||
- similaritySearch(conversationId, query, k=5)
|
- similaritySearch(targetId, query, k=5, memoryType)
|
||||||
|
- Supports both conversation and character memory
|
||||||
- Uses raw Prisma queries with pgvector operators
|
- Uses raw Prisma queries with pgvector operators
|
||||||
|
|
||||||
- MemoryManager
|
- MemoryManager
|
||||||
- buildContext(conversationId, currentMessage): string
|
- buildConversationContext(conversationId, currentMessage): string
|
||||||
|
- buildCharacterContext(characterId, query): string
|
||||||
- summarizeOldMessages(conversationId): Promise<void>
|
- summarizeOldMessages(conversationId): Promise<void>
|
||||||
- retrieveRelevantMemories(conversationId, query): Document[]
|
- retrieveRelevantMemories(targetId, query, memoryType): Document[]
|
||||||
|
|
||||||
|
- CharacterKnowledgeService
|
||||||
|
- importKnowledge(characterId, file/url)
|
||||||
|
- chunkAndEmbed(knowledgeId, content)
|
||||||
|
- processKnowledgeSource(knowledgeId)
|
||||||
|
- searchCharacterKnowledge(characterId, query)
|
||||||
|
|
||||||
Prisma Model:
|
Prisma Model:
|
||||||
- VectorMemory
|
- VectorMemory
|
||||||
- id
|
- id
|
||||||
- conversationId (relation)
|
|
||||||
- content: String
|
- content: String
|
||||||
- embedding: Unsupported("vector") // pgvector type
|
- embedding: Unsupported("vector")
|
||||||
|
- memoryType: enum ('conversation' | 'character')
|
||||||
|
- conversationId?: Conversation
|
||||||
|
- characterId?: Character
|
||||||
|
- knowledgeId?: CharacterKnowledge
|
||||||
- metadata: Json?
|
- metadata: Json?
|
||||||
- createdAt: DateTime
|
- createdAt: DateTime
|
||||||
```
|
```
|
||||||
@@ -235,6 +262,23 @@ class DataPreprocessor {
|
|||||||
chunk(text: string, maxChunkSize: number): string[];
|
chunk(text: string, maxChunkSize: number): string[];
|
||||||
extractEntities(text: string): Entity[];
|
extractEntities(text: string): Entity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Character Knowledge Import Service
|
||||||
|
class CharacterKnowledgeService {
|
||||||
|
// Import file/URL as character knowledge
|
||||||
|
importKnowledge(characterId: string, file: File): Promise<CharacterKnowledge>;
|
||||||
|
importFromUrl(characterId: string, url: string): Promise<CharacterKnowledge>;
|
||||||
|
|
||||||
|
// Process and embed knowledge
|
||||||
|
processKnowledge(knowledgeId: string): Promise<void>;
|
||||||
|
chunkAndEmbed(knowledgeId: string, content: string): Promise<void>;
|
||||||
|
|
||||||
|
// Search character knowledge
|
||||||
|
searchKnowledge(characterId: string, query: string): Promise<VectorMemory[]>;
|
||||||
|
|
||||||
|
// Get knowledge context for LLM
|
||||||
|
buildKnowledgeContext(characterId: string, query: string): string;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend (React + Vite)
|
### Frontend (React + Vite)
|
||||||
@@ -438,9 +482,17 @@ dreamchat/
|
|||||||
│ └── typescript/
|
│ └── typescript/
|
||||||
│
|
│
|
||||||
├── prisma/ # Database schema (shared)
|
├── prisma/ # Database schema (shared)
|
||||||
│ ├── schema.prisma
|
│ ├── schema.prisma # Main schema with imports
|
||||||
│ ├── migrations/
|
│ ├── migrations/
|
||||||
│ └── seed.ts
|
│ ├── seed.ts
|
||||||
|
│ └── models/ # Individual model files
|
||||||
|
│ ├── user.prisma
|
||||||
|
│ ├── character.prisma
|
||||||
|
│ ├── conversation.prisma
|
||||||
|
│ ├── message.prisma
|
||||||
|
│ ├── vectorMemory.prisma
|
||||||
|
│ ├── importDocument.prisma
|
||||||
|
│ └── storyBranch.prisma
|
||||||
│
|
│
|
||||||
├── docker-compose.yml
|
├── docker-compose.yml
|
||||||
├── pnpm-workspace.yaml
|
├── pnpm-workspace.yaml
|
||||||
|
|||||||
@@ -23,35 +23,41 @@ CREATE EXTENSION IF NOT EXISTS "pgvector";
|
|||||||
│ username │ │ name │ │ user_id (FK) │
|
│ username │ │ name │ │ user_id (FK) │
|
||||||
│ password_hash │ │ avatar_url │ │ title │
|
│ password_hash │ │ avatar_url │ │ title │
|
||||||
│ keycloak_sub │ │ personality │ │ created_at │
|
│ keycloak_sub │ │ personality │ │ created_at │
|
||||||
│ role │ │ backstory │ │ updated_at │
|
│ role │ │ attributes │ │ updated_at │
|
||||||
│ created_at │ │ attributes │ └────────┬────────┘
|
│ created_at │ │ created_at │ └────────┬────────┘
|
||||||
│ updated_at │ │ created_at │ │
|
│ updated_at │ │ updated_at │ │
|
||||||
└─────────────────┘ │ updated_at │ │
|
└─────────────────┘ └────────┬────────┘ │
|
||||||
└─────────────────┘ │
|
│ │
|
||||||
┌─────────────────┐ │
|
┌────────┴────────┐ │
|
||||||
│import_documents │ │
|
│character_knowledge│ │
|
||||||
├─────────────────┤ │
|
├─────────────────┤ │
|
||||||
│ id (PK) │ │
|
│ id (PK) │◄───────────────┤
|
||||||
│ user_id (FK) │ │
|
│ character_id │ │
|
||||||
│ source_type │ ┌─────────────────┐ │
|
│ name │ │
|
||||||
│ source_name │ │ messages │◄───────────────┘
|
│ source_type │ │
|
||||||
│ content │ ├─────────────────┤
|
│ raw_content │ │
|
||||||
│ metadata │ │ id (PK) │
|
│ status │ │
|
||||||
│ vector_id │ │ conversation_id │
|
└────────┬────────┘ │
|
||||||
│ created_at │ │ role │
|
│ │
|
||||||
└─────────────────┘ │ content │
|
┌─────────────────┐ ┌────────┴────────┐ ┌────────┴────────┐
|
||||||
│ tokens_used │
|
│import_documents │ │ vector_memories│ │ messages │
|
||||||
│ model │
|
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||||
┌─────────────────┐ │ metadata │
|
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||||
│vector_memories │ │ created_at │
|
│ user_id (FK) │ │ content │ │ conversation_id │
|
||||||
├─────────────────┤ └─────────────────┘
|
│ source_type │ │ embedding │ │ role │
|
||||||
|
│ source_name │ │ memory_type │ │ content │
|
||||||
|
│ content │ │ conversation_id │ │ tokens_used │
|
||||||
|
│ status │ │ character_id │ │ model │
|
||||||
|
└─────────────────┘ │ knowledge_id │ │ metadata │
|
||||||
|
│ created_at │ │ created_at │
|
||||||
|
└─────────────────┘ └─────────────────┘
|
||||||
|
│
|
||||||
|
┌────────┴────────┐
|
||||||
|
│ story_branches │ (Phase 2)
|
||||||
|
├─────────────────┤
|
||||||
│ id (PK) │
|
│ id (PK) │
|
||||||
│ conversation_id │ ┌─────────────────┐
|
│ conversation_id │
|
||||||
│ content │ │ story_branches │ (Phase 2)
|
│ parent_id (FK) │
|
||||||
│ embedding │ ├─────────────────┤
|
|
||||||
│ metadata │ │ id (PK) │
|
|
||||||
│ created_at │ │ conversation_id │
|
|
||||||
└─────────────────┘ │ parent_id (FK) │
|
|
||||||
│ content │
|
│ content │
|
||||||
│ direction │
|
│ direction │
|
||||||
│ metadata │
|
│ metadata │
|
||||||
@@ -89,7 +95,7 @@ CREATE INDEX idx_users_keycloak_sub ON users(keycloak_sub);
|
|||||||
|
|
||||||
### 2. characters
|
### 2. characters
|
||||||
|
|
||||||
Character definitions with complex attribute system (JSONB for flexibility).
|
Character definitions with complex attribute system. Character knowledge is stored separately in `character_knowledge` with embeddings.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE characters (
|
CREATE TABLE characters (
|
||||||
@@ -101,9 +107,6 @@ CREATE TABLE characters (
|
|||||||
-- Core personality prompt sent to LLM
|
-- Core personality prompt sent to LLM
|
||||||
personality_prompt TEXT NOT NULL,
|
personality_prompt TEXT NOT NULL,
|
||||||
|
|
||||||
-- Backstory context for the character
|
|
||||||
backstory TEXT,
|
|
||||||
|
|
||||||
-- Complex attribute system (structured JSON)
|
-- Complex attribute system (structured JSON)
|
||||||
-- Example: {"traits": ["brave", "witty"], "age": 25, "species": "human"}
|
-- Example: {"traits": ["brave", "witty"], "age": 25, "species": "human"}
|
||||||
attributes JSONB DEFAULT '{}',
|
attributes JSONB DEFAULT '{}',
|
||||||
@@ -181,19 +184,53 @@ CREATE INDEX idx_messages_created_at ON messages(created_at);
|
|||||||
CREATE INDEX idx_messages_conversation_created ON messages(conversation_id, created_at);
|
CREATE INDEX idx_messages_conversation_created ON messages(conversation_id, created_at);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. vector_memories
|
### 5. character_knowledge
|
||||||
|
|
||||||
Vector embeddings for conversation memory using pgvector. Stores chunked content for semantic search.
|
Multiple knowledge sources for characters. Each source is chunked and stored with embeddings in `vector_memories`.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Create vector extension first
|
CREATE TYPE import_source_type AS ENUM ('file', 'url', 'manual');
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
CREATE TYPE import_status AS ENUM ('pending', 'processing', 'completed', 'failed');
|
||||||
|
|
||||||
|
CREATE TABLE character_knowledge (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Knowledge source info
|
||||||
|
name VARCHAR(255) NOT NULL, -- Display name
|
||||||
|
source_type import_source_type NOT NULL,
|
||||||
|
source_name VARCHAR(255) NOT NULL, -- Original filename or URL
|
||||||
|
mime_type VARCHAR(100),
|
||||||
|
file_size BIGINT,
|
||||||
|
|
||||||
|
-- Raw content (before chunking)
|
||||||
|
raw_content TEXT,
|
||||||
|
|
||||||
|
-- Processing status
|
||||||
|
status import_status DEFAULT 'pending',
|
||||||
|
processing_info JSONB, -- chunks count, errors, etc.
|
||||||
|
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_character_knowledge_character ON character_knowledge(character_id);
|
||||||
|
CREATE INDEX idx_character_knowledge_status ON character_knowledge(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. vector_memories
|
||||||
|
|
||||||
|
Unified vector embeddings storage for:
|
||||||
|
- **Character knowledge** - Background info, imported documents (linked to `character_knowledge`)
|
||||||
|
- **Conversation history** - Chat context (linked to `conversations`)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TYPE memory_type AS ENUM ('conversation', 'character');
|
||||||
|
|
||||||
CREATE TABLE vector_memories (
|
CREATE TABLE vector_memories (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
||||||
|
|
||||||
-- The text content
|
-- The text chunk
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
|
|
||||||
-- Vector embedding (configurable dimension based on model)
|
-- Vector embedding (configurable dimension based on model)
|
||||||
@@ -201,14 +238,21 @@ CREATE TABLE vector_memories (
|
|||||||
-- Must match the EMBEDDING_DIMENSION env var
|
-- Must match the EMBEDDING_DIMENSION env var
|
||||||
embedding VECTOR({{EMBEDDING_DIMENSION}}),
|
embedding VECTOR({{EMBEDDING_DIMENSION}}),
|
||||||
|
|
||||||
-- Metadata for filtering
|
-- Memory type determines the context
|
||||||
metadata JSONB DEFAULT '{
|
memory_type memory_type DEFAULT 'conversation',
|
||||||
"chunk_index": 0,
|
|
||||||
"source": "conversation",
|
|
||||||
"timestamp": null
|
|
||||||
}',
|
|
||||||
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
-- Metadata (chunk_index, source_info, etc.)
|
||||||
|
metadata JSONB,
|
||||||
|
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
|
||||||
|
-- Polymorphic relations (at least one must be set)
|
||||||
|
-- For conversation context
|
||||||
|
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- For character knowledge
|
||||||
|
character_id UUID REFERENCES characters(id) ON DELETE CASCADE,
|
||||||
|
knowledge_id UUID REFERENCES character_knowledge(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- HNSW index for efficient similarity search
|
-- HNSW index for efficient similarity search
|
||||||
@@ -216,22 +260,22 @@ CREATE TABLE vector_memories (
|
|||||||
-- CREATE INDEX idx_vector_memories_embedding ON vector_memories
|
-- CREATE INDEX idx_vector_memories_embedding ON vector_memories
|
||||||
-- USING hnsw (embedding vector_cosine_ops);
|
-- USING hnsw (embedding vector_cosine_ops);
|
||||||
|
|
||||||
CREATE INDEX idx_vector_memories_conversation ON vector_memories(conversation_id);
|
CREATE INDEX idx_vector_memories_conversation ON vector_memories(conversation_id) WHERE conversation_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_vector_memories_character ON vector_memories(character_id) WHERE character_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_vector_memories_knowledge ON vector_memories(knowledge_id) WHERE knowledge_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_vector_memories_type ON vector_memories(memory_type);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. import_documents
|
### 7. import_documents
|
||||||
|
|
||||||
Raw imported documents from files or web scraping.
|
General-purpose imported documents (not linked to characters). For character knowledge, use `character_knowledge`.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TYPE import_source_type AS ENUM ('file', 'url');
|
|
||||||
CREATE TYPE import_status AS ENUM ('pending', 'processing', 'completed', 'failed');
|
|
||||||
|
|
||||||
CREATE TABLE import_documents (
|
CREATE TABLE import_documents (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
source_type import_source_type NOT NULL,
|
source_type import_source_type NOT NULL, -- file, url, manual
|
||||||
source_name VARCHAR(255) NOT NULL, -- filename or URL
|
source_name VARCHAR(255) NOT NULL, -- filename or URL
|
||||||
|
|
||||||
-- Mime type for files
|
-- Mime type for files
|
||||||
@@ -258,7 +302,7 @@ CREATE INDEX idx_import_documents_user_id ON import_documents(user_id);
|
|||||||
CREATE INDEX idx_import_documents_status ON import_documents(status);
|
CREATE INDEX idx_import_documents_status ON import_documents(status);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. story_branches (Phase 2)
|
### 8. story_branches (Phase 2)
|
||||||
|
|
||||||
Tree structure for branching narratives.
|
Tree structure for branching narratives.
|
||||||
|
|
||||||
@@ -291,7 +335,7 @@ CREATE INDEX idx_story_branches_conversation ON story_branches(conversation_id);
|
|||||||
CREATE INDEX idx_story_branches_parent ON story_branches(parent_id);
|
CREATE INDEX idx_story_branches_parent ON story_branches(parent_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8. conversation_participants (Phase 3 - Multi-Character)
|
### 9. conversation_participants (Phase 3 - Multi-Character)
|
||||||
|
|
||||||
Supports multiple characters in a single conversation.
|
Supports multiple characters in a single conversation.
|
||||||
|
|
||||||
@@ -313,12 +357,73 @@ CREATE TABLE conversation_participants (
|
|||||||
CREATE INDEX idx_participants_conversation ON conversation_participants(conversation_id);
|
CREATE INDEX idx_participants_conversation ON conversation_participants(conversation_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Enums
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- User roles
|
||||||
|
CREATE TYPE user_role AS ENUM ('USER', 'ADMIN');
|
||||||
|
|
||||||
|
-- Message roles
|
||||||
|
CREATE TYPE message_role AS ENUM ('user', 'assistant', 'system');
|
||||||
|
|
||||||
|
-- Import/knowledge source types
|
||||||
|
CREATE TYPE import_source_type AS ENUM ('file', 'url', 'manual');
|
||||||
|
|
||||||
|
-- Processing status
|
||||||
|
CREATE TYPE import_status AS ENUM ('pending', 'processing', 'completed', 'failed');
|
||||||
|
|
||||||
|
-- Vector memory types
|
||||||
|
CREATE TYPE memory_type AS ENUM ('conversation', 'character');
|
||||||
|
```
|
||||||
|
|
||||||
## Prisma Schema (Reference)
|
## Prisma Schema (Reference)
|
||||||
|
|
||||||
|
Prisma schema uses the [multi-file schema](https://www.prisma.io/docs/orm/prisma-schema/overview/location) feature. Models are organized in `prisma/models/` folder and imported into `schema.prisma`.
|
||||||
|
|
||||||
|
### Schema Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
prisma/
|
||||||
|
├── schema.prisma # Main schema file with imports
|
||||||
|
├── seed.ts # Database seeding
|
||||||
|
└── models/
|
||||||
|
├── user.prisma # User model + UserRole enum
|
||||||
|
├── character.prisma # Character + CharacterKnowledge models
|
||||||
|
├── conversation.prisma # Conversation + ConversationParticipant
|
||||||
|
├── message.prisma # Message model + MessageRole enum
|
||||||
|
├── vectorMemory.prisma # VectorMemory + MemoryType enum
|
||||||
|
├── importDocument.prisma # ImportDocument model
|
||||||
|
└── storyBranch.prisma # StoryBranch model
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Schema (schema.prisma)
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
// schema.prisma
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import all models from the models folder
|
||||||
|
import { User } from "./models/user"
|
||||||
|
import { Character, CharacterKnowledge } from "./models/character"
|
||||||
|
import { Conversation, ConversationParticipant } from "./models/conversation"
|
||||||
|
import { Message } from "./models/message"
|
||||||
|
import { VectorMemory } from "./models/vectorMemory"
|
||||||
|
import { ImportDocument } from "./models/importDocument"
|
||||||
|
import { StoryBranch } from "./models/storyBranch"
|
||||||
|
```
|
||||||
|
|
||||||
### Full Schema Definition
|
### Full Schema Definition
|
||||||
|
|
||||||
```prisma
|
```prisma
|
||||||
// schema.prisma
|
// models/user.prisma
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
@@ -344,6 +449,7 @@ enum MessageRole {
|
|||||||
enum ImportSourceType {
|
enum ImportSourceType {
|
||||||
file
|
file
|
||||||
url
|
url
|
||||||
|
manual
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ImportStatus {
|
enum ImportStatus {
|
||||||
@@ -353,6 +459,11 @@ enum ImportStatus {
|
|||||||
failed
|
failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MemoryType {
|
||||||
|
conversation
|
||||||
|
character
|
||||||
|
}
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
@@ -378,7 +489,6 @@ model Character {
|
|||||||
name String
|
name String
|
||||||
avatarUrl String?
|
avatarUrl String?
|
||||||
personalityPrompt String
|
personalityPrompt String
|
||||||
backstory String?
|
|
||||||
attributes Json @default("{}")
|
attributes Json @default("{}")
|
||||||
config Json @default("{}")
|
config Json @default("{}")
|
||||||
isPublic Boolean @default(false)
|
isPublic Boolean @default(false)
|
||||||
@@ -388,11 +498,34 @@ model Character {
|
|||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
conversations Conversation[]
|
conversations Conversation[]
|
||||||
|
knowledgeSources CharacterKnowledge[]
|
||||||
|
vectorMemories VectorMemory[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([name])
|
@@index([name])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CharacterKnowledge {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
sourceType ImportSourceType
|
||||||
|
sourceName String
|
||||||
|
mimeType String?
|
||||||
|
fileSize BigInt?
|
||||||
|
rawContent String?
|
||||||
|
status ImportStatus @default(pending)
|
||||||
|
processingInfo Json?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
characterId String
|
||||||
|
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||||||
|
vectorMemories VectorMemory[]
|
||||||
|
|
||||||
|
@@index([characterId])
|
||||||
|
@@index([status])
|
||||||
|
}
|
||||||
|
|
||||||
model Conversation {
|
model Conversation {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
title String?
|
title String?
|
||||||
@@ -436,14 +569,24 @@ model Message {
|
|||||||
model VectorMemory {
|
model VectorMemory {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
content String
|
content String
|
||||||
embedding Unsupported("vector")? // pgvector extension
|
embedding Unsupported("vector")?
|
||||||
|
memoryType MemoryType @default(conversation)
|
||||||
metadata Json?
|
metadata Json?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
conversationId String
|
conversationId String?
|
||||||
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
conversation Conversation? @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
characterId String?
|
||||||
|
character Character? @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
knowledgeId String?
|
||||||
|
knowledge CharacterKnowledge? @relation(fields: [knowledgeId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([conversationId])
|
@@index([conversationId])
|
||||||
|
@@index([characterId])
|
||||||
|
@@index([knowledgeId])
|
||||||
|
@@index([memoryType])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ImportDocument {
|
model ImportDocument {
|
||||||
@@ -546,11 +689,16 @@ export class CharacterRepository {
|
|||||||
```typescript
|
```typescript
|
||||||
// Similarity search using pgvector with Prisma
|
// Similarity search using pgvector with Prisma
|
||||||
async similaritySearch(
|
async similaritySearch(
|
||||||
conversationId: string,
|
targetId: string,
|
||||||
queryEmbedding: number[],
|
queryEmbedding: number[],
|
||||||
|
memoryType: MemoryType,
|
||||||
k: number = 5
|
k: number = 5
|
||||||
) {
|
) {
|
||||||
// Using raw query for pgvector-specific operations
|
// Build the where clause based on memory type
|
||||||
|
const whereClause = memoryType === 'conversation'
|
||||||
|
? { conversationId: targetId, memoryType }
|
||||||
|
: { characterId: targetId, memoryType };
|
||||||
|
|
||||||
const results = await this.prisma.$queryRaw`
|
const results = await this.prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
@@ -558,7 +706,7 @@ async similaritySearch(
|
|||||||
metadata,
|
metadata,
|
||||||
embedding <=> ${queryEmbedding}::vector as distance
|
embedding <=> ${queryEmbedding}::vector as distance
|
||||||
FROM "VectorMemory"
|
FROM "VectorMemory"
|
||||||
WHERE "conversationId" = ${conversationId}
|
WHERE ${whereClause}
|
||||||
ORDER BY embedding <=> ${queryEmbedding}::vector
|
ORDER BY embedding <=> ${queryEmbedding}::vector
|
||||||
LIMIT ${k}
|
LIMIT ${k}
|
||||||
`;
|
`;
|
||||||
@@ -566,20 +714,23 @@ async similaritySearch(
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternative: using cosine similarity
|
// Search character knowledge
|
||||||
async similaritySearchCosine(
|
async searchCharacterKnowledge(
|
||||||
conversationId: string,
|
characterId: string,
|
||||||
queryEmbedding: number[],
|
queryEmbedding: number[],
|
||||||
k: number = 5
|
k: number = 5
|
||||||
) {
|
) {
|
||||||
const results = await this.prisma.$queryRaw`
|
const results = await this.prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
vm.id,
|
||||||
content,
|
vm.content,
|
||||||
metadata,
|
vm.metadata,
|
||||||
1 - (embedding <=> ${queryEmbedding}::vector) as similarity
|
ck.name as source_name,
|
||||||
FROM "VectorMemory"
|
1 - (vm.embedding <=> ${queryEmbedding}::vector) as similarity
|
||||||
WHERE "conversationId" = ${conversationId}
|
FROM "VectorMemory" vm
|
||||||
|
JOIN "CharacterKnowledge" ck ON vm."knowledgeId" = ck.id
|
||||||
|
WHERE vm."characterId" = ${characterId}
|
||||||
|
AND vm."memoryType" = 'character'
|
||||||
ORDER BY similarity DESC
|
ORDER BY similarity DESC
|
||||||
LIMIT ${k}
|
LIMIT ${k}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -112,24 +112,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- dreamchat-network
|
- dreamchat-network
|
||||||
|
|
||||||
keycloak:
|
|
||||||
image: quay.io/keycloak/keycloak:23.0
|
|
||||||
restart: unless-stopped
|
|
||||||
command: start-dev
|
|
||||||
environment:
|
|
||||||
KEYCLOAK_ADMIN: admin
|
|
||||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
|
||||||
KC_DB: postgres
|
|
||||||
KC_DB_URL: jdbc:postgresql://db:5432/keycloak
|
|
||||||
KC_DB_USERNAME: postgres
|
|
||||||
KC_DB_PASSWORD: postgres
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
networks:
|
|
||||||
- dreamchat-network
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
|
|
||||||
@@ -138,6 +120,8 @@ networks:
|
|||||||
driver: bridge
|
driver: bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** Keycloak is configured as an external service. Set `KEYCLOAK_URL` in your environment to point to your external Keycloak instance.
|
||||||
|
|
||||||
### .devcontainer/Dockerfile
|
### .devcontainer/Dockerfile
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
@@ -274,15 +258,15 @@ services:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
# Frontend
|
# Frontend (static file server)
|
||||||
|
# Note: External reverse proxy expected for SSL and routing
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: apps/frontend/Dockerfile
|
dockerfile: apps/frontend/Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "3001:3000"
|
||||||
- "443:443"
|
|
||||||
environment:
|
environment:
|
||||||
- VITE_API_URL=/api
|
- VITE_API_URL=/api
|
||||||
- VITE_WS_URL=/ws
|
- VITE_WS_URL=/ws
|
||||||
@@ -319,23 +303,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- dreamchat-network
|
- dreamchat-network
|
||||||
|
|
||||||
# Nginx Reverse Proxy (optional)
|
|
||||||
nginx:
|
|
||||||
image: nginx:alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
||||||
- model-cache:/model-cache:ro
|
|
||||||
depends_on:
|
|
||||||
- backend
|
|
||||||
- frontend
|
|
||||||
networks:
|
|
||||||
- dreamchat-network
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
redis-data:
|
redis-data:
|
||||||
@@ -400,7 +367,7 @@ KEYCLOAK_CLIENT_SECRET=your_keycloak_secret
|
|||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# apps/backend/Dockerfile
|
# apps/backend/Dockerfile
|
||||||
FROM node:20-alpine AS base
|
FROM node:24-alpine AS base
|
||||||
RUN npm install -g pnpm@8
|
RUN npm install -g pnpm@8
|
||||||
|
|
||||||
FROM base AS dependencies
|
FROM base AS dependencies
|
||||||
@@ -456,7 +423,7 @@ CMD ["node", "dist/main.js"]
|
|||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# apps/frontend/Dockerfile
|
# apps/frontend/Dockerfile
|
||||||
FROM node:20-alpine AS base
|
FROM node:24-alpine AS base
|
||||||
RUN npm install -g pnpm@8
|
RUN npm install -g pnpm@8
|
||||||
|
|
||||||
FROM base AS dependencies
|
FROM base AS dependencies
|
||||||
@@ -486,46 +453,59 @@ RUN pnpm --filter @dreamchat/shared build
|
|||||||
# Build frontend
|
# Build frontend
|
||||||
RUN pnpm --filter @dreamchat/frontend build
|
RUN pnpm --filter @dreamchat/frontend build
|
||||||
|
|
||||||
# Production with Nginx
|
# Production stage - using serve for static files
|
||||||
FROM nginx:alpine
|
# External reverse proxy (nginx/traefik/etc.) expected
|
||||||
|
FROM node:24-alpine AS production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install serve
|
||||||
|
RUN npm install -g serve
|
||||||
|
|
||||||
# Copy built assets
|
# Copy built assets
|
||||||
COPY --from=build /app/apps/frontend/dist /usr/share/nginx/html
|
COPY --from=build /app/apps/frontend/dist ./dist
|
||||||
|
|
||||||
# Copy nginx config
|
# Create non-root user
|
||||||
COPY apps/frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
RUN addgroup -g 1001 -S nodejs
|
||||||
|
RUN adduser -S nodejs -u 1001
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
# Serve static files
|
||||||
|
# Note: External reverse proxy should handle:
|
||||||
|
# - SSL/TLS termination
|
||||||
|
# - Path routing (/api -> backend, / -> frontend)
|
||||||
|
# - WebSocket proxying
|
||||||
|
CMD ["serve", "-s", "dist", "-l", "3000"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### frontend/nginx.conf
|
### External Reverse Proxy Configuration
|
||||||
|
|
||||||
|
The frontend container serves static files on port 3000. An external reverse proxy is expected to handle:
|
||||||
|
|
||||||
|
- **SSL/TLS termination**
|
||||||
|
- **Path routing**:
|
||||||
|
- `/api/*` → Backend (port 3000)
|
||||||
|
- `/ws` → Backend WebSocket (port 3000)
|
||||||
|
- `/*` → Frontend (port 3001)
|
||||||
|
- **Static file caching**
|
||||||
|
|
||||||
|
Example nginx configuration:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 443 ssl;
|
||||||
server_name localhost;
|
server_name dreamchat.example.com;
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Gzip compression
|
ssl_certificate /path/to/cert.pem;
|
||||||
gzip on;
|
ssl_certificate_key /path/to/key.pem;
|
||||||
gzip_vary on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
|
||||||
|
|
||||||
# API proxy
|
# API proxy
|
||||||
location /api {
|
location /api {
|
||||||
proxy_pass http://backend:3000/api;
|
proxy_pass http://backend:3000/api;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# WebSocket proxy
|
# WebSocket proxy
|
||||||
@@ -534,45 +514,110 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Static files
|
# Frontend static files
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
proxy_pass http://frontend:3000;
|
||||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
proxy_http_version 1.1;
|
||||||
}
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
# Health check
|
|
||||||
location /health {
|
|
||||||
access_log off;
|
|
||||||
return 200 "healthy\n";
|
|
||||||
add_header Content-Type text/plain;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Keycloak Configuration
|
## Keycloak Configuration (External)
|
||||||
|
|
||||||
### Initial Setup
|
Keycloak is configured as an external service with support for group/role/attribute-based authorization and auto-user creation.
|
||||||
|
|
||||||
1. Access Keycloak admin console: `http://localhost:8080/admin`
|
### Prerequisites
|
||||||
2. Login with admin credentials
|
|
||||||
3. Create new realm: `dreamchat`
|
1. Have a running Keycloak instance (self-hosted or managed)
|
||||||
4. Create client: `dreamchat-backend`
|
2. Configure the following environment variables in `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic Keycloak settings
|
||||||
|
KEYCLOAK_ENABLED=true
|
||||||
|
KEYCLOAK_URL=http://your-keycloak-server:8080
|
||||||
|
KEYCLOAK_REALM=dreamchat
|
||||||
|
KEYCLOAK_CLIENT_ID=dreamchat-backend
|
||||||
|
KEYCLOAK_CLIENT_SECRET=your_keycloak_secret
|
||||||
|
|
||||||
|
# Authorization settings (optional but recommended)
|
||||||
|
KEYCLOAK_REQUIRED_GROUP=dreamchat-users
|
||||||
|
KEYCLOAK_REQUIRED_ROLE=dreamchat-access
|
||||||
|
KEYCLOAK_REQUIRED_CLIENT_ROLE=user
|
||||||
|
KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
|
||||||
|
|
||||||
|
# Auto-create users
|
||||||
|
KEYCLOAK_AUTO_CREATE_USER=true
|
||||||
|
KEYCLOAK_DEFAULT_USER_ROLE=USER
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keycloak Realm Setup
|
||||||
|
|
||||||
|
1. Access your Keycloak admin console
|
||||||
|
2. Create new realm: `dreamchat`
|
||||||
|
3. Create client: `dreamchat-backend`
|
||||||
- Client authentication: ON
|
- Client authentication: ON
|
||||||
- Authorization: ON
|
- Authorization: ON
|
||||||
- Valid redirect URIs: `http://localhost:3000/*`
|
- Valid redirect URIs: `http://localhost:3000/*`
|
||||||
- Web origins: `http://localhost:3000`
|
- Web origins: `http://localhost:3000`
|
||||||
|
|
||||||
5. Create client: `dreamchat-frontend`
|
4. Create client: `dreamchat-frontend`
|
||||||
- Client authentication: OFF
|
- Client authentication: OFF
|
||||||
- Valid redirect URIs: `http://localhost:5173/*`
|
- Valid redirect URIs: `http://localhost:5173/*`
|
||||||
- Web origins: `http://localhost:5173`
|
- Web origins: `http://localhost:5173`
|
||||||
|
|
||||||
|
### Authorization Configuration
|
||||||
|
|
||||||
|
You can restrict access based on:
|
||||||
|
|
||||||
|
**1. Group Membership**
|
||||||
|
```bash
|
||||||
|
KEYCLOAK_REQUIRED_GROUP=dreamchat-users
|
||||||
|
```
|
||||||
|
Users must be members of this Keycloak group to access the application.
|
||||||
|
|
||||||
|
**2. Realm Role**
|
||||||
|
```bash
|
||||||
|
KEYCLOAK_REQUIRED_ROLE=dreamchat-access
|
||||||
|
```
|
||||||
|
Users must have this realm-level role.
|
||||||
|
|
||||||
|
**3. Client Role**
|
||||||
|
```bash
|
||||||
|
KEYCLOAK_REQUIRED_CLIENT_ROLE=user
|
||||||
|
```
|
||||||
|
Users must have this role for the `dreamchat-backend` client.
|
||||||
|
|
||||||
|
**4. User Attribute**
|
||||||
|
```bash
|
||||||
|
KEYCLOAK_REQUIRED_ATTRIBUTE=department:engineering
|
||||||
|
# or
|
||||||
|
KEYCLOAK_REQUIRED_ATTRIBUTE=approved:true
|
||||||
|
```
|
||||||
|
Users must have this attribute with the specified value.
|
||||||
|
|
||||||
|
### User Auto-Creation
|
||||||
|
|
||||||
|
When `KEYCLOAK_AUTO_CREATE_USER=true`:
|
||||||
|
- Users are automatically created in the database on first Keycloak login
|
||||||
|
- Username is derived from Keycloak preferred_username
|
||||||
|
- Email is taken from Keycloak email claim
|
||||||
|
- Role is set to `KEYCLOAK_DEFAULT_USER_ROLE` (default: USER)
|
||||||
|
- The `keycloakSub` field links the local user to Keycloak
|
||||||
|
|
||||||
|
When `KEYCLOAK_AUTO_CREATE_USER=false`:
|
||||||
|
- Only existing local users can log in via Keycloak
|
||||||
|
- The `keycloakSub` must match between Keycloak and local user
|
||||||
|
|
||||||
|
### Example Keycloak Group/Role Setup
|
||||||
|
|
||||||
|
1. Create a group: `dreamchat-users`
|
||||||
|
2. Create a realm role: `dreamchat-access`
|
||||||
|
3. Assign the group and/or role to users who should have access
|
||||||
|
4. Configure `KEYCLOAK_REQUIRED_GROUP` and/or `KEYCLOAK_REQUIRED_ROLE`
|
||||||
|
|
||||||
### realm-export.json (Optional)
|
### realm-export.json (Optional)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ This document outlines the phased implementation approach for DreamChat.
|
|||||||
- [ ] Initialize NestJS app in `apps/backend`
|
- [ ] Initialize NestJS app in `apps/backend`
|
||||||
- [ ] Configure Prisma ORM with PostgreSQL
|
- [ ] Configure Prisma ORM with PostgreSQL
|
||||||
- [ ] Install and configure pgvector extension
|
- [ ] Install and configure pgvector extension
|
||||||
- [ ] Define Prisma schema for all entities
|
- [ ] Define Prisma schema using multi-file structure (`prisma/models/`)
|
||||||
- [ ] Configure Jest for unit testing
|
- [ ] Configure Jest for unit testing
|
||||||
- [ ] Set up Swagger/OpenAPI
|
- [ ] Set up Swagger/OpenAPI
|
||||||
- [ ] Implement basic logging (Winston/Pino)
|
- [ ] Implement basic logging (Winston/Pino)
|
||||||
@@ -204,10 +204,15 @@ HUGGINGFACE_API_KEY=hf_...
|
|||||||
|
|
||||||
#### Backend Tasks
|
#### Backend Tasks
|
||||||
- [ ] Import adapter interface
|
- [ ] Import adapter interface
|
||||||
- [ ] Text file adapter
|
- [ ] Text file adapter (txt, md)
|
||||||
- [ ] File upload endpoint
|
- [ ] File upload endpoint
|
||||||
- [ ] Data preprocessor (cleaning)
|
- [ ] Data preprocessor (cleaning)
|
||||||
- [ ] Basic text chunking
|
- [ ] Basic text chunking
|
||||||
|
- [ ] CharacterKnowledge Prisma model
|
||||||
|
- [ ] CharacterKnowledgeService
|
||||||
|
- [ ] Import file/URL as knowledge
|
||||||
|
- [ ] Chunk and embed content
|
||||||
|
- [ ] Store in VectorMemory with type='character'
|
||||||
|
|
||||||
#### Frontend Tasks
|
#### Frontend Tasks
|
||||||
- [ ] Import page
|
- [ ] Import page
|
||||||
@@ -274,7 +279,8 @@ HUGGINGFACE_API_KEY=hf_...
|
|||||||
- [ ] Predefined scraper: AO3
|
- [ ] Predefined scraper: AO3
|
||||||
- [ ] Predefined scraper: FanFiction.net
|
- [ ] Predefined scraper: FanFiction.net
|
||||||
- [ ] URL validation and scraper selection
|
- [ ] URL validation and scraper selection
|
||||||
- [ ] URL import endpoint
|
- [ ] URL import endpoint (stores as CharacterKnowledge)
|
||||||
|
- [ ] PDF and Markdown support for character knowledge
|
||||||
|
|
||||||
#### Frontend Tasks
|
#### Frontend Tasks
|
||||||
- [ ] URL import component
|
- [ ] URL import component
|
||||||
|
|||||||
@@ -616,7 +616,7 @@ services:
|
|||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# apps/backend/Dockerfile
|
# apps/backend/Dockerfile
|
||||||
FROM node:20-alpine AS base
|
FROM node:24-alpine AS base
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
FROM base AS dependencies
|
FROM base AS dependencies
|
||||||
|
|||||||
107
docker-compose.yml
Normal file
107
docker-compose.yml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Backend API
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/backend/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/dreamchat
|
||||||
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
|
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-1h}
|
||||||
|
- JWT_REFRESH_EXPIRES_IN=${JWT_REFRESH_EXPIRES_IN:-7d}
|
||||||
|
- LLM_PROVIDER=${LLM_PROVIDER}
|
||||||
|
- LLM_API_KEY=${LLM_API_KEY}
|
||||||
|
- LLM_MODEL=${LLM_MODEL}
|
||||||
|
- EMBEDDING_PROVIDER=${EMBEDDING_PROVIDER:-local}
|
||||||
|
- EMBEDDING_MODEL=${EMBEDDING_MODEL:-Xenova/all-MiniLM-L6-v2}
|
||||||
|
- EMBEDDING_DIMENSION=${EMBEDDING_DIMENSION:-384}
|
||||||
|
- EMBEDDING_DEVICE=${EMBEDDING_DEVICE:-cpu}
|
||||||
|
- HUGGINGFACE_API_KEY=${HUGGINGFACE_API_KEY}
|
||||||
|
# Keycloak Configuration
|
||||||
|
- KEYCLOAK_ENABLED=${KEYCLOAK_ENABLED:-false}
|
||||||
|
- KEYCLOAK_URL=${KEYCLOAK_URL:-}
|
||||||
|
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-}
|
||||||
|
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-}
|
||||||
|
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-}
|
||||||
|
# Keycloak Authorization
|
||||||
|
- KEYCLOAK_REQUIRED_GROUP=${KEYCLOAK_REQUIRED_GROUP:-}
|
||||||
|
- KEYCLOAK_REQUIRED_ROLE=${KEYCLOAK_REQUIRED_ROLE:-}
|
||||||
|
- KEYCLOAK_REQUIRED_CLIENT_ROLE=${KEYCLOAK_REQUIRED_CLIENT_ROLE:-}
|
||||||
|
- KEYCLOAK_REQUIRED_ATTRIBUTE=${KEYCLOAK_REQUIRED_ATTRIBUTE:-}
|
||||||
|
# Keycloak Auto-Create
|
||||||
|
- KEYCLOAK_AUTO_CREATE_USER=${KEYCLOAK_AUTO_CREATE_USER:-true}
|
||||||
|
- KEYCLOAK_DEFAULT_USER_ROLE=${KEYCLOAK_DEFAULT_USER_ROLE:-USER}
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- backend-logs:/app/logs
|
||||||
|
- model-cache:/app/models
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# Frontend (static files served via 'serve')
|
||||||
|
# Note: External reverse proxy expected for SSL and path routing
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/frontend/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
|
environment:
|
||||||
|
- VITE_API_URL=/api
|
||||||
|
- VITE_WS_URL=/ws
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
|
||||||
|
# Database
|
||||||
|
db:
|
||||||
|
image: ankane/pgvector:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: dreamchat
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# Redis (optional, for session storage and caching)
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- dreamchat-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
|
backend-logs:
|
||||||
|
model-cache:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dreamchat-network:
|
||||||
|
driver: bridge
|
||||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "dreamchat",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@8.15.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm -r build",
|
||||||
|
"dev": "pnpm -r --parallel dev",
|
||||||
|
"test": "pnpm -r test",
|
||||||
|
"lint": "pnpm -r lint",
|
||||||
|
"clean": "pnpm -r clean && rm -rf node_modules"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/shared/package.json
Normal file
29
packages/shared/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@dreamchat/shared",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"./websocket": {
|
||||||
|
"import": "./dist/websocket/index.js",
|
||||||
|
"types": "./dist/websocket/index.d.ts"
|
||||||
|
},
|
||||||
|
"./api": {
|
||||||
|
"import": "./dist/api/index.js",
|
||||||
|
"types": "./dist/api/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/shared/tsconfig.json
Normal file
16
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
7615
pnpm-lock.yaml
generated
Normal file
7615
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
packages:
|
||||||
|
- 'apps/*'
|
||||||
|
- 'packages/*'
|
||||||
Reference in New Issue
Block a user