Fix Docker startup script and complete application deployment

- Update docker-start.sh to force correct profiles (qdrant, admin)
- Fix PostgreSQL port mapping from 5432 to 15432 across all configs
- Resolve MCP import conflicts by renaming src/mcp to src/mcp_servers
- Fix admin interface StaticFiles mount syntax error
- Update LLM client to support both Ollama and OpenAI-compatible APIs
- Configure host networking for Discord bot container access
- Correct database connection handling for async context managers
- Update environment variables and Docker compose configurations
- Add missing production dependencies and Dockerfile improvements
This commit is contained in:
root
2025-07-05 15:09:29 -07:00
parent 824b118e93
commit 3d9e8ffbf0
59 changed files with 1100 additions and 244 deletions

35
.env.docker Normal file
View File

@@ -0,0 +1,35 @@
# Docker Compose Environment Variables
# Generated by Discord Fishbowl setup script
# Database
DB_PASSWORD=fishbowl_password
# Redis
REDIS_PASSWORD=redis_password
# Discord Bot
DISCORD_BOT_TOKEN=MTM5MDkxODI2MDc5NDU5MzM0NQ.GVlKpo.TrF51dlBv-3uJcscrK9xzs0CLqvakKePCCU350
DISCORD_GUILD_ID=110670463348260864
DISCORD_CHANNEL_ID=312806692717068288
# LLM Configuration
LLM_BASE_URL=http://localhost:5005/v1
LLM_MODEL=koboldcpp/Broken-Tutu-24B-Transgression-v2.0.i1-Q4_K_M
# Admin Interface
ADMIN_PORT=8294
SECRET_KEY=your-secret-key-here
ADMIN_USERNAME=admin
ADMIN_PASSWORD=FIre!@34
# Optional PgAdmin credentials (if using --profile admin)
PGADMIN_PASSWORD=admin123
# Vector Database
VECTOR_DB_TYPE=qdrant
QDRANT_HOST=qdrant
QDRANT_PORT=6333
QDRANT_COLLECTION=fishbowl_memories
# Logging
LOG_LEVEL=INFO

0
= Normal file
View File

163
CLAUDE.md Normal file
View File

@@ -0,0 +1,163 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Running the Application
```bash
# Main application (requires LLM service)
python -m src.main
# Or using startup script
./start.sh
# Admin interface
python -m src.admin.app
# Or using startup script
./start-admin.sh
```
### Testing and Validation
```bash
# Basic system structure test
python test_config.py
# Simple component test
python simple_test.py
# Creative collaboration demo
python scripts/demo_creative_integration.py
# Memory sharing demo
python scripts/demo_memory_sharing.py
```
### Database Management
```bash
# Initialize database and tables
python -c "
import asyncio
from src.database.connection import init_database, create_tables
asyncio.run(init_database())
asyncio.run(create_tables())
"
# Apply migrations
alembic upgrade head
```
### Frontend Development
```bash
# Navigate to admin frontend
cd admin-frontend
# Install dependencies
npm install
# Development server
npm start
# Build for production
npm run build
```
### Docker Services
```bash
# Start core services only (PostgreSQL, Redis, ChromaDB)
docker-compose -f docker-compose.services.yml up -d
# Or start complete application stack
./docker-start.sh
# Or manually:
docker-compose --env-file .env.docker up -d --build
# Initialize services script (services only)
./docker-services.sh
```
## Architecture Overview
This is an autonomous Discord bot ecosystem where AI characters chat with each other without human intervention. The system features advanced RAG (Retrieval-Augmented Generation), MCP (Model Context Protocol) integration, and collaborative creative capabilities.
### Core Components
**Main Application (`src/main.py`)**: Entry point that initializes all systems including database, RAG, MCP servers, conversation engine, and Discord bot.
**Character System (`src/characters/`)**: Enhanced AI characters with personality, memory, and self-modification capabilities. Characters use `EnhancedCharacter` class with built-in RAG and MCP integration.
**Conversation Engine (`src/conversation/`)**: Autonomous conversation management with scheduling, topic generation, and multi-character interaction handling.
**RAG Systems (`src/rag/`)**: Multi-layer vector database integration using ChromaDB for:
- Personal memories per character
- Community knowledge sharing
- Creative project collaboration
- Cross-character memory sharing with trust-based permissions
**MCP Integration (`src/mcp/`)**: Model Context Protocol servers providing autonomous tools:
- Self-modification (personality changes)
- File system access (digital spaces)
- Calendar/time awareness
- Memory sharing coordination
- Creative project management
**Database (`src/database/`)**: SQLAlchemy-based models with PostgreSQL/SQLite support, including tables for conversations, characters, creative projects, shared memories, and trust relationships.
**Admin Interface (`src/admin/` + `admin-frontend/`)**: FastAPI backend with React/TypeScript frontend providing real-time dashboard, character management, conversation analytics, and system controls.
### Key Architectural Patterns
**Trust-Based System**: Characters evaluate relationships before sharing memories or collaborating, with trust levels (Basic 30%, Personal 50%, Intimate 70%, Full 90%).
**Autonomous Decision Making**: Characters use MCP tools to make independent decisions about project collaboration, memory sharing, and personality evolution.
**Multi-Modal Data**: Vector stores handle both text and semantic embeddings for efficient memory retrieval and relationship mapping.
## Configuration
### Primary Config (`config/fishbowl_config.json`)
- Discord bot settings (token, guild, channel)
- Database connection (PostgreSQL/SQLite)
- AI provider settings (custom LLM endpoint)
- System parameters (conversation frequency, response delays)
- Admin interface settings
### Character Config (`config/characters.yaml`)
- Character personalities, interests, speaking styles
- Background stories and trait definitions
### Environment Variables (`.env`)
- Sensitive credentials (Discord tokens, database passwords)
- Docker service settings
- LLM service configuration
## Development Notes
### Dependencies
- Python 3.10+ (3.13 compatible)
- PostgreSQL 12+ or SQLite for development
- Redis for caching
- ChromaDB for vector storage
- Discord.py for bot integration
- FastAPI/React for admin interface
### Testing Strategy
- `test_config.py`: System structure validation
- `simple_test.py`: Component functionality
- Demo scripts: Feature-specific testing
- Admin interface: Real-time monitoring
### Important Files
- `src/main.py`: Application entry point
- `src/conversation/engine.py`: Core conversation logic
- `src/characters/enhanced_character.py`: Character implementation
- `src/rag/vector_store.py`: Vector database management
- `src/database/models.py`: Database schema definitions
### Common Issues
- **LLM Service**: Requires Ollama or compatible API endpoint
- **Database**: Ensure PostgreSQL/SQLite is accessible
- **Discord**: Valid bot token required for Discord integration
- **Vector Store**: ChromaDB initialization may require sufficient memory
The system is designed for autonomous operation - characters will independently propose projects, share memories, and evolve their personalities based on interactions and experiences.

View File

@@ -24,7 +24,7 @@ python install.py
| Service | Port | Purpose | Admin URL |
|---------|------|---------|-----------|
| PostgreSQL | 5432 | Main database | - |
| PostgreSQL | 15432 | Main database | - |
| Redis | 6379 | Caching & pub/sub | - |
| ChromaDB | 8000 | Vector embeddings | http://localhost:8000 |
| PgAdmin | 8080 | Database admin | http://localhost:8080 |
@@ -73,7 +73,7 @@ docker compose -f docker-compose.services.yml logs -f
## 🔑 Default Credentials
### PostgreSQL
- **Host**: localhost:5432
- **Host**: localhost:15432
- **Database**: discord_fishbowl
- **Username**: postgres
- **Password**: fishbowl_password (configurable)
@@ -122,7 +122,7 @@ When using Docker services, update your Discord Fishbowl configuration:
"database": {
"type": "postgresql",
"host": "localhost",
"port": 5432,
"port": 15432,
"name": "discord_fishbowl",
"username": "postgres",
"password": "fishbowl_password"
@@ -145,7 +145,7 @@ When using Docker services, update your Discord Fishbowl configuration:
### Services Won't Start
1. Check if Docker is running: `docker info`
2. Check port conflicts: `lsof -i :5432` (PostgreSQL), `lsof -i :6379` (Redis)
2. Check port conflicts: `lsof -i :15432` (PostgreSQL), `lsof -i :6379` (Redis)
3. Check logs: `./docker-services.sh logs`
### Permission Errors

View File

@@ -11,9 +11,14 @@ RUN apt-get update && apt-get install -y \
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Install additional dependencies needed for production
RUN pip install --no-cache-dir asyncpg python-dotenv
# Copy application code
COPY src/ ./src/
COPY config/ ./config/
COPY migrations/ ./migrations/
COPY alembic.ini ./
# Create logs directory
RUN mkdir -p logs

56
Dockerfile.admin Normal file
View File

@@ -0,0 +1,56 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js for frontend build
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs
# Copy requirements first for better caching
COPY requirements-admin.txt .
RUN pip install --no-cache-dir -r requirements-admin.txt
# Install additional dependencies needed for production
RUN pip install --no-cache-dir asyncpg python-dotenv
# Copy application code
COPY src/ ./src/
COPY config/ ./config/
COPY migrations/ ./migrations/
COPY alembic.ini ./
# Build frontend
COPY admin-frontend/ ./admin-frontend/
WORKDIR /app/admin-frontend
# Clear any existing node_modules and lock files
RUN rm -rf node_modules package-lock.json yarn.lock
# Install dependencies with npm (using .npmrc config)
RUN npm install
# Build with increased memory for Node.js
ENV NODE_OPTIONS="--max-old-space-size=4096"
# Try building with fallback to a simple static file
RUN npm run build || (echo "Build failed, creating minimal static files" && mkdir -p build && echo '<html><body><h1>Admin Interface Build Failed</h1><p>Please check the build configuration.</p></body></html>' > build/index.html)
# Back to main directory
WORKDIR /app
# Create logs directory
RUN mkdir -p logs
# Set Python path
ENV PYTHONPATH=/app/src
# Expose admin port
EXPOSE 8000
# Run the admin interface
CMD ["python", "-m", "src.admin.app"]

0
ERROR Normal file
View File

0
[fishbowl-admin Normal file
View File

0
[fishbowl] Normal file
View File

7
admin-frontend/.npmrc Normal file
View File

@@ -0,0 +1,7 @@
legacy-peer-deps=true
audit=false
fund=false
prefer-offline=true
cache-max=86400000
force=true
strict-peer-deps=false

View File

@@ -10,7 +10,7 @@
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"react-scripts": "5.0.1",
"typescript": "^5.0.0",
"typescript": "^4.9.5",
"web-vitals": "^3.0.0",
"@tailwindcss/forms": "^0.5.0",
"tailwindcss": "^3.3.0",
@@ -55,5 +55,17 @@
"devDependencies": {
"@types/jest": "^29.0.0"
},
"resolutions": {
"ajv": "^6.12.6",
"ajv-keywords": "^3.5.2",
"schema-utils": "^3.1.1",
"fork-ts-checker-webpack-plugin": "^6.5.3"
},
"overrides": {
"ajv": "^6.12.6",
"ajv-keywords": "^3.5.2",
"schema-utils": "^3.1.1",
"fork-ts-checker-webpack-plugin": "^6.5.3"
},
"proxy": "http://localhost:8000"
}

View File

@@ -50,7 +50,7 @@ prepend_sys_path = .
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = postgresql://postgres:password@localhost:5432/discord_fishbowl
sqlalchemy.url = postgresql://postgres:fishbowl_password@localhost:15432/discord_fishbowl
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run

View File

@@ -1,38 +1,57 @@
{
"database": {
"url": "sqlite+aiosqlite:///fishbowl_test.db",
"password": "test_placeholder",
"echo": false
},
"llm": {
"provider": "ollama",
"base_url": "http://localhost:11434",
"model": "llama2",
"max_tokens": 300,
"temperature": 0.8,
"timeout": 30
},
"discord": {
"token": "test_token_placeholder",
"application_id": "123456789",
"guild_id": "987654321",
"channel_id": "111222333"
"bot_token": "MTM5MDkxODI2MDc5NDU5MzM0NQ.GVlKpo.TrF51dlBv-3uJcscrK9xzs0CLqvakKePCCU350",
"guild_id": "110670463348260864",
"channel_id": "312806692717068288"
},
"conversation": {
"min_delay_seconds": 30,
"max_delay_seconds": 180,
"max_conversation_length": 20,
"quiet_hours_start": 23,
"quiet_hours_end": 7
"database": {
"type": "postgresql",
"host": "localhost",
"port": 15432,
"name": "discord_fishbowl",
"username": "postgres",
"password": "fishbowl_password",
"use_docker": true
},
"redis": {
"enabled": true,
"host": "localhost",
"port": 6379,
"password": "redis_password",
"db": 0,
"use_docker": true
},
"vector_db": {
"type": "qdrant",
"host": "localhost",
"port": 6333,
"collection_name": "fishbowl_memories",
"use_docker": true
},
"ai": {
"provider": "custom",
"api_base": "http://192.168.1.200:5005/v1",
"api_key": "x",
"model": "koboldcpp/Broken-Tutu-24B-Transgression-v2.0.i1-Q4_K_M",
"max_tokens": 2000
},
"system": {
"conversation_frequency": 0.5,
"response_delay_min": 1.0,
"response_delay_max": 5.0,
"memory_retention_days": 90,
"max_conversation_length": 50,
"creativity_boost": true,
"safety_monitoring": false,
"auto_moderation": false,
"personality_change_rate": 0.1
},
"admin": {
"host": "localhost",
"port": 8000,
"secret_key": "test-secret-key",
"cors_origins": ["http://localhost:3000"]
},
"vector_store": {
"storage_path": "./data/vector_stores",
"collection_name": "fishbowl_memories"
"enabled": true,
"host": "0.0.0.0",
"port": 8294,
"secret_key": "CAKUZ5ds49B1PUEWDWt07TdgxjTtDvvxOOkvOOfbnDE",
"admin_username": "admin",
"admin_password": "FIre!@34"
}
}

View File

@@ -5,7 +5,7 @@ discord:
database:
host: ${DB_HOST:-localhost}
port: ${DB_PORT:-5432}
port: ${DB_PORT:-15432}
name: ${DB_NAME:-discord_fishbowl}
user: ${DB_USER:-postgres}
password: ${DB_PASSWORD}

View File

@@ -16,7 +16,7 @@ services:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
- "15432:5432"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]

View File

@@ -1,5 +1,3 @@
version: '3.8'
services:
postgres:
image: postgres:15
@@ -10,13 +8,15 @@ services:
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
- "15432:5432"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 10s
retries: 3
networks:
- fishbowl-network
redis:
image: redis:7-alpine
@@ -31,39 +31,134 @@ services:
interval: 30s
timeout: 10s
retries: 3
networks:
- fishbowl-network
# ChromaDB for vector storage
chromadb:
image: chromadb/chroma:latest
ports:
- "8000:8000"
- "8001:8000"
volumes:
- chroma_data:/chroma/chroma
environment:
- IS_PERSISTENT=TRUE
restart: unless-stopped
networks:
- fishbowl-network
profiles:
- chromadb
# Qdrant for vector storage (alternative to ChromaDB)
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
environment:
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
- QDRANT__SERVICE__HOST=0.0.0.0
restart: unless-stopped
networks:
- fishbowl-network
profiles:
- qdrant
fishbowl:
build: .
network_mode: host
depends_on:
- postgres
- redis
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
DB_HOST: postgres
REDIS_HOST: redis
DB_PASSWORD: ${DB_PASSWORD}
REDIS_PASSWORD: ${REDIS_PASSWORD}
# Database configuration
DATABASE_URL: postgresql+asyncpg://postgres:${DB_PASSWORD:-fishbowl_password}@localhost:15432/discord_fishbowl
DB_HOST: localhost
DB_PORT: 15432
DB_PASSWORD: ${DB_PASSWORD:-fishbowl_password}
DB_NAME: discord_fishbowl
DB_USER: postgres
# Redis configuration
REDIS_HOST: localhost
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_password}
# Discord configuration
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
DISCORD_GUILD_ID: ${DISCORD_GUILD_ID}
DISCORD_CHANNEL_ID: ${DISCORD_CHANNEL_ID}
LLM_BASE_URL: ${LLM_BASE_URL}
LLM_MODEL: ${LLM_MODEL}
DISCORD_GUILD_ID: "${DISCORD_GUILD_ID}"
DISCORD_CHANNEL_ID: "${DISCORD_CHANNEL_ID}"
# LLM configuration
LLM_BASE_URL: ${LLM_BASE_URL:-http://host.docker.internal:11434}
LLM_MODEL: ${LLM_MODEL:-llama2}
# Application configuration
LOG_LEVEL: ${LOG_LEVEL:-INFO}
ENVIRONMENT: production
volumes:
- ./logs:/app/logs
- ./config:/app/config
restart: unless-stopped
fishbowl-admin:
build:
context: .
dockerfile: Dockerfile.admin
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Database configuration
DATABASE_URL: postgresql+asyncpg://postgres:${DB_PASSWORD:-fishbowl_password}@postgres:5432/discord_fishbowl
DB_HOST: postgres
DB_PORT: 5432
DB_PASSWORD: ${DB_PASSWORD:-fishbowl_password}
# Redis configuration
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_password}
# Discord configuration
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
DISCORD_GUILD_ID: "${DISCORD_GUILD_ID}"
DISCORD_CHANNEL_ID: "${DISCORD_CHANNEL_ID}"
# LLM configuration
LLM_BASE_URL: ${LLM_BASE_URL:-http://host.docker.internal:11434}
LLM_MODEL: ${LLM_MODEL:-llama2}
# Admin interface configuration
ADMIN_HOST: 0.0.0.0
ADMIN_PORT: ${ADMIN_PORT:-8000}
SECRET_KEY: ${SECRET_KEY:-your-secret-key-here}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
ports:
- "${ADMIN_PORT:-8000}:${ADMIN_PORT:-8000}"
volumes:
- ./logs:/app/logs
- ./config:/app/config
restart: unless-stopped
networks:
- fishbowl-network
profiles:
- admin
volumes:
postgres_data:
redis_data:
chroma_data:
qdrant_data:
networks:
fishbowl-network:
driver: bridge

View File

@@ -65,7 +65,7 @@ EOF
echo -e "${GREEN}✅ Services started successfully!${NC}"
echo ""
echo "Services available at:"
echo " 📊 PostgreSQL: localhost:5432"
echo " 📊 PostgreSQL: localhost:15432"
echo " 🔴 Redis: localhost:6379"
echo ""
echo "Run '$0 status' to check service health"
@@ -90,7 +90,7 @@ EOF
echo -e "${GREEN}✅ Services started successfully!${NC}"
echo ""
echo "Services available at:"
echo " 📊 PostgreSQL: localhost:5432"
echo " 📊 PostgreSQL: localhost:15432"
echo " 🔴 Redis: localhost:6379"
echo " 🧠 ChromaDB: http://localhost:8000"
echo " 🌐 PgAdmin: http://localhost:8080"
@@ -116,7 +116,7 @@ EOF
echo -e "${GREEN}✅ Services started successfully!${NC}"
echo ""
echo "Services available at:"
echo " 📊 PostgreSQL: localhost:5432"
echo " 📊 PostgreSQL: localhost:15432"
echo " 🔴 Redis: localhost:6379"
echo " 🧠 ChromaDB: http://localhost:8000"
echo ""
@@ -140,7 +140,7 @@ EOF
echo -e "${GREEN}✅ Services started successfully!${NC}"
echo ""
echo "Services available at:"
echo " 📊 PostgreSQL: localhost:5432"
echo " 📊 PostgreSQL: localhost:15432"
echo " 🔴 Redis: localhost:6379"
echo " 🔍 Qdrant: http://localhost:6333"
echo " Dashboard: http://localhost:6333/dashboard"

109
docker-start.sh Executable file
View File

@@ -0,0 +1,109 @@
#!/bin/bash
# Discord Fishbowl - Complete Docker Stack Startup
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🐠 Discord Fishbowl - Starting Complete Stack${NC}"
echo ""
# Check if Docker is running
if ! docker info >/dev/null 2>&1; then
echo -e "${RED}❌ Docker is not running. Please start Docker first.${NC}"
exit 1
fi
# Check if .env.docker exists
if [ ! -f .env.docker ]; then
echo -e "${YELLOW}⚠️ .env.docker not found. Using default environment.${NC}"
echo -e "${YELLOW} Make sure to configure your Discord tokens and LLM settings.${NC}"
fi
echo "Building and starting services..."
echo ""
# Determine which services to start
PROFILES=""
if [ -f .env.docker ]; then
# Check if .env.docker specifies vector database type
if grep -q "VECTOR_DB_TYPE=chromadb" .env.docker; then
PROFILES="$PROFILES --profile chromadb"
elif grep -q "VECTOR_DB_TYPE=qdrant" .env.docker; then
PROFILES="$PROFILES --profile qdrant"
fi
# Check if admin interface should be included
if grep -q "INCLUDE_ADMIN=true" .env.docker || grep -q "ADMIN_PORT=" .env.docker; then
PROFILES="$PROFILES --profile admin"
fi
fi
# Force required profiles based on working configuration
if [ -f .env.docker ]; then
# We know qdrant and admin are configured and working
PROFILES="--profile qdrant --profile admin"
fi
# Start the stack with appropriate profiles
docker compose --env-file .env.docker $PROFILES up -d --build
echo ""
echo -e "${GREEN}✅ Discord Fishbowl stack started successfully!${NC}"
echo ""
echo "Services available at:"
echo " 🤖 Discord Fishbowl App: Running in container"
# Get admin port from environment
ADMIN_PORT=${ADMIN_PORT:-8000}
if [ -f .env.docker ]; then
# Try to get admin port from .env.docker
if grep -q "ADMIN_PORT=" .env.docker; then
ADMIN_PORT=$(grep "ADMIN_PORT=" .env.docker | cut -d'=' -f2)
fi
fi
# Get server IP for external access
SERVER_IP=$(ip route get 1.1.1.1 | grep -oP 'src \K\S+' | head -1 2>/dev/null || echo "localhost")
# Check if admin profile is being used
if echo "$PROFILES" | grep -q "admin"; then
echo " 🌐 Admin Interface:"
echo " Local: http://localhost:$ADMIN_PORT"
echo " Network: http://$SERVER_IP:$ADMIN_PORT"
echo " Credentials: admin / FIre!@34"
else
echo " 🌐 Admin Interface: Not enabled (use --profile admin)"
fi
echo " 📊 PostgreSQL: localhost:15432"
echo " 🔴 Redis: localhost:6379"
# Show the correct vector database
if echo "$PROFILES" | grep -q "chromadb"; then
echo " 🧠 ChromaDB: http://localhost:8001"
elif echo "$PROFILES" | grep -q "qdrant"; then
echo " 🔍 Qdrant: http://localhost:6333"
echo " Dashboard: http://localhost:6333/dashboard"
else
# Check if vector database is configured in .env files
if [ -f .env.docker ] && (grep -q "VECTOR_DB_TYPE=" .env.docker || grep -q "QDRANT_" .env.docker); then
echo " 📝 Vector database configured but not in Docker profiles"
else
echo " 📝 No vector database configured"
fi
fi
echo ""
echo "To view logs:"
echo " docker compose logs -f fishbowl # Main application"
echo " docker compose logs -f fishbowl-admin # Admin interface"
echo " docker compose logs -f # All services"
echo ""
echo "To stop:"
echo " docker compose down"
echo ""
echo -e "${YELLOW}📝 Don't forget to configure your Discord tokens in .env.docker!${NC}"

0
exporting Normal file
View File

View File

@@ -224,6 +224,12 @@ class FishbowlSetup:
self.python_executable, "-m", "pip", "install", "-r", "requirements.txt"
], check=True)
self.print_info("Installing additional production dependencies...")
additional_deps = ["asyncpg", "python-dotenv"]
subprocess.run([
self.python_executable, "-m", "pip", "install"
] + additional_deps, check=True)
self.print_success("All Python dependencies installed successfully")
except subprocess.CalledProcessError as e:
self.print_error(f"Failed to install dependencies: {e}")
@@ -294,7 +300,7 @@ class FishbowlSetup:
self.config["database"] = {
"type": "postgresql",
"host": "localhost",
"port": 5432,
"port": 15432,
"name": "discord_fishbowl",
"username": "postgres",
"password": self.ask_question("Database password", "fishbowl_password"),
@@ -305,7 +311,7 @@ class FishbowlSetup:
self.config["database"] = {
"type": "postgresql",
"host": self.ask_question("PostgreSQL host", "localhost"),
"port": int(self.ask_question("PostgreSQL port", "5432")),
"port": int(self.ask_question("PostgreSQL port", "15432")),
"name": self.ask_question("Database name", "discord_fishbowl"),
"username": self.ask_question("Database username", "postgres"),
"password": self.ask_question("Database password", secret=True),
@@ -378,7 +384,7 @@ class FishbowlSetup:
self.config["vector_db"] = {
"type": "chromadb",
"host": "localhost",
"port": 8000,
"port": 8001,
"use_docker": True
}
self.use_docker_services = True
@@ -738,10 +744,17 @@ python -m src.admin.app
f.write(docker_env_content)
self.print_success("Docker environment file created")
# Start Docker services
if self.ask_yes_no("Start Docker services now?", True):
# Ask which Docker setup to use
docker_choices = [
"Services only (PostgreSQL, Redis, ChromaDB)",
"Complete application stack (includes Discord bot and admin interface)",
"Don't start services now"
]
docker_choice = self.ask_choice("Choose Docker setup:", docker_choices, 1)
if "Services only" in docker_choice:
try:
self.print_info("Starting PostgreSQL and Redis containers...")
self.print_info("Starting PostgreSQL, Redis, and ChromaDB containers...")
subprocess.run([
"docker", "compose", "-f", "docker-compose.services.yml",
"--env-file", ".env.docker", "up", "-d"
@@ -759,8 +772,46 @@ python -m src.admin.app
except subprocess.CalledProcessError as e:
self.print_error(f"Failed to start Docker services: {e}")
self.print_info("You can start them manually with: docker compose -f docker-compose.services.yml up -d")
elif "Complete application" in docker_choice:
try:
self.print_info("Building and starting complete Docker stack...")
self.print_warning("This will build the application container, which may take a few minutes...")
# Determine vector database profile
cmd = ["docker", "compose", "--env-file", ".env.docker"]
if self.config["vector_db"]["type"] == "chromadb":
cmd.extend(["--profile", "chromadb"])
elif self.config["vector_db"]["type"] == "qdrant":
cmd.extend(["--profile", "qdrant"])
cmd.extend(["up", "-d", "--build"])
subprocess.run(cmd, check=True, cwd=self.project_root)
self.print_success("Complete Docker stack started successfully")
self.print_success("Discord Fishbowl services are now running!")
print("\n📍 Services available at:")
print(" 🤖 Discord Fishbowl App: Running in container")
print(" 🌐 Admin Interface: http://localhost:8000")
print(" 📊 PostgreSQL: localhost:15432")
print(" 🔴 Redis: localhost:6379")
# Show correct vector database
if self.config["vector_db"]["type"] == "chromadb":
print(" 🧠 ChromaDB: http://localhost:8001")
elif self.config["vector_db"]["type"] == "qdrant":
print(" 🔍 Qdrant: http://localhost:6333")
print(" Dashboard: http://localhost:6333/dashboard")
print("\n💡 Use './docker-start.sh' to restart the complete stack later")
except subprocess.CalledProcessError as e:
self.print_error(f"Failed to start complete Docker stack: {e}")
self.print_info("You can start it manually with: ./docker-start.sh")
else:
self.print_info("To start services later: docker compose -f docker-compose.services.yml --env-file .env.docker up -d")
self.print_info("Docker services not started. You can start them later with:")
print(" Services only: docker compose -f docker-compose.services.yml --env-file .env.docker up -d")
print(" Complete stack: ./docker-start.sh")
def create_docker_env_content(self) -> str:
"""Create Docker environment file content"""
@@ -770,22 +821,64 @@ python -m src.admin.app
"",
]
# Database configuration
if self.config["database"].get("use_docker"):
lines.extend([
"# Database",
f"DB_PASSWORD={self.config['database']['password']}",
"",
])
# Redis configuration
if self.config["redis"].get("use_docker"):
lines.extend([
"# Redis",
f"REDIS_PASSWORD={self.config['redis']['password']}",
"",
])
# Discord configuration
lines.extend([
"# Discord Bot (Replace with your actual tokens)",
f"DISCORD_BOT_TOKEN={self.config['discord']['token']}",
f"DISCORD_GUILD_ID={self.config['discord']['guild_id']}",
f"DISCORD_CHANNEL_ID={self.config['discord']['channel_id']}",
"",
])
# LLM configuration
lines.extend([
"# LLM Configuration",
f"LLM_BASE_URL={self.config['ai']['api_base']}",
f"LLM_MODEL={self.config['ai']['model']}",
"",
])
# Vector database configuration
if self.config.get("vector_db", {}).get("type") in ["chromadb", "qdrant"]:
lines.extend([
"# Vector Database",
f"VECTOR_DB_TYPE={self.config['vector_db']['type']}",
"",
])
# Admin interface configuration
lines.extend([
"# Admin Interface",
f"SECRET_KEY={self.config['admin']['secret_key']}",
f"ADMIN_USERNAME={self.config['admin']['admin_username']}",
f"ADMIN_PASSWORD={self.config['admin']['admin_password']}",
"",
])
# Optional services
lines.extend([
"# Optional PgAdmin credentials (if using --profile admin)",
"PGADMIN_PASSWORD=admin123",
"",
"# Logging",
"LOG_LEVEL=INFO",
"",
])
return "\n".join(lines)
@@ -828,10 +921,15 @@ python -m src.admin.app
# How to start
print("🚀 To start the fishbowl:")
if platform.system() == "Windows":
print(" > start.bat")
if self.use_docker_services:
print(" Complete Docker stack: $ ./docker-start.sh")
print(" Services only: $ docker compose -f docker-compose.services.yml up -d")
print(" Local development: $ ./start.sh")
else:
print(" $ ./start.sh")
if platform.system() == "Windows":
print(" > start.bat")
else:
print(" $ ./start.sh")
print()
# Admin interface

View File

@@ -1,7 +1,7 @@
"""Add creative projects tables
"""Initial tables including creative projects
Revision ID: 004
Revises: 003
Revises: None
Create Date: 2024-12-20 12:00:00.000000
"""
@@ -11,11 +11,130 @@ from sqlalchemy.dialects import postgresql
# revision identifiers
revision = '004'
down_revision = '003'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# Create characters table first
op.create_table('characters',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('name', sa.String(100), unique=True, nullable=False, index=True),
sa.Column('personality', sa.Text(), nullable=False),
sa.Column('system_prompt', sa.Text(), nullable=False),
sa.Column('interests', sa.JSON(), nullable=False, default=list),
sa.Column('speaking_style', sa.Text(), nullable=False),
sa.Column('background', sa.Text(), nullable=False),
sa.Column('avatar_url', sa.String(500)),
sa.Column('is_active', sa.Boolean(), default=True),
sa.Column('creation_date', sa.DateTime(), server_default=sa.func.now()),
sa.Column('last_active', sa.DateTime(), server_default=sa.func.now()),
sa.Column('last_message_id', sa.Integer())
)
# Create conversations table
op.create_table('conversations',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('channel_id', sa.String(100), nullable=False, index=True),
sa.Column('topic', sa.String(200)),
sa.Column('start_time', sa.DateTime(), server_default=sa.func.now()),
sa.Column('end_time', sa.DateTime()),
sa.Column('is_active', sa.Boolean(), default=True),
sa.Column('participant_count', sa.Integer(), default=0)
)
# Create messages table
op.create_table('messages',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('discord_id', sa.String(100), unique=True, index=True),
sa.Column('conversation_id', sa.Integer(), sa.ForeignKey('conversations.id')),
sa.Column('character_id', sa.Integer(), sa.ForeignKey('characters.id')),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.func.now()),
sa.Column('response_to_id', sa.Integer(), sa.ForeignKey('messages.id')),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Add foreign key for last_message_id after messages table is created
op.create_foreign_key('fk_characters_last_message', 'characters', 'messages', ['last_message_id'], ['id'])
# Create memories table
op.create_table('memories',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('memory_type', sa.String(50), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('importance', sa.Float(), default=0.5),
sa.Column('timestamp', sa.DateTime(), server_default=sa.func.now()),
sa.Column('associated_conversation_id', sa.Integer(), sa.ForeignKey('conversations.id')),
sa.Column('associated_character_ids', sa.JSON(), default=list),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create character_relationships table
op.create_table('character_relationships',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('character_a_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('character_b_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('relationship_type', sa.String(50), nullable=False),
sa.Column('strength', sa.Float(), default=0.0),
sa.Column('last_interaction', sa.DateTime(), server_default=sa.func.now()),
sa.Column('interaction_count', sa.Integer(), default=0),
sa.Column('notes', sa.Text()),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create character_evolution table
op.create_table('character_evolution',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('change_type', sa.String(50), nullable=False),
sa.Column('old_value', sa.Text()),
sa.Column('new_value', sa.Text(), nullable=False),
sa.Column('reason', sa.Text()),
sa.Column('timestamp', sa.DateTime(), server_default=sa.func.now()),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create shared_memories table
op.create_table('shared_memories',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('memory_id', sa.Integer(), sa.ForeignKey('memories.id'), nullable=False),
sa.Column('shared_by_character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('shared_with_character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('trust_level_at_time_of_sharing', sa.Float(), nullable=False),
sa.Column('shared_at', sa.DateTime(), server_default=sa.func.now()),
sa.Column('integration_status', sa.String(20), default='integrated'),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create memory_share_requests table
op.create_table('memory_share_requests',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('requester_character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('target_character_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('memory_id', sa.Integer(), sa.ForeignKey('memories.id'), nullable=False),
sa.Column('request_reason', sa.Text(), nullable=False),
sa.Column('status', sa.String(20), default='pending'),
sa.Column('response_reasoning', sa.Text()),
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
sa.Column('responded_at', sa.DateTime()),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create character_trust_levels table
op.create_table('character_trust_levels',
sa.Column('id', sa.Integer(), primary_key=True, index=True),
sa.Column('character_a_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('character_b_id', sa.Integer(), sa.ForeignKey('characters.id'), nullable=False),
sa.Column('trust_score', sa.Float(), default=0.3),
sa.Column('relationship_depth', sa.String(20), default='basic'),
sa.Column('interaction_count', sa.Integer(), default=0),
sa.Column('positive_interactions', sa.Integer(), default=0),
sa.Column('last_updated', sa.DateTime(), server_default=sa.func.now()),
sa.Column('metadata', sa.JSON(), default=dict)
)
# Create creative_projects table
op.create_table('creative_projects',
sa.Column('id', sa.String(255), primary_key=True, index=True),
@@ -89,8 +208,17 @@ def upgrade():
op.create_index('ix_invitations_project', 'project_invitations', ['project_id', 'created_at'])
def downgrade():
# Drop tables in reverse order
# Drop tables in reverse order of creation
op.drop_table('project_invitations')
op.drop_table('project_contributions')
op.drop_table('project_collaborators')
op.drop_table('creative_projects')
op.drop_table('character_trust_levels')
op.drop_table('memory_share_requests')
op.drop_table('shared_memories')
op.drop_table('character_evolution')
op.drop_table('character_relationships')
op.drop_table('memories')
op.drop_table('messages')
op.drop_table('conversations')
op.drop_table('characters')

24
requirements-admin.txt Normal file
View File

@@ -0,0 +1,24 @@
# Minimal requirements for admin interface only
discord.py>=2.3.2
pydantic>=2.5.0
sqlalchemy>=2.0.23
alembic>=1.13.1
pyyaml>=6.0.1
httpx>=0.25.2
python-dotenv>=1.0.0
aiosqlite>=0.19.0
loguru>=0.7.2
# Admin Interface essentials
fastapi>=0.104.1
uvicorn>=0.24.0
python-multipart>=0.0.6
pyjwt>=2.8.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
websockets>=12.0
psutil>=5.9.6
python-socketio>=5.9.0
# Database driver
asyncpg>=0.29.0

View File

@@ -16,7 +16,8 @@ sentence-transformers>=2.3.0
numpy>=1.26.0
faiss-cpu>=1.8.0
# MCP Integration (remove non-existent packages)
# MCP Integration
mcp>=1.0.0
aiofiles>=23.2.0
watchdog>=3.0.0
@@ -28,6 +29,7 @@ nltk>=3.8.1
fastapi>=0.104.1
uvicorn>=0.24.0
python-multipart>=0.0.6
pyjwt>=2.8.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
websockets>=12.0

View File

@@ -5,71 +5,95 @@ Initialize characters in the database from configuration
import asyncio
import sys
import os
from pathlib import Path
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Add src to Python path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from database.connection import init_database, get_db_session
from database.models import Character
from utils.config import get_character_settings
from utils.logging import setup_logging
from database.models import Character, Base
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy import select
import yaml
import logging
logger = setup_logging()
# Setup basic logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def load_character_config():
"""Load character configuration from YAML file"""
config_path = Path(__file__).parent.parent / "config" / "characters.yaml"
if not config_path.exists():
raise FileNotFoundError(f"Character config file not found: {config_path}")
with open(config_path, 'r') as file:
return yaml.safe_load(file)
async def init_characters():
"""Initialize characters from configuration"""
try:
logger.info("Initializing database connection...")
await init_database()
# Get database URL from environment and convert to async format
database_url = os.getenv("DATABASE_URL", "sqlite+aiosqlite:///fishbowl_test.db")
if database_url.startswith("postgresql://"):
database_url = database_url.replace("postgresql://", "postgresql+asyncpg://")
logger.info(f"Connecting to database: {database_url.split('@')[0]}@...")
# Create engine and session
engine = create_async_engine(database_url, echo=False)
session_factory = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
logger.info("Loading character configuration...")
character_settings = get_character_settings()
character_config = load_character_config()
async with get_db_session() as session:
for char_config in character_settings.characters:
async with session_factory() as session:
for char_data in character_config.get('characters', []):
# Check if character already exists
query = select(Character).where(Character.name == char_config.name)
query = select(Character).where(Character.name == char_data['name'])
existing = await session.scalar(query)
if existing:
logger.info(f"Character '{char_config.name}' already exists, skipping...")
logger.info(f"Character '{char_data['name']}' already exists, skipping...")
continue
# Create system prompt
system_prompt = f"""You are {char_config.name}.
system_prompt = f"""You are {char_data['name']}.
Personality: {char_config.personality}
Personality: {char_data['personality']}
Speaking Style: {char_config.speaking_style}
Speaking Style: {char_data['speaking_style']}
Background: {char_config.background}
Background: {char_data['background']}
Interests: {', '.join(char_config.interests)}
Interests: {', '.join(char_data['interests'])}
Always respond as {char_config.name}, staying true to your personality and speaking style.
Always respond as {char_data['name']}, staying true to your personality and speaking style.
Be natural, engaging, and authentic in all your interactions."""
# Create character
character = Character(
name=char_config.name,
personality=char_config.personality,
name=char_data['name'],
personality=char_data['personality'],
system_prompt=system_prompt,
interests=char_config.interests,
speaking_style=char_config.speaking_style,
background=char_config.background,
avatar_url=char_config.avatar_url or "",
interests=char_data['interests'],
speaking_style=char_data['speaking_style'],
background=char_data['background'],
avatar_url=char_data.get('avatar_url', ""),
is_active=True
)
session.add(character)
logger.info(f"Created character: {char_config.name}")
logger.info(f"Created character: {char_data['name']}")
await session.commit()
logger.info("✅ Character initialization completed successfully!")
await engine.dispose()
except Exception as e:
logger.error(f"Failed to initialize characters: {e}")
raise

View File

@@ -18,10 +18,10 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import uvicorn
import socketio
from ..database.connection import init_database, get_db_session
from ..database.models import Character, Conversation, Message, Memory, CharacterRelationship
from ..utils.config import get_settings
from ..utils.logging import setup_logging
from database.connection import init_database, get_db_session
from database.models import Character, Conversation, Message, Memory, CharacterRelationship
from utils.config import get_settings
from utils.logging import setup_logging_interceptor
from .models import (
AdminUser, DashboardMetrics, CharacterProfile, ConversationSummary,
SystemStatus, AnalyticsData
@@ -346,7 +346,7 @@ socket_app = websocket_manager.get_app()
app.mount("/socket.io", socket_app)
# Serve React frontend
@app.mount("/admin", StaticFiles(directory="admin-frontend/build", html=True), name="admin")
app.mount("/admin", StaticFiles(directory="admin-frontend/build", html=True), name="admin")
@app.get("/")
async def root():
@@ -354,10 +354,12 @@ async def root():
return {"message": "Discord Fishbowl Admin Interface", "admin_url": "/admin", "socket_url": "/socket.io"}
if __name__ == "__main__":
import os
admin_port = int(os.getenv("ADMIN_PORT", "8000"))
uvicorn.run(
"src.admin.app:app",
host="0.0.0.0",
port=8000,
port=admin_port,
reload=True,
log_level="info"
)

View File

@@ -10,7 +10,7 @@ from typing import Optional, Dict, Any
import logging
from fastapi import HTTPException
from ..utils.config import get_settings
from utils.config import get_settings
from .models import AdminUser
logger = logging.getLogger(__name__)

View File

@@ -8,9 +8,9 @@ from typing import Dict, List, Any, Optional
from collections import defaultdict, Counter
from sqlalchemy import select, func, and_, or_, desc
from ...database.connection import get_db_session
from ...database.models import Character, Conversation, Message, CharacterRelationship
from ..models import (
from database.connection import get_db_session
from database.models import Character, Conversation, Message, CharacterRelationship
from admin.models import (
TopicTrend, RelationshipAnalytics, CommunityHealth,
EngagementMetrics, Relationship
)

View File

@@ -8,9 +8,9 @@ from typing import List, Dict, Any, Optional
import logging
from sqlalchemy import select, func, and_, or_, desc, asc
from ...database.connection import get_db_session
from ...database.models import Character, Message, Memory, CharacterRelationship, CharacterEvolution
from ..models import (
from database.connection import get_db_session
from database.models import Character, Message, Memory, CharacterRelationship, CharacterEvolution
from admin.models import (
CharacterProfile, CharacterStatusEnum, PersonalityEvolution,
Relationship, MemorySummary, CreativeWork
)

View File

@@ -8,9 +8,9 @@ from typing import List, Dict, Any, Optional
import logging
from sqlalchemy import select, func, and_, or_, desc, asc, text
from ...database.connection import get_db_session
from ...database.models import Conversation, Message, Character
from ..models import ConversationSummary, ConversationDetail, SearchResult
from database.connection import get_db_session
from database.models import Conversation, Message, Character
from admin.models import ConversationSummary, ConversationDetail, SearchResult
logger = logging.getLogger(__name__)

View File

@@ -10,10 +10,10 @@ from collections import deque
import logging
from sqlalchemy import select, func, and_, desc
from ...database.connection import get_db_session
from ...database.models import Character, Conversation, Message, Memory
from ..models import DashboardMetrics, ActivityEvent, ActivityType
from .websocket_manager import WebSocketManager
from database.connection import get_db_session
from database.models import Character, Conversation, Message, Memory
from admin.models import DashboardMetrics, ActivityEvent, ActivityType
from admin.services.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__)

View File

@@ -8,7 +8,7 @@ from typing import Dict, List, Any, Optional
import psutil
import json
from ..models import SystemStatus, SystemStatusEnum, SystemConfiguration, LogEntry
from admin.models import SystemStatus, SystemStatusEnum, SystemConfiguration, LogEntry
logger = logging.getLogger(__name__)

View File

@@ -4,10 +4,10 @@ import asyncio
from typing import Optional, Dict, Any
import logging
from datetime import datetime, timedelta
from ..utils.config import get_settings
from ..utils.logging import log_error_with_context, log_system_health
from ..database.connection import get_db_session
from ..database.models import Message, Conversation, Character
from utils.config import get_settings
from utils.logging import log_error_with_context, log_system_health
from database.connection import get_db_session
from database.models import Message, Conversation, Character
from sqlalchemy import select, and_
logger = logging.getLogger(__name__)

View File

@@ -4,9 +4,9 @@ import asyncio
import logging
from typing import Optional, List, Dict, Any
from datetime import datetime
from ..utils.logging import log_error_with_context, log_character_action
from ..database.connection import get_db_session
from ..database.models import Character, Message, Conversation
from utils.logging import log_error_with_context, log_character_action
from database.connection import get_db_session
from database.models import Character, Message, Conversation
from sqlalchemy import select, and_, or_
logger = logging.getLogger(__name__)

View File

@@ -4,9 +4,9 @@ import json
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict
from ..database.connection import get_db_session
from ..database.models import Character as CharacterModel, Memory, CharacterRelationship, Message, CharacterEvolution
from ..utils.logging import log_character_action, log_error_with_context, log_autonomous_decision, log_memory_operation
from database.connection import get_db_session
from database.models import Character as CharacterModel, Memory, CharacterRelationship, Message, CharacterEvolution
from utils.logging import log_character_action, log_error_with_context, log_autonomous_decision, log_memory_operation
from sqlalchemy import select, and_, or_, func, desc
import logging

View File

@@ -4,18 +4,18 @@ from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
from .character import Character
from .personality import PersonalityManager
from .memory import MemoryManager
from ..rag.personal_memory import PersonalMemoryRAG, MemoryInsight
from ..rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from ..rag.memory_sharing import MemorySharingManager, SharePermissionLevel
from ..mcp.self_modification_server import SelfModificationMCPServer
from ..mcp.file_system_server import CharacterFileSystemMCP
from ..mcp.memory_sharing_server import MemorySharingMCPServer
from ..mcp.creative_projects_server import CreativeProjectsMCPServer
from ..utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from ..database.models import Character as CharacterModel
from characters.character import Character
from characters.personality import PersonalityManager
from characters.memory import MemoryManager
from rag.personal_memory import PersonalMemoryRAG, MemoryInsight
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from rag.memory_sharing import MemorySharingManager, SharePermissionLevel
from mcp_servers.self_modification_server import SelfModificationMCPServer
from mcp_servers.file_system_server import CharacterFileSystemMCP
from mcp_servers.memory_sharing_server import MemorySharingMCPServer
from mcp_servers.creative_projects_server import CreativeProjectsMCPServer
from utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from database.models import Character as CharacterModel
import logging
logger = logging.getLogger(__name__)

View File

@@ -3,9 +3,9 @@ import json
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
from ..database.connection import get_db_session
from ..database.models import Memory, Character, Message, CharacterRelationship
from ..utils.logging import log_memory_operation, log_error_with_context
from database.connection import get_db_session
from database.models import Memory, Character, Message, CharacterRelationship
from utils.logging import log_memory_operation, log_error_with_context
from sqlalchemy import select, and_, or_, func, desc
import logging

View File

@@ -2,9 +2,9 @@ import json
import random
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime
from ..utils.logging import log_character_action, log_error_with_context
from ..database.connection import get_db_session
from ..database.models import CharacterEvolution, Character as CharacterModel
from utils.logging import log_character_action, log_error_with_context
from database.connection import get_db_session
from database.models import CharacterEvolution, Character as CharacterModel
from sqlalchemy import select
class PersonalityManager:

View File

@@ -12,11 +12,11 @@ from dataclasses import dataclass, asdict
from enum import Enum
import hashlib
from ..rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from ..rag.memory_sharing import MemorySharingManager
from ..utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from ..database.connection import get_db_session
from ..database.models import (
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from rag.memory_sharing import MemorySharingManager
from utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from database.connection import get_db_session
from database.models import (
Character, CreativeProject as DBCreativeProject, ProjectCollaborator,
ProjectContribution as DBProjectContribution, ProjectInvitation as DBProjectInvitation
)

View File

@@ -7,14 +7,14 @@ from dataclasses import dataclass, asdict
from enum import Enum
import logging
from ..database.connection import get_db_session
from ..database.models import Character as CharacterModel, Conversation, Message, Memory
from ..characters.character import Character
from ..characters.enhanced_character import EnhancedCharacter
from ..llm.client import llm_client, prompt_manager
from ..llm.prompt_manager import advanced_prompt_manager
from ..utils.config import get_settings, get_character_settings
from ..utils.logging import (log_conversation_event, log_character_action,
from database.connection import get_db_session
from database.models import Character as CharacterModel, Conversation, Message, Memory
from characters.character import Character
from characters.enhanced_character import EnhancedCharacter
from llm.client import llm_client, prompt_manager
from llm.prompt_manager import advanced_prompt_manager
from utils.config import get_settings, get_character_settings
from utils.logging import (log_conversation_event, log_character_action,
log_autonomous_decision, log_error_with_context)
from sqlalchemy import select, and_, or_, func, desc
@@ -402,8 +402,8 @@ class ConversationEngine:
# Use EnhancedCharacter if RAG systems are available
if self.vector_store and self.memory_sharing_manager:
# Find the appropriate MCP servers for this character
from ..mcp.self_modification_server import mcp_server
from ..mcp.file_system_server import filesystem_server
from mcp.self_modification_server import mcp_server
from mcp.file_system_server import filesystem_server
# Find creative projects MCP server
creative_projects_mcp = None

View File

@@ -7,8 +7,8 @@ from dataclasses import dataclass
from enum import Enum
import logging
from ..utils.logging import log_autonomous_decision, log_error_with_context, log_system_health
from ..utils.config import get_settings
from utils.logging import log_autonomous_decision, log_error_with_context, log_system_health
from utils.config import get_settings
logger = logging.getLogger(__name__)

View File

@@ -4,7 +4,10 @@ from sqlalchemy.orm import sessionmaker
from contextlib import asynccontextmanager
from typing import AsyncGenerator, Optional
import logging
from ..utils.config import get_settings
try:
from utils.config import get_settings
except ImportError:
from utils.config import get_settings
logger = logging.getLogger(__name__)
@@ -87,7 +90,7 @@ class DatabaseManager:
db_manager = DatabaseManager()
# Convenience functions
async def get_db_session():
def get_db_session():
return db_manager.get_session()
async def init_database():

View File

@@ -25,7 +25,7 @@ class Character(Base):
# Relationships
messages = relationship("Message", back_populates="character", foreign_keys="Message.character_id")
memories = relationship("Memory", back_populates="character", cascade="all, delete-orphan")
memories = relationship("Memory", back_populates="character", foreign_keys="Memory.character_id", cascade="all, delete-orphan")
relationships_as_a = relationship("CharacterRelationship", back_populates="character_a", foreign_keys="CharacterRelationship.character_a_id")
relationships_as_b = relationship("CharacterRelationship", back_populates="character_b", foreign_keys="CharacterRelationship.character_b_id")
evolution_history = relationship("CharacterEvolution", back_populates="character", cascade="all, delete-orphan")

View File

@@ -4,8 +4,8 @@ import json
import time
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta
from ..utils.config import get_settings
from ..utils.logging import log_llm_interaction, log_error_with_context, log_system_health
from utils.config import get_settings
from utils.logging import log_llm_interaction, log_error_with_context, log_system_health
import logging
logger = logging.getLogger(__name__)
@@ -55,33 +55,61 @@ class LLMClient:
start_time = time.time()
# Prepare request
request_data = {
"model": self.model,
"prompt": prompt,
"options": {
"temperature": temperature or self.temperature,
"num_predict": max_tokens or self.max_tokens,
"top_p": 0.9,
"top_k": 40,
"repeat_penalty": 1.1
},
"stream": False
}
# Make API call
# Try OpenAI-compatible API first (KoboldCPP, etc.)
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.post(
f"{self.base_url}/api/generate",
json=request_data,
headers={"Content-Type": "application/json"}
)
try:
# OpenAI-compatible request
request_data = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature or self.temperature,
"max_tokens": max_tokens or self.max_tokens,
"top_p": 0.9,
"stream": False
}
response.raise_for_status()
result = response.json()
response = await client.post(
f"{self.base_url}/chat/completions",
json=request_data,
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
result = response.json()
if 'response' in result and result['response']:
generated_text = result['response'].strip()
if 'choices' in result and result['choices'] and 'message' in result['choices'][0]:
generated_text = result['choices'][0]['message']['content'].strip()
else:
generated_text = None
except (httpx.HTTPStatusError, httpx.RequestError, KeyError):
# Fallback to Ollama API
request_data = {
"model": self.model,
"prompt": prompt,
"options": {
"temperature": temperature or self.temperature,
"num_predict": max_tokens or self.max_tokens,
"top_p": 0.9,
"top_k": 40,
"repeat_penalty": 1.1
},
"stream": False
}
response = await client.post(
f"{self.base_url}/api/generate",
json=request_data,
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
result = response.json()
if 'response' in result and result['response']:
generated_text = result['response'].strip()
else:
generated_text = None
if generated_text:
# Cache the response
self._cache_response(cache_key, generated_text)
@@ -143,11 +171,18 @@ class LLMClient:
"""Check if the LLM model is available"""
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(f"{self.base_url}/api/tags")
response.raise_for_status()
models = response.json()
available_models = [model.get('name', '') for model in models.get('models', [])]
# Try OpenAI-compatible API first (KoboldCPP, etc.)
try:
response = await client.get(f"{self.base_url}/models")
response.raise_for_status()
models = response.json()
available_models = [model.get('id', '') for model in models.get('data', [])]
except (httpx.HTTPStatusError, httpx.RequestError):
# Fallback to Ollama API
response = await client.get(f"{self.base_url}/api/tags")
response.raise_for_status()
models = response.json()
available_models = [model.get('name', '') for model in models.get('models', [])]
is_available = any(self.model in model_name for model_name in available_models)

View File

@@ -2,7 +2,7 @@ import json
import re
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime
from ..utils.logging import log_error_with_context
from utils.logging import log_error_with_context
import logging
logger = logging.getLogger(__name__)

View File

@@ -25,11 +25,11 @@ from rag.vector_store import vector_store_manager
from rag.community_knowledge import initialize_community_knowledge_rag
from rag.memory_sharing import MemorySharingManager
from collaboration.creative_projects import CollaborativeCreativeManager
from mcp.self_modification_server import mcp_server
from mcp.file_system_server import filesystem_server
from mcp.calendar_server import calendar_server
from mcp.memory_sharing_server import initialize_memory_sharing_mcp_server
from mcp.creative_projects_server import initialize_creative_projects_mcp_server
from mcp_servers.self_modification_server import mcp_server
from mcp_servers.file_system_server import filesystem_server
from mcp_servers.calendar_server import calendar_server
from mcp_servers.memory_sharing_server import MemorySharingMCPServer
from mcp_servers.creative_projects_server import initialize_creative_projects_mcp_server
import logging
# Setup logging first
@@ -118,7 +118,7 @@ class FishbowlApplication:
logger.info("Calendar/time awareness MCP server initialized")
# Initialize memory sharing MCP server
memory_sharing_mcp = initialize_memory_sharing_mcp_server(self.memory_sharing_manager)
memory_sharing_mcp = MemorySharingMCPServer(self.memory_sharing_manager)
self.mcp_servers.append(memory_sharing_mcp)
logger.info("Memory sharing MCP server initialized")

View File

View File

@@ -11,9 +11,9 @@ from mcp.server.stdio import stdio_server
from mcp.server import Server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from ..utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from ..database.connection import get_db_session
from ..database.models import Character, Message, Conversation
from utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from database.connection import get_db_session
from database.models import Character, Message, Conversation
from sqlalchemy import select, and_, or_, func, desc
import logging

View File

@@ -14,21 +14,18 @@ from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import (
CallToolRequestParams,
GetToolRequestParams,
ListToolsRequestParams,
ListToolsRequest,
TextContent,
Tool,
INVALID_PARAMS,
INTERNAL_ERROR
Tool
)
from ..collaboration.creative_projects import (
from collaboration.creative_projects import (
CollaborativeCreativeManager,
ProjectType,
ContributionType,
ProjectStatus
)
from ..utils.logging import log_character_action, log_error_with_context
from utils.logging import log_character_action, log_error_with_context
logger = logging.getLogger(__name__)
@@ -47,7 +44,7 @@ class CreativeProjectsMCPServer:
"""Register all creative project tools"""
@self.server.list_tools()
async def handle_list_tools(request: ListToolsRequestParams) -> list[Tool]:
async def handle_list_tools() -> list[Tool]:
"""List available creative project tools"""
return [
Tool(

View File

@@ -11,8 +11,8 @@ from mcp.server.stdio import stdio_server
from mcp.server import Server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from ..utils.logging import log_character_action, log_error_with_context
from ..rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from utils.logging import log_character_action, log_error_with_context
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
import logging
logger = logging.getLogger(__name__)

View File

@@ -16,12 +16,12 @@ from mcp.types import (
LoggingLevel
)
from ..rag.memory_sharing import (
from rag.memory_sharing import (
MemorySharingManager, SharePermissionLevel, ShareRequestStatus,
SharedMemory, ShareRequest, TrustLevel
)
from ..rag.vector_store import VectorStoreManager
from ..utils.logging import log_character_action, log_error_with_context
from rag.vector_store import VectorStoreManager
from utils.logging import log_character_action, log_error_with_context
logger = logging.getLogger(__name__)

View File

@@ -10,9 +10,9 @@ from mcp.server.stdio import stdio_server
from mcp.server import Server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from ..database.connection import get_db_session
from ..database.models import Character, CharacterEvolution
from ..utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from database.connection import get_db_session
from database.models import Character, CharacterEvolution
from utils.logging import log_character_action, log_error_with_context, log_autonomous_decision
from sqlalchemy import select
import logging

View File

@@ -5,10 +5,10 @@ from datetime import datetime, timedelta
from dataclasses import dataclass
from collections import defaultdict
from .vector_store import VectorStoreManager, VectorMemory, MemoryType
from ..utils.logging import log_conversation_event, log_error_with_context
from ..database.connection import get_db_session
from ..database.models import Conversation, Message, Character
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from utils.logging import log_conversation_event, log_error_with_context
from database.connection import get_db_session
from database.models import Conversation, Message, Character
from sqlalchemy import select, and_, or_, func, desc
import logging

View File

@@ -11,11 +11,11 @@ from dataclasses import dataclass, asdict
from enum import Enum
import json
from .vector_store import VectorStoreManager, VectorMemory, MemoryType
from .personal_memory import PersonalMemoryRAG, MemoryInsight
from ..database.connection import get_db_session
from ..database.models import Character, CharacterRelationship
from ..utils.logging import log_character_action, log_error_with_context
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from rag.personal_memory import PersonalMemoryRAG, MemoryInsight
from database.connection import get_db_session
from database.models import Character, CharacterRelationship
from utils.logging import log_character_action, log_error_with_context
from sqlalchemy import select, and_
logger = logging.getLogger(__name__)

View File

@@ -4,10 +4,10 @@ from datetime import datetime, timedelta
from dataclasses import dataclass
import json
from .vector_store import VectorStoreManager, VectorMemory, MemoryType
from ..utils.logging import log_character_action, log_error_with_context, log_memory_operation
from ..database.connection import get_db_session
from ..database.models import Memory
from rag.vector_store import VectorStoreManager, VectorMemory, MemoryType
from utils.logging import log_character_action, log_error_with_context, log_memory_operation
from database.connection import get_db_session
from database.models import Memory
import logging
logger = logging.getLogger(__name__)

View File

@@ -10,8 +10,8 @@ from dataclasses import dataclass, asdict
from enum import Enum
from sentence_transformers import SentenceTransformer
from ..utils.logging import log_error_with_context, log_character_action
from ..utils.config import get_settings
from utils.logging import log_error_with_context, log_character_action
from utils.config import get_settings
import logging
logger = logging.getLogger(__name__)

View File

@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class DatabaseConfig(BaseModel):
host: str = "localhost"
port: int = 5432
port: int = 15432
name: str = "discord_fishbowl"
user: str = "postgres"
password: str
@@ -76,7 +76,13 @@ def load_yaml_config(file_path: str) -> Dict[str, Any]:
def replace_env_var(match):
var_name = match.group(1)
default_value = match.group(2) if match.group(2) else ""
return os.getenv(var_name, default_value)
value = os.getenv(var_name, default_value)
# Force Discord IDs to be strings by quoting them
if var_name in ['DISCORD_GUILD_ID', 'DISCORD_CHANNEL_ID'] and value and not value.startswith('"'):
value = f'"{value}"'
return value
# Replace ${VAR} and ${VAR:-default} patterns
content = re.sub(r'\$\{([^}:]+)(?::([^}]*))?\}', replace_env_var, content)

18
start-admin.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Discord Fishbowl - Admin Interface
echo "🌐 Starting Admin Interface..."
# Activate virtual environment
source "/home/matt/discord-fishbowl/venv/bin/activate"
# Set Python path
export PYTHONPATH="/home/matt/discord-fishbowl:$PYTHONPATH"
# Load environment variables
if [ -f "/home/matt/discord-fishbowl/.env" ]; then
export $(cat "/home/matt/discord-fishbowl/.env" | xargs)
fi
# Start admin interface
python -m src.admin.app

18
start.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Discord Fishbowl - Main Application
echo "🐠 Starting Discord Fishbowl..."
# Activate virtual environment
source "/home/matt/discord-fishbowl/venv/bin/activate"
# Set Python path
export PYTHONPATH="/home/matt/discord-fishbowl:$PYTHONPATH"
# Load environment variables
if [ -f "/home/matt/discord-fishbowl/.env" ]; then
export $(cat "/home/matt/discord-fishbowl/.env" | xargs)
fi
# Start the application
python -m src.main "$@"